python游戏开发库pygame。pygame实现贪吃蛇 电脑版发表于:2022/7/25 14:06 ![](https://img.tnblog.net/arcimg/aojiancc2/759d4cb4171947239bcb660ab337c3fa.png) [TOC] ### pygame介绍 Pygame 是一个专门用来开发游戏的 Python 模块,主要为开发、设计 2D 电子游戏而生,它是一个免费、开源的第三方软件包,支持多种操作系统,具有良好的跨平台性(比如 Windows、Linux、Mac 等)。Pygame 是 Pete Shinners 在 SDL(Simple DirectMedia Layer,一套开源的跨平台多媒体开发库)基础上开发而来,其目的是取代 PySDL。 tn2> 截止到 2020 年 10 月 28 日,Pygame 已经诞生 20 周年。 SDL 是一套开放源代码的跨平台多媒体开发库,使用 C语言编写,它提供了多种控制图像、声音、输入/输出的函数,Pygame 可以看做是对 SDL 的封装,在 SDL 库基础上提供了各种 Python 的 API接口。目前 SDL 主要用于多媒体领域,比如开发游戏、模拟器、媒体播放器等。 通过 Pygame 我们能够创建各种各样的游戏和多媒体程序,但相比于开发大型 3D 游戏来说,它更擅长与开发 2D 游戏,比如扫雷、纸牌游戏、贪吃蛇、超级马里奥、飞机大战等,如果是 3D 游戏,可以选择一些功能更为全面的 Python 游戏开发库,比如 Panda3D(迪士尼开发的3D游戏引擎),PyOgre(Ogre 3D渲染引擎)等。 Python 作为一门解释型语言并不适合开发大型的 3D 游戏,但 Python 通过对其他语言的接口封装,使自身具备了开发大型 3D 游戏的能力,例如 Panda3D 的底层是用 C++ 语言编写的。一些较为知名的 3D 游戏,比如魔兽世界、文明帝国4、战地风云2,这些游戏都是使用 Python 语言开发的,而国内较为知名的“阴阳师”手游,也是由 Python 语言开发而成。 Pygame 官方网站(https://www.pygame.org/tags/all)提供许多丰富的游戏案例,它们全部使用 Pygame 开发 ### pygame安装 ``` pip install pygame ``` 安装完成后使用命令查看版本 ``` python -m pygame --version ``` 或者,输入:python -m pygame.examples.aliens,若能弹出游戏页面,则安装成功 python的常用对象 ``` pygame.display 显示 pygame.time 时间 pygame.event 事件 pygame.draw 绘制 ``` ## pygame实现贪吃蛇 ### 一:能打开游戏窗体,点击x退出 ``` ## 引入所需要的模块 import sys import pygame ## 使用pygame之前必须初始化 pygame.init() ## 定义一个宽高 width = 800 height = 600 size = (width,height) # 设置主屏窗口大小 screen = pygame.display.set_mode(size) # 设置窗口的标题,即游戏名称 pygame.display.set_caption('hello world') # 固定代码段,实现点击"X"号退出界面的功能,几乎所有的pygame都会使用该段代码 while True: # 循环获取事件,监听事件状态 for event in pygame.event.get(): # 判断用户是否点了"X"关闭按钮,并执行if代码段 if event.type == pygame.QUIT: #卸载所有模块 pygame.quit() #终止程序,确保退出程序 sys.exit() pygame.display.flip() #更新屏幕内容 ``` 默认就是一个黑色的窗体: ![](https://img.tnblog.net/arcimg/aojiancc2/15747ad0f014427b9f691ffc9bd4a719.png) ### 二:可以设置一下帧率,降低一下循环刷新的频率 不设置的话它是没有间隔的一直刷新 ``` ## 获取pygame的Clock对象 clock = pygame.time.Clock() while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() pygame.display.flip() ## 设置一下帧率 clock.tick(60) ``` ### 三:绘制背景,让窗体变成白色背景 tn2>绘制背景,让窗体变成白色背景。其实就是画一个矩形覆盖整个窗体即可。其实就一句话 pygame.draw.rect(screen,(255,255,255),(0,0,width,height)) - 参数1:需要绘制到的窗体对象 - 参数2:绘制的颜色 - 参数3:矩形的坐标,左上坐标-长-宽 **主要代码如下:** ``` # 设置主屏窗口大小 screen = pygame.display.set_mode(size) while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() ## 绘制背景,让窗体变成白色背景。其实就是画一个矩形覆盖整个窗体即可 pygame.draw.rect(screen,(255,255,255),(0,0,width,height)) pygame.display.flip() clock.tick(60) ``` ### 四:绘制格子,也就是绘制蛇身,食物等 tn2>贪吃蛇的地图很简单,我们可以看成一个一个的格子,比如蛇身是一个一个的格子,食物也是一个一个的格子,障碍物也可以是一个一个的格子。格子的多少也就是地图可以操控空间有多少。 绘制一个任意大小的格子,其实也是使用draw.rect方法比如绘制一个正方形格子 ``` ## 绘制一个起始坐标点为0,0,宽度为50的正方形 pygame.draw.rect(screen,(226, 163, 29),(0,0,50,50)) ``` ![](https://img.tnblog.net/arcimg/aojiancc2/1fd315ad2b9a41b1869485ae89c76ae8.png) 如果想要长一点,修改一下长宽即可,如下 ``` ## 绘制一个起始坐标点为0,0,宽度为130,高度为30的矩形 pygame.draw.rect(screen,(226, 163, 29),(0,0,130,30)) ``` 颜色选取的话,可以参考这个网站 https://htmlcolorcodes.com/zh/yanse-xuanze-qi/ #### 上面我们是直接写死位置以及写死高宽的,实际情况下,这些都应该动态计算得来。我们先动态的画出来定义任意位置的格子 比如我定义地图的宽度是600*400,最开始也是定义了的 ``` ## 定义地图的宽度是600*400 width = 600 height = 400 ``` 我们定义地图的行列数是10*15,注意为了保证我们我们每个格子是一个正方形,我们行列数要按照同一个比例来。当然如果你的游戏格子不是正方形可以不管这个。 ``` ## 定义地图的行列数是10*15 row = 10 col = 15 ``` 比如我们蛇头的位置是在5行7列,或者修改成其他任意合理的参数,怎么根据这些变量画出来蛇头的位置呢 ``` ## 定义蛇头的位置 snake_head_row = 5 snake_head_col = 7 ``` 其实很简单,首先蛇头所在格子的宽高,就是等于每个格子的宽度,我们先计算出来 ``` ## 格子的高度 = 地图的高度/行数 boxheight = height/row ## 格子的宽度 = 地图的高度/列数 boxwidth = width/col ``` 然后宽高有了,就是起始的位置坐标了,也很简单。左边的距离就是列等于列宽*所在列,距离上边的位置=行高*所在行。然后画出来即可 ``` ## 格子的高度 = 地图的高度/行数 boxheight = height/row ## 格子的宽度 = 地图的高度/列数 boxwidth = width/col ## 定义蛇头的位置 snake_head_row = 5 snake_head_col = 7 ## 距离左边的位置=列宽*所在列 snake_head_left = snake_head_col*boxwidth ## 距离上边的位置=行高*所在行 snake_head_top = snake_head_row * boxheight ## 根据定义的行列位置画出来格子所在的位置 pygame.draw.rect(screen,(226, 163, 29),(snake_head_left,snake_head_top,boxwidth,boxheight)) ``` 蛇头位置定义在第5行,第7列的效果如下: ![](https://img.tnblog.net/arcimg/aojiancc2/ffc7162bf60c48518a5737e6300a9ed0.png) 蛇头位置定义在5行,0列的位置如下: ![](https://img.tnblog.net/arcimg/aojiancc2/28cc29080bdb4868a2d366666fe5b4b0.png) 完全没有问题,现在我们就可以动态的根据行列来了。 #### 封装一个类表示格子的位置 上面格子的位置,也就是所在的行列,很多元素都需要使用到它,为了重复使用,也为了方便管理,最好封装一下,比如封装成一个类,更方便操作一点。很简单的一个类,就一个行列属性,然后一个初始方法用于赋值。 ``` ## 定义一个类表示格式的位置 class Point: row = 0 col = 0 def __init__(self,row,col) : self.row = row self.col = col ``` 然后蛇头的位置我们就可以这样定义,比如定义一个大概中间的位置 ``` ## 定义蛇头的坐标位置 head = Point(int(row/2),int(col/2)) ``` 计算位置的时候,就可以使用对象的方式 ``` ## 距离左边的位置=列宽*所在列 snake_head_left = head.col*boxwidth ## 距离上边的位置=行高*所在行 snake_head_top = head.row * boxheight ## 根据定义的行列位置画出来格子所在的位置 pygame.draw.rect(screen,(226, 163, 29),(snake_head_left,snake_head_top,boxwidth,boxheight)) ``` #### 封装一个画格子的方法 因为画格子也是一个需要重复使用的方法,所以我们需要封装一下。方法也很简单就是把前面写的计算格子宽高,根据格子行列计算距离左边与上边的距离等提到一个方法中。其实计算格子宽高的可以单独提出来写,我们暂时把这个放到这里吧。 tn2>方法有两个参数一个是坐标也就是格子所在的行列,还有一个是颜色 ``` def drawRect(point,color): ## 格子的高度 = 地图的高度/行数 boxheight = height/row ## 格子的宽度 = 地图的高度/列数 boxwidth = width/col ## 距离左边的位置=列宽*所在列 box_head_left = point.col*boxwidth ## 距离上边的位置=行高*所在行 box_head_top = point.row * boxheight ## 根据定义的行列位置画出来格子所在的位置 pygame.draw.rect(screen,color,(box_head_left,box_head_top,boxwidth,boxheight)) ``` 现在我们要画格子就可以直接调用方法了。(蛇的颜色最好单独定义一个变量,后面要换成的时候随时可以换,这里为了演示没有使用变量) ``` ## 调用封装方法绘制蛇身 drawRect(headPoint,(226, 163, 29)) ``` 现在如果我们要在画一个其他元素,比如食物,就非常简单了,定义一下食物出现的位置和颜色,在调用一次方法就行。核心代码如下: ``` ## 定义食物格子的位置 foodPoint = Point(3,3) ## 定义一个食物格子的颜色 foodColor = (226, 64, 29) ## 调用封装方法绘制食物 drawRect(foodPoint,foodColor) ``` ![](https://img.tnblog.net/arcimg/aojiancc2/05f753c42c4e4682996848862635aa92.png) ### 五:控制蛇的移动 #### 让蛇可以自己移动 要让蛇移动非常简单,比如要让蛇左边这一步其实就是移动一格,就是让蛇头的位置让左边移动一格,也就是让蛇头的对象的列让左边-1就行了。向右移动就是让列+1,同理上下移动就是对行的加减。 先定义一个默认移动的方向,比如向左边吧。 ``` snakeDirect = "left" ``` 然后封装一个蛇移动的方法 ``` ## 封装一个蛇移动的方法 def snakeRun(snakeDirect): ## 向左边移动就是让列减去1即可。其他同理 if snakeDirect == "left": headPoint.col -=1 if snakeDirect == "right": headPoint.col +=1 if snakeDirect == "up": headPoint.row -=1 if snakeDirect == "down": headPoint.row +=1 ``` 然后在主循环里边调用一下即可 ``` while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() ## 让地图是白色背景 pygame.draw.rect(screen,(255,255,255),(0,0,width,height)) ## ----------------调用蛇移动的方法----------------- snakeRun(snakeDirect) ## 调用封装方法绘制蛇身 drawRect(headPoint,snakeColor) ## 调用封装方法绘制食物 drawRect(foodPoint,foodColor) #更新屏幕内容 pygame.display.flip() ## 设置一下帧率 clock.tick(5) ``` 然后我们就可以看到蛇头的移动了。注意帧率哦,上面修改成5做测试了,如果是以前这种的60就会移动得非常快。60对于贪吃蛇这个游戏来吃帧率太高了-。-根本操作不过来。但是帧率的控制如果太低了,看着会很卡 ![](https://img.tnblog.net/arcimg/aojiancc2/c37fbccd9dff4932b718f08ffeee90c6.gif) #### 控制蛇的方向 很简单,监听按键事件,然后根据按键改变移动的方向即可。比如wsad对应上下左右 ``` for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() ## 监听按键事件 if event.type == pygame.KEYDOWN: if event.key == 119: snakeDirect = "up" if event.key == 115: snakeDirect = "down" if event.key == 97: snakeDirect = "left" if event.key == 100: snakeDirect = "right" print(event) print(snakeDirect) ``` 对应的key可以print输出看就行了 ![](https://img.tnblog.net/arcimg/aojiancc2/7e09c59b61bd4e96bea9a8f2c267af1a.png) 效果如下: ![](https://img.tnblog.net/arcimg/aojiancc2/5f53fd6f92fe40e5952c68cffce677ee.gif) ### 六:绘制蛇身 就是让蛇的格子多一点而已,其实也非常简单,可以用一个对象集合来表示蛇身。 ``` ## 定义一个蛇身的集合 snakeBody = [ Point(row=headPoint.row,col=headPoint.col+1), Point(row=headPoint.row,col=headPoint.col+2), Point(row=headPoint.row,col=headPoint.col+3), ] ## 随便定义一个蛇身的颜色 snakeBodyColor = (200,200,200) ``` 然后在主循环里边绘制出来,就是循环调用封装的方法而已。 ``` ## 蛇身绘制出来 for item in snakeBody: drawRect(item,snakeBodyColor) ``` **让蛇身跟着蛇头动。其实也非常简单,蛇身的移动就是两步。** 第1步就是让蛇身向前一步,也就是前面蛇头的位置,代码表示就是向蛇身的集合里边添加一条记录而已 ``` ## 蛇身添加一条记录,也就是向前走一步,向蛇头位置走一步 snakeBody.insert(0,Point(headPoint.row,headPoint.col)) ``` 第2步就是把蛇尾最后一格去掉,直接pop方法搞定 ``` 去掉蛇尾 snakeBody.pop() ``` 效果如下: ![](https://img.tnblog.net/arcimg/aojiancc2/6ac7f1e9e9f64559ad09623ca817a0e8.gif) 当然如果我们不区分蛇头与蛇身可以全部使用一个list表示即可。 ### 七:随机产生食物与吃食物 随机产生食物使用一个随机数就行 ``` import random foodPoint = Point(random.randint(1,row-2),random.randint(1,row-2)) ``` 判断是否吃到了食物,只需要判断蛇头和食物重叠就行,如果重叠了说明吃到了食物。而判断是否重叠只需要判断蛇头的行列是否与食物的行列相同。 ``` ## 蛇头和食物重叠说明吃到了食物 if headPoint.row == foodPoint.row and headPoint.col == foodPoint.col: ## 重新产生食物 foodPoint = Point(random.randint(0,row-1),random.randint(0,row-1)) else: ##没有吃掉食物才去掉蛇尾 snakeBody.pop() ``` ![](https://img.tnblog.net/arcimg/aojiancc2/752a29824b3847cc93fa1c340be33436.gif) ### 八:封装产生食物的方法,防止随机产生的食物与蛇身重叠了 我们产生食物的时候要判断一下,是否和蛇头,蛇身重叠了,如果重叠了要重新产生一次。封装的方式有很多种,下面是我封装的一种。 ``` def createFood(_headPoint,_snakeBody): while True: rfoodPoint = Point(random.randint(1,row-2),random.randint(1,row-2)) isRepater = False ## 判断食物是否与蛇头重叠 if _headPoint.row == rfoodPoint.row and _headPoint.col == rfoodPoint.col: isRepater=True continue ## 判断食物是否与蛇身重叠 for item in _snakeBody: if item.col == rfoodPoint.col and item.row == rfoodPoint.row: isRepater=True continue ## 都没有碰上说明这次产生的食物是正常的可以正常返回 if isRepater == False: return rfoodPoint ``` 然后产生食物的地方都可以使用这个方法了,初始化食物的时候 ``` foodPoint = createFood(headPoint,snakeBody) ``` 吃了食物后的时候: ``` ## 蛇头和食物重叠说明吃到了食物 if headPoint.row == foodPoint.row and headPoint.col == foodPoint.col: ## 重新产生食物 foodPoint = createFood(headPoint,snakeBody) ##foodPoint = Point(random.randint(1,row-2),random.randint(1,row-2)) else: ##没有吃掉食物才去掉蛇尾 snakeBody.pop() ``` ### 九:游戏结束检查,game over 贪吃蛇的游戏结束检查很简单,基本就是两点,一个是撞墙死了,一个是撞自己死了。当然规则自己定嘛,也可以撞自己不用死。判断也是非常简单,如下 ``` ## 游戏是否结束 isdead = False ## 装边界死了 if headPoint.row<0 or headPoint.row>=row or headPoint.col<0 or headPoint.col>=col: print("装边界死了") isdead = True ## 撞自己死了 for body in snakeBody: if headPoint.row == body.row and headPoint.col == body.col: print("撞自己死了") isdead=True ## 最简单的处理,直接退出游戏 if isdead == True: break ## 蛇身添加一条记录,也就是向前走一步,向蛇头位置走一步 snakeBody.insert(0,Point(headPoint.row,headPoint.col)) ## ----------------蛇的移动----------------- snakeRun(snakeDirect) ``` 我们这里处理游戏结束非常简单,直接退出游戏了。应该还是要有点动画效果,至少也有个game over的展示界面撒,还能再次开始什么的。下次再说了。 未完待续....