贪吃蛇 —— Python 小项目实战
贪吃蛇 —— Python 小项目实战
前言:
在知乎上听大牛说编程直接上项目就是干,以项目为导向,以开发为目标,效果非常好。正巧最近想学python,故尝试以零基础做小项目,疑难部分通过谷歌和书本解决,并通过发表博文检验和锤炼学习成果。
本文结构的思维导图:
导入贪吃蛇小游戏所需要的库和模块
python导入库和模块
使用python进行编程时,有些功能无法用原生python实现。这时需要导入一些python库或者模块,这类似于在Windows操作系统为了实现操作系统没有的功能而去install相应的软件。
导出的基本语法为
import Modulename
Module 模块
或
import Modulename as Modulename_Aliases
Alias 别名
如常用的
import pandas
import pandas as np
当有多个库或模块时,也可以写到一句中,用逗号隔开
import Module1, Module2, Module3
导入需要的库
pygame
pygame
库是一个免费开源的python
库,一个利用SDL库的写就的游戏库。sys
sys
是一个python
标准模块,提供了一些变量和函数。(在该程序中可加可不加)
- random
random
属于python
标准库中的数学和数字模块,作用是生成随机数。在贪吃蛇中,食物出现的位置时随机的。
1 | import pygame |
或者是
1 | import pygame, sys, random |
设定贪吃蛇游戏界面的大小
全局定义
程序中如果有常量,如恒定的数等。可以在开头全局定义。
语法格式为
name = value
value 值
贪吃蛇游戏中,游戏界面的长和宽是不会变化的,是恒定的值
设定贪吃蛇游戏界面的大小
1 | # 全局定义游戏界面的长和宽(单位为分辨率) |
批注:在Python中,我们用缩进来表示不同代码之间的关系。若要说明一段代码从属于另一端代码,则需要通过缩进四个空格或一个Tab
键来表示。不缩进的代码段之间为同级关系。Python中正确的运用缩进不仅能使程序顺利运行,还可以增强代码的可读性。
#1 我们已经知道python有一个特殊的“库(模块)”叫pygame了。在我们要动手用它完成我们的想法之前,电脑这个强迫症需要我们检查一遍,这个工具包是否完整,能否正常给我们提供帮助。而这个检查的动作,就是pygame.init()
。
#2 pygame.display.set_mode(resolution=(SCREEN_X,SCREEN_Y))
初始化一个准备显示的窗口或屏幕。
#3 pygame.display.set_caption(‘title’)
设置当前游戏窗口的标题
当设置完成后,运行代码就可以看到一个“一闪而过”的游戏窗口了
用类创建一个“贪吃蛇”模板
Python从问世之初就是一个可以面向对象的语言。在面向对象编程中,可以用类(class)表示现实世界的事物和情形。类(class)类似于现实世界中的模板。当创建了类后,你定义了整个对象类别可以有的一般行为和特征。
如在贪吃蛇游戏中,我需要定义一个贪吃蛇的类,在其中定义一般贪吃蛇所具有的行为和特征。当用贪吃蛇的类创建一个独立的对象——一条贪吃蛇,就等于把这个类实例化(instantiation)。该条贪吃蛇就可以一个实例(instance)。
在python中创建一个类的基本语法为(在Python中以下三者等价):
class ClassName:
class ClassName():
class ClassName(object):
创建一个贪吃蛇的类,并初始化个各种需要的属性
1 | # 在Python中类的名称一般首字母大写,与类的实例(全部小写)进行区分 |
类中的函数在Python中叫做方法(method)。在类中的方法具有函数的一切特征,也有一些区别。
创建函数的一般语法为
def funcitonname(parameter1, parameter2, ...):
parameter 参数
#4处的__init__
是一类特殊的函数。当我们根据类创建了一个实例时,Python会自动调用__init__
函数。 在Python中的每个类中都必须要有__init__
函数。
#4处定义的__init__
函数一个参数self
。**self
参数在每一个类的每一个方法里都是必须的,而且顺序必须是第一个。**因为当我们根据类创建实例后,每一个与实例相关联的方法的调用会自动传递self
参数,self
是对实例本身的引用,它可以让各个实例可以访问类中的属性。在我们创建的__init__
函数中,不需要其他参数,所以只需要self
一个必须参数就可以了。
#5处的变量self.dirction
带有self
的前缀。在类中任何以self为前缀的变量都可以被类中的每个方法调用(因为每个方法中都有参数self),我们也可以通过从类中创建的任何实例来访问这些变量。
#5处的pygame.K_RIGHT
是Pygame库中的属性,作用是使创建的对象(在这里是贪吃蛇)向右运动,相当于键入→方向键。
同理 pygame.K_UP
使创建的对象向上运动。
同理 pygame.K_DOWN
使创建的对象向下运动。
Pygame库中可以用字符常量表示输入键盘中的特定键位。
Pygame Constant | The key in keyboard |
---|---|
K_UP | up arrow |
K_DOWN | down arrow |
K_LEFT | left arrow |
K_RIGHT | right arrow |
K_SPACE | space |
#6处为self参数创建一个body的属性,body初始化为空列表,利用列表的可变性(列表中元素的值和数量都可以变化)存放蛇块。
#7处,为self参数创建一个addnote()的属性,用以增加蛇块的数量,初始化蛇块数量设定为5(可以根据个人喜好修改,但不宜过多)
设定贪吃蛇移动时的蛇块变化
1 | # 无论何时 都在前端增加蛇块 |
#9 在这段代码中我们创建了addnode方法,该方法有且self这一个必须参数
#10 设定贪吃蛇的出现位置在最右上角。left, top在后文代表的意思可以用下面这张图片解释。
#11处出现的rect函数中的left, top指的是矩形区域(白色部分)距离x轴和y轴的水平距离和垂直距离。
Pygame 通过 Rect 对象存储和操作矩形区域,由pygame.Rect(left, top, width, height)命令创建。
该一整段代码的运行流程:
在__init__
函数中有一个for循环,使addnote()方法按照该流程循环了5次。
第一次循环并没有执行if self.body:
后的语句,因为我们最初定义的self.body
是一个空列表。所以第一次循环首先执行node = pygame.Rect(left, top, 25, 25)
。这时我们创建了一个在游戏界面最右上角,大小为25*25像素的矩形对象node。因为在__init__
函数中我们self.dirction = pygame.K_RIGHT
语句使对象向右移动,所以判定向右运动为真,node变量的left参数加25——即向右运动了25像素。这样实现了一个像素块向右的移动。最后的self.body.insert(0,node)
命令在self.body列表的插入node变量,并将其放在第一位。
之后四次循环中,因为self.body
列表不再是空列表,所以总是执行第一个if语句后的命令,将参数left,top赋值为self.body
列表中的第一位,这样做,使得每次循环之初的node对象总是上一次循环得到的移动过后的像素块,并在此基础上根据运动方向再次移动——这样做实现 贪吃蛇身体的连续,使得贪吃蛇刚出来就是完整的五个25*25像素块
这样做是不够的,因为随着贪吃蛇的移动,是不断右像素块(图中绿色,颜色可自定义)产生和像素块的消失。在最后的像素块总是最先消失的。也就是self.body[-1]
会随着每次移动而删去。
所以我们需要另外创建一个delnode函数
1 | def delnode(self): |
delnode方法同样仅有self这一必须参数,pop函数可以删去列表中的最后一个元素。
我们需要同时同刷新率运行addnode和delnode方法。这时,又需要创建一个方法move
.在运行addnode增加蛇块的同时运行delnode减少蛇块。
1 | # 移动! |
那么,除非吃到食物(后面会设置),我们以类创建出来的贪吃蛇的蛇块增加速度等于删除速度,最后蛇的长度不变。又因为蛇块总是在动态变化,我们就用程序完成了蛇的移动。
效果图
改变方向 但是左右、上下不能被逆向改变
但这样是不够的。在经典的贪吃蛇游戏中,蛇是不能逆向运动的。我们需要在蛇左右或上下运动的同时对其运动方向做出限制。
1 | # 改变方向 但是左右、上下不能被逆向改变 |
我的方案是将左右运动和上下运动分开讨论,因为两者的情况是不一样。
在左右运动时,无论是按←还是→键,蛇总是按照原来的方向前进。左右运动时情况类似。
#21 我们分别创建LR和UD列表代表左右运动和上下运动。
#20 这时我们需要输入蛇当前的运动方向,所以在定义方法时在self参数后加了curkey参数。
#23 我们需要判定curkey是否与self.dirction都处于向左或向右运动方向上,如果为真,则返回self.dirction。后面的语句就不会执行了。
#25 若curkey与当前的self.dirction不冲突,则将curkey的值赋予self.dirction,这样贪吃蛇的方向就发生了改变。
贪吃蛇的死亡判断
经典的贪吃蛇中有两种死亡方式,一是碰撞到墙壁(在这里我们用屏幕边界代替),而是头碰到自己的身体。这两者只要满足一个就可以判定为死亡。
这里先做一个简单的死亡判断函数,对死亡前后的各种对象的设置将会在main
函数中进行。
1 | # 死亡判断 |
一开始,我们需要在主函数中初始化isdead方法为否,即
isdead = False
self.body
列表中存储着一些代表蛇块的矩形对象。当任何一个矩形对象超出屏幕范围内时,则返回isdead为真;或者,当代表蛇头的矩形对象(在self.body
列表中总是第一个元素)的位置(left和top参数)包含于蛇身(self.body[1:]
)时,则返回isdead为真。
当两个条件都不满足时,返回isdead = False
用类创建一个“食物”模板
食物所具有的特征就贪吃蛇简单多了,我们需要创建关于食物的类,满足下列的要求:
1.食物出现的位置随机
2.食物被贪吃蛇碰到后会更换位置出现在界面上
3.食物的大小适中(在这里我们固定食物的大小为25*25像素,等于一个蛇块的大小)
创建一个食物的类
初始化
1 | class Food: |
我们需要一个刷新食物出现地点的判定标准,当类Food
的实例被创建时,python会自动调用__init__
函数,这时就应该自动刷新食物出现的地点。当食物被贪吃蛇吃掉时,也应该自动刷新食物出现的地点。
在这里我选择self.rect.left == 0
为真,作为食物刷新的条件。所以在__init__
方法中,我将0赋给了self.rect.left
,并设定了食物的大小为25*25分辨率。
在remove方法中,我选择直接赋值,因为食物大小已经设置过了,不需要再次设置。
之后,很容易想到可以用random函数随机分配食物的位置。
这时可以选择先构建一个包含所有位置的库(为了降低游戏难度,删除了外围25分辨率),在用choice函数在位置库中随机选取。也可以直接在random函数中选构建所有位置的表达式后一步解决。这里选择较简单的前一种。
#26 用for循环将除外围25分辨率的所有位置加入空列表allpos,再用random.choice()方法随机返回。choice() 方法返回一个列表,元组或字符串的随机项。
1 | import random |
注意:choice()是不能直接访问的,需要导入 random 模块,然后通过 random 静态对象调用该方法。
seq – 可以是一个列表,元组或字符串。
实例化类Snack和类Food
从该分隔线以下代码均在main
函数内
从该分隔线以下代码均在main
函数内
从该分隔线以下代码均在main
函数内
实例化类的语法为instancename = ClassName(parameter1, parameter2, ...)
在python中一般用全小写字母表示实例,与类做出区分。
1 | snack = Snack() |
在pygame中,设定游戏开始有相对固定的一套代码
1 | while True: |
pygame提供了现成的方法检测玩家按下输入关键键位
pygame.KEYDOWN 按下键盘时所按下的键
pygame.KEYUP 释放键盘时
event.key 指的是玩家按下的关键键位
构建蛇身体
1 | if not isdead: # 如果蛇没有死,那么蛇会一直移动 |
如果isdead参数为否,则执行snake实例中的move方法。
#27 rect表示矩形对象, 对snake.body中的每一个矩形对象,用pygame.draw.rect(screen, RGB,shape)
绘制矩形(贪吃蛇就是由矩形构成)。 screen表示屏幕界面,RGB用三元元组表示,rect为固定参数。
加入食物与蛇的互动
1 | # 食物处理 / 吃到+50分 |
计算分数并在屏幕上打印文字
添加语句创建scores
变量并赋初值为0
1 | def main(): |
构建在屏幕上打印字体的函数
1 | def show_text(screen, pos, text, color, font_bold = False, font_size = 60, font_italic = False): |
代码中方法均为pygame库内置。
用函数在屏幕上显示分数
1 | # 显示分数文字 |
显示死亡文字
1 | # 显示死亡文字 |
运行和调试
最后来一个main函数就可以运行游戏了!
1 | main() |
项目源代码地址
github.com/ZhangChunXian/learn-python-by-projects/tree/master/项目源代码
最终效果:
写在最后
感谢各位的阅读!你的阅读是我更新的动力源泉。请根据博文质量点击相应的星级。
这是我第一次做python方面的项目。在尚无基础的情况下上手,我翻阅了一些书籍,还有不少大牛的博客,最终写下了这篇博文。有许多不足,望诸位谅解并指出在评论区,也可以通过邮件告诉我,我将逐一听取。感谢各位在我成长过程中给予的帮助。
同时也欢迎各位计算机学习者前来交流。