文章目录
哎,实在是想不出有什么好的实战项目。如果你们有什么思路,可以提供提供,我们的目标是解决生活中,工作中遇到的问题。一起边学边写代码。
想不到也得继续呀。那就来个增加趣味性的pygame项目-贪吃蛇
方案一: 搜索 “python3 贪吃蛇” 有很多案例,直接拷贝。或者看别人代码如何写的。
方案二: 跟着我思路,一步一步的探索,我对pygame 也不熟悉。
认识pygame
我在网上搜 python 游戏,有很多项目都用到了pygame
那就试试这个吧
首先,肯定是去官网看看https://www.pygame.org/docs/
当然,如果不想看英文,搜素下也能看到中文版本的介绍,和案例。
安装pygame
python 解释器给我们提供了很多强大又好用的模块,我们可以不需要额外的安装就能import使用。
但是也有很多库是需要我们自己安装的。pygame就是其中之一
pip 是python的安装包工具。如果你是安装了python2 python3两个版本,那么pip3 才是对应python3的安装包工具。
python3 -m pip install -U pygame --user
# 或者
pip3 install pygame
容我先看教程学点东西
http://blog.sina.com.cn/s/blog_71c2d34a0101r1gy.html
这里有个翻译,还不错。
分析怎么做
先看看我想做的效果图
然后进行拆解:
- 我需要一个游戏窗口
- 我需要一条蛇(白色的块)
2.1 这条蛇能自己往前移动
2.2 这条蛇能接受键盘控制上下左右动
2.3 蛇吃到食物会变长
2.4 当蛇吃到自己的时候,游戏结束 - 我需要一个食物(红色的块)
3.1 这个食物随机出现位置
3.2 这个食物不会动
3.3 这个食物全屏只能出现一个
3.4 这个食物不能出现在蛇身上
3.5 当食物没办法生成(满屏都是蛇)的时候,游戏结束
我发现边讲解边写代码还是有点困难,篇幅会打很多。所以我打算用git 管理
git 是一个版本控制的工具。你需要安装它. 点击查看安装教程
# 下载代码
git clone https://github.com/tfhappy/pygame_snake.git
# 进入我的项目
cd pygame_snake
# 查看有哪些步骤
git tag
# 选择并查看某个步骤的代码, 这里以"1_官方案例" 为例子
git checkout 1_官方案例
git checkout {tag_name} 之后,当前项目下你的代码就会变成制定版本的代码。
这样你就可以一边看着步骤,一边查看代码是如何变更的了。
步骤
1_官方案例
# 在项目的路径下
git checkout 1_官方案例
# 你会发现你的文件夹子下只有3个文件
.
├── README.md
├── demo.py
└── intro_ball.gif
这是https://pygame.org/docs/tut/PygameIntro.html展示的代码.
能够帮你快速的了解pygame
2_创建一个窗口
# 在项目的目录下
git checkout 2_创建一个窗口
# 你会发现项目下多了个文件
.
├── README.md
├── demo.py
├── intro_ball.gif
└── snake.py
# 运行snake.py 查看效果
python3 snake.py
查看snake.py的代码,许多内容我都已写到注释中了
如果你想看每个步骤和上一步的差别是什么,可以用git show 命令
如:
# 显示 1_官方案例 到 2_创建一个窗口 做了哪些改动
git shwo 2_创建一个窗口
一开始你可能看得不是很习惯,没关系,你可以选择直接看代码。
知识点
-
元组(tuple)
元组也是一种数据类型,和列表很像,用()包围a = (1,2,3) # 第一种赋值方法 b = 1,2,3 # 可以不带括号 print(type(a)) # type方法查看一个对象的类型 print(type(b)) # 打印结果: <class 'tuple'> print(a == b) # True # 元组内的值不可修改,如 a[0] = 10 # TypeError: 'tuple' object does not support item assignment
-
连续赋值
a = b = 0 # 把0赋值给a和b a = b = c = d = 0 # 把0赋值给a,b,c,d
连续赋值的写法能让我们更快的开发
-
退出进程
我们知道代码执行完毕后,程序会退出。但是有些情况下,我们希望提前退出。那么就可以用sys模块下的exit方法。sys 是一个内置模块,不需要安装import sys sys.exit()
-
一行实现if语句
a = 0 if a == 0: print(a) # 等价写法 a = 0 if a == 0: print(a)
一般用于只执行一条语句
-
time.sleep()
time 包也是内置的包import time # sleep 接受一个数字类型的参数, 单位s # 功能:让程序休眠一段时间(什么都不干) time.sleep(1) # 休眠1s
3_填充黑色背景
如何查看代码,不再描述
知识点:
29行代码修正:
# 你看到的代码
screen.fill((0,0,0))
# 应该为
screen.fill(black)
这里black
也是(0, 0, 0)
, 所以是等效的。发现有点晚了,只能补个说明到这里了。
4_画蛇头
- pygame.Color(r, g, b, a=255)
pygame 封装的一个类。这样我可以对颜色做更多的操作
可以接收4个参数,我们只用了前三个。第4个参数控制透明度,值域:[0,255]
扩展阅读 - pygame.Rect
创建一个矩形
扩展阅读
5_画食物
画食物和画蛇头没什么太大的区别
但是我们的食物的位置应该是随机的
random 是一个内置的包,提供了很多随机数生成的方法
randint 只是其中一个方法,随机生成一个整数
import random
num = random.randint(0,2) # 随机生成一个整数, 整数值的范围: 0<=num<=2
现在每次运行代码,你应该能看到每次食物都在不同的位置
可能你会想到这样实现有点问题,先别急,我们慢慢来
6_让蛇头动起来
ps: 从这里开始,为了代码看起来简洁,我删掉了一些多余的注释。
先思考
我们现在蛇头是在右上角,那我们设定初始的运动方向为右。
那如何动起来呢?
看看现在的实现
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
screen.fill(black) # 填充黑色背景
pygame.draw.rect(screen, snake_head_color, snake_head) # 画蛇头
pygame.draw.rect(screen, food_color, food) # 画食物
pygame.display.flip() # 刷新屏幕
time.sleep(1)
我们把刷新屏幕放到了一个无限循环中,也就是说,每次循环就会刷新一次屏幕。
如果我们画蛇头的时候,位置是变的,那么是不是就可以动起来了?
talk is cheap,show you the code
…
over ~ 蛇动起来了,你可以调整到你喜欢的速度哦
6_让蛇头动起来_2
# 每次循环加上一个移动单位, 生成新的位置
left = snake_head_pos[0] + move_to[0]
top = snake_head_pos[1] + move_to[1]
snake_head_pos = (left, top)
snake_head = pygame.Rect(snake_head_pos, snake_head_size) # 重新创建一个蛇头
pygame.draw.rect(screen, snake_head_color, snake_head) # 画蛇头
这是上一个步骤的逻辑。
move_to
的值一直是(3,0)
所以每次刷新,snake_head_pos 都会在横坐标+3
我们还可以这样写,让代码更为简洁
# 每次循环加上一个移动单位, 生成新的位置
snake_head_pos = tuple(map(lambda x, y: x + y, snake_head_pos, move_to))
snake_head = pygame.Rect(snake_head_pos, snake_head_size) # 重新创建一个蛇头
pygame.draw.rect(screen, snake_head_color, snake_head) # 画蛇头
效果是一样的。
这里涉及了几个新概念:
-
类型强转
类似函数调用,函数名用类型的名称。
作用是强行转换参数的类型,并返回。如果无法强转,则会报错# 强转为字符串 a = str(10) print(a, type(a)) # '10' <class 'str'> b = str([1,2,3]) print(b, type(b)) # '[1, 2, 3]' <class 'str'> # 强转为整数 c = int('10') print(c, type(c) # 10 <class 'int'> d = int('abc') # 报错, 无法强转
其他类型也是这般,上面的代码中我们强行转换为
tuple
类型, 也就是元组类型 -
map 函数
map 函数是python内置的函数
使用方法: https://www.runoob.com/python/python-func-map.html
大概是第一个参数是一个函数,其他参数为函数使用的参数
返回的是一个可迭代对象。
凡是能用for遍历的都叫可迭代对象。
map函数返回的值,不能像列表一样用[index]
取值 , 所以我们在上面强转为tuple
-
lambda
匿名函数, 经常和map连用
使用方法: https://www.runoob.com/python3/python3-function.htmlmap 和 lambda 一开始可能比较不好理解,不要求掌握,大概知道那么一回事就好
嗯,到这,我们属于优化了一次。
但是这还不是我单独列一步骤的目的。
其实,像移动一个矩形这种事。pygame 早就帮我们做好了,上面的代码你可以认为是帮助你理解移动的原理。
我们看下官方文档,看看Rect 给我们提供了什么方法http://www.pygame.org/docs/ref/rect.html
嗯,假装你看到了,我们其实可以用move方法,所以代码再次改造
# 每次循环加上一个移动单位, 生成新的位置
snake_head = snake_head.move(move_to)
pygame.draw.rect(screen, snake_head_color, snake_head) # 画蛇头
ps: 别忘了git checkout 查看完整代码
7_控制蛇头方向
上一步,我们实现了让蛇头自己动。但是,只有一个方向。而且超出边界后也没有结束游戏。
我们先来解决控制的问题
我们希望能够使用键盘来控制蛇头的运动方向,也就是说,程序需要知道你是什么时候按下键盘的。
https://www.pygame.org/docs/ref/key.html
看看官方教程的例子
for event in pygame.event.get():
if event.type == pygame.KEYDOWN or event.type == pygame.KEYUP:
if event.mod == pygame.KMOD_NONE:
print('No modifier keys were in a pressed state when this '
'event occurred.')
else:
if event.mod & pygame.KMOD_LSHIFT:
print('Left shift was in a pressed state when this event '
'occurred.')
if event.mod & pygame.KMOD_RSHIFT:
print('Right shift was in a pressed state when this event '
'occurred.')
if event.mod & pygame.KMOD_SHIFT:
print('Left shift or right shift or both were in a '
'pressed state when this event occurred.')
第一行有没有觉得很熟悉
然后我们继续读
event
这里代表了事件
event.type
翻译过来就是事件的类型
event.type == pygame.KEYDOWN
这里看着就是 如果事件的类型等于一个什么东西,那么pygame.KEYDOWN
是一个值。如果你想象力够丰富,应该能看出来 KEYDOWN
就是按键按下去的意思;KEYUP
就是案件起来
这合起来就是, 如果事件的类型为按键按下或者按键弹起。
好了,大概就是这样读代码的。我就不啰嗦了
直接改造成我们需要的。
8_碰到边界退出游戏
退出游戏后我们希望界面能停留,而且显示"game over" 的字样,而不是直接退出程序
如何显示字体:https://www.pygame.org/docs/ref/font.html#pygame.font.Font
(需要一步一步的看每个方法有什么功能,返回值是什么)
另外,因为退出游戏的步骤会经常使用,我们封装成了game_over函数
9_吃
- 蛇头移动
- 蛇身体移动
- 判断蛇头和食物是否有碰撞
3.1. 如果有,则身体变长,生成新的食物 - 判断蛇头和身体是否有碰撞
2.1. 如果有,游戏结束
存在问题:
- 因为我们不是一个一个像素移动的,所以有可能发生在过程中吃到食物/身体。
- 同理,身体也不是一个一个像素移动的,如何才能让身体紧跟身体?
解决方案:
每次只移动一个像素, 但是移动speed个像素后,才刷新一次屏幕
如果这个方案效率不行,再考虑别的方案。
10_部分优化
食物的生成:
如果随机生成的位置,在蛇身上怎么办?
如果简单的判断如果在蛇身上,就再次随机生成,那么,当蛇的身体很长的时候,落在蛇身上的概率是很大的
另外,如果已经没有空间生成了,应该游戏结束。
控制速度:
目前的方案控制速度没有和食物生成的位置关联起来,导致你可能永远吃不到食物
项目后期可能还有改动,可以在github查看。这里不再介绍。