使用pygame制作贪吃蛇游戏

1. 基本游戏画面

首先,我们需要制作出一个基本的游戏画面,这个过程中,我们将学习pygame的一些基本用法

from pygame.locals import *
import pygame
import time


class SnakeGame():
    def __init__(self):
        self.width = 800
        self.height = 600
        self._running = False

    def init(self):
        pygame.init()  #初始化所有导入的pygame模块
        # 初始化一个准备显示的窗口或屏幕
        self._display_surf = pygame.display.set_mode((self.width,self.height), pygame.HWSURFACE)
        self._running = True

    def run(self):
        self.init()
        while self._running:
            time.sleep(0.05)


if __name__ == '__main__':
    snake = SnakeGame()
    snake.run()

pygame.init()的目的是为了初始化所有导入的pygame模块,它会检查你电脑上的一些硬件驱动,比如声卡之类的。

游戏画面使用pygame.display.set_mode来初始化,这是一个必要的准备。通过属性_running来标识游戏是否仍在运行,这样,使用while循环时,就可以在特定条件下停下来。

为什么要写一个while循环呢?游戏是一个程序,它一直在运行,而且要不停的监听我们对游戏的操作,只有循环的程序结构才能满足这样的要求。

现在,我们已经有了一个基本游戏界面了,下一步,我们学习如何在界面上画出贪吃蛇所要吃的食物

2. 制作食物

贪吃蛇要迟到食物才能长大,界面上的食物,本质上是一张图片,我们通过技术手段将其画到界面上,下图是我准备的食物图片--food.png
python 贪吃蛇
开发游戏程序,建议你使用面向对象编程,这样会大大的减轻我们程序设计的难度,现在,我要设计一个食物类

STEP = 44

class Food():
    def __init__(self, x, y, surface):
        self.x = x*STEP
        self.y = y*STEP
        self.surface = surface
        self.image = pygame.image.load("food.png").convert()

    def draw(self):
        self.surface.blit(self.image,(self.x, self.y))

Food类在创建对象时,需要提供x, y 坐标,这样就知道将其放在游戏画面的哪个位置上,在初始化时,同时将图片导入, surface需要传入SnakeGame对象的_display_surf属性。

draw方法负责将食物图片绘制在游戏画面上。

3. 在界面上显示食物

3.1 创建食物对象

接下来,要将Food类和Snake类结合在一起,首先修改on_init方法, 在方法默认增加一行代码

self.food = Food(5, 5, self._display_surf)

创建一个食物对象,让其成为SnakeGame的一个属性

3.2 渲染

增加一个渲染的方法

def render(self):
    self._display_surf.fill((0,0,0))    # 游戏界面填充为黑色
    self.food.draw()        # 画出食物
    pygame.display.flip()   # 刷新屏幕

这段渲染游戏的代码是极为关键的,游戏里的事物之所以是动态变化的,全靠反复的绘制刷新。

3.3 修改run方法

你需要将render方法放在run方法里调用,这样才能不停的刷新游戏界面

def run(self):
    self.init()
    while self._running:
        pygame.event.pump()    # 内部处理pygame事件处理程序
        self.render()
        time.sleep(0.5)

启动程序,游戏画面变成了这个样子
python 贪吃蛇

全部代码如下

from pygame.locals import *
import pygame
import time

STEP = 44

class Food():
    def __init__(self, x, y, surface):
        self.x = x*STEP
        self.y = y*STEP
        self.surface = surface
        self.image = pygame.image.load("food.png").convert()

    def draw(self):
        self.surface.blit(self.image, (self.x, self.y))


class SnakeGame():
    def __init__(self):
        self.width = 800
        self.height = 600
        self._running = False

    def init(self):
        pygame.init()  #初始化所有导入的pygame模块
        # 初始化一个准备显示的窗口或屏幕
        self._display_surf = pygame.display.set_mode((self.width,self.height), pygame.HWSURFACE)
        self._running = True
        self.food = Food(5, 5, self._display_surf)

    def run(self):
        self.init()
        while self._running:
            pygame.event.pump()    # 内部处理pygame事件处理程序
            self.render()
            time.sleep(0.05)

    def render(self):
        self._display_surf.fill((0, 0, 0))    # 游戏界面填充为黑色
        self.food.draw()        # 画出食物
        pygame.display.flip()   # 刷新屏幕


if __name__ == '__main__':
    snake = SnakeGame()
    snake.run()

4. 制作贪吃蛇

4.1 定义贪吃蛇类

贪吃蛇是整个游戏的难点所在,我们先对他进行分析

  1. 贪吃蛇的长度是变化的
  2. 贪吃蛇前进是有方向的
  3. 贪吃蛇由多个小图片构成
  4. 贪吃蛇可以改变前进方向

贪吃蛇的每个节点使用snake.png
python 贪吃蛇

class Snake():
    def __init__(self, x, y, surface):
        self.x = [x*STEP]
        self.y = [y*STEP]      # 用两个列表来存储贪吃蛇每个节点的位置
        self.length = 1        # 贪吃蛇的长度
        self.direction = 0     # 0表示向右, 1表示向下, 2表示向左, 3表示向上
        self.image = pygame.image.load("snake.png").convert()       # 加载蛇
        self.surface = surface
        self.step = 44         # 运动步长
        self.updateCount = 0     # 更新次数

        # 虽然有这么多节点,但是有length来控制界面上画出多少蛇的节点
        for i in range(1, 100):
            self.x.append(-100)
            self.y.append(-100)

    def draw(self):
        for i in range(self.length):
            self.surface.blit(self.image, (self.x[i],self.y[i]))

由于蛇是由多个节点构成,因此,我使用两个列表来保存每个节点的坐标位置, direction表示蛇前进的方向。

4.2 在界面上显示蛇

在界面上显示蛇的方法,可以参考显示食物的过程,修改两个函数即可

1 初始游戏函数

def init(self):
    pygame.init()  #初始化所有导入的pygame模块
    # 初始化一个准备显示的窗口或屏幕
    self._display_surf = pygame.display.set_mode((self.width,self.height), pygame.HWSURFACE)
    self._running = True
    self.food = Food(5, 5, self._display_surf)
    self.snake = Snake(1, 1, self._display_surf)

2 渲染函数

def render(self):
    self._display_surf.fill((0, 0, 0))    # 游戏界面填充为黑色
    self.food.draw()        # 画出食物
    self.snake.draw()       # 画出蛇
    pygame.display.flip()   # 刷新屏幕

python 贪吃蛇

4.3 让蛇动起来

为了让蛇动起来,我们需要实现一个方法,根据蛇的运动方向和单次运动的距离计算出蛇每个节点的最新的位置,在Snake类里新增update方法

def update(self):
    self.updateCount += 1
    if self.updateCount > 2:
        for i in range(self.length-1, 0, -1):
            self.x[i] = self.x[i-1]
            self.y[i] = self.y[i-1]

        if self.direction == 0:
            self.x[0] = self.x[0] + self.step    # 向右
        if self.direction == 1:
            self.y[0] = self.y[0] + self.step    # 向下
        if self.direction == 2:
            self.x[0] = self.x[0] - self.step    # 向左
        if self.direction == 3:
            self.y[0] = self.y[0] - self.step    # 向上
            
        self.updateCount = 0

蛇在运动时,后面的节点运动到前一个节点所在的位置,因此他们的位置不需要计算,只有蛇的头部节点需要根据方向和运动步长进行计算。

这里有必要解释一下,为什么要判断self.count是否大于2,在SnameGame的run方法里,while循环中每次会sleep0.05秒,这就意味着每0.05秒蛇就要发生一次运动,这样的速度太快了,因此每两个0.05秒才运动一次,为什么不修改sleep的时间呢,因为还要在while循环里监听键盘,如果sleep的时间过长,监听事件就不灵敏了。

在SnakeGame里新增loop方法,来调用上面的update方法

def run(self):
    self.init()
    while self._running:
        pygame.event.pump()    # 内部处理pygame事件处理程序
        self.loop()
        self.render()
        time.sleep(0.05)

def loop(self):
    self.snake.update()

python 贪吃蛇
现在蛇已经可以移动了,但是它的运动不受控制,而且遇到墙壁以后,游戏应该结束,对于这两个问题,我们逐个解决。

5. 游戏控制

5.1 用键盘改变贪吃蛇运动方向

首先要在Snake里增加改变方向的方法

def moveRight(self):
    self.direction = 0

def moveLeft(self):
    self.direction = 2

def moveUp(self):
    self.direction = 3

def moveDown(self):
    self.direction = 1

在SnakeGame增加一个监听键盘的方法

def run(self):
    self.init()
    while self._running:
        pygame.event.pump()     # 内部处理pygame事件处理程序
        self.listen_keybord()   # 监听键盘上下左右键
        self.loop()
        self.render()
        time.sleep(0.05)

def listen_keybord(self):
    keys = pygame.key.get_pressed()

    if (keys[K_RIGHT]):
        self.snake.moveRight()

    if (keys[K_LEFT]):
        self.snake.moveLeft()

    if (keys[K_UP]):
        self.snake.moveUp()

    if (keys[K_DOWN]):
        self.snake.moveDown()

    if (keys[K_ESCAPE]):
        self._running = False

根据键盘上下左右键来改变蛇的运动方向

5.2 检测碰撞

当贪吃蛇的头碰到食物时,要吃掉食物,吃掉食物的动作很好做,只要修改食物的位置即可,坐标可以随机生成。怎么算是碰到食物呢,需要在SnakeGame中编写一个方法来检测是否发生碰撞

@staticmethod
def isCollision(x1, y1, x2, y2, bsize):
if x1 >= x2 and x1 <= x2 + bsize:
    if y1 >= y2 and y1 <= y2 + bsize:
        return True
return False

5.3 吃掉食物

吃掉食物后,食物随机出现在其他位置上,同时蛇的长度要增加,在SnakeGame里增加一个吃掉食物的方法eat

def loop(self):
    self.snake.update()
    self.eat()

def eat(self):
    if self.isCollision(self.food.x, self.food.y, self.snake.x[0], self.snake.y[0], 40):
        self.food.x = random.randint(2, 9)*STEP
        self.food.y = random.randint(2, 9)*STEP
        self.snake.length += 1      # 蛇的长度加1

5.4 边界检测与吃掉自己

游戏界面是有范围的,如果蛇出了范围,游戏就结束了,同样,蛇的头部也不应当碰到自己,因此需要检查这两种情况

def loop(self):
    self.snake.update()
    self.eat()
    self.faild_check()

def faild_check(self):
    # 检查是否吃到了自己
    for i in range(2,self.snake.length):
        if self.isCollision(self.snake.x[0], self.snake.y[0], self.snake.x[i], self.snake.y[i],40):
            print('吃到自己了')
            exit(0)

    if self.snake.x[0] < 0 or self.snake.x[0] > self.width \
            or self.snake.y[0] < 0 or self.snake.y[0] > self.height:
        print('出边界了')
        exit(0)

6. 获取源码

扫描关注微信公众号,回复 贪吃蛇 即可获得所有源码
酷python

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案