Python Pygame贪吃蛇游戏的实现详解(上)

我们在前面介绍了函数的概念以及函数的作用。程序最终要以通过函数来定义所要执行的功能,并且通过函数调用来完成和实现这些功能。前面介绍了贪吃蛇这款游戏需要定义的函数,这些函数是游戏程序的核心代码,接下来,我们依次来看看这些函数是如何实现的。

main() 函数

main() 函数是程序执行的入口。先来看一下 main() 函数的详细代码。
def main():           #入口函数,程序从这里开始运行
    pygame.init()     # 模块初始化  
    screen = pygame.display.set_mode((windowsWidth, windowsHeight))
    pygame.display.set_caption("贪吃蛇")
    screen.fill(WHITE)   
    snakeSpeedClock = pygame.time.Clock()
    startGame(screen)      #游戏开始
    while True:
        music=pygame.mixer.Sound("snake.wav")
        music.play(-1)
        runGame(screen, snakeSpeedClock)
        music.stop()
        gameOver(screen)   #游戏结束

首先,初始化 Pygame,调用 pygame.init() 函数进行模块初始化。然后调用了 pygame.display.set_mode() 函数,创建了一个宽 800 像素、高 600 像素的显示窗口,返回了用于该窗口的 pygame.Surface 对象并将其存储在名为 screen 的变量中。

接下来,调用 pygame.display.set_caption() 函数来设置窗口的标题,这里给游戏窗口起名为“贪吃蛇”。调用 screen.fill() 函数,将窗口用白色填充。然后创建了一个时钟(Clock)对象,将其赋值给 snakeSpeedClock 变量,用它来控制帧速率。

然后,调用 startGame 这个自定义函数,这是负责启动游戏的函数,给它传递的参数是变量 screen,后面会详细介绍这个函数。

接下来,为了保持 Pygame 事件循环一直运行,我们使用 while 循环,并且循环条件就是布尔值 True,这表示循环会一直进行,而退出这个循环的唯一方式是程序终止。在前面的示例中,我们通常都会使用一个变量作为循环条件,当程序退出时,修改这个变量来结束循环。这两种做法的效果是相同的,相比之下,这里的用法要更加简单一些。

在循环体中,我们先初始化混合器,然后将一段背景音乐 snake.wav 加载到一个 Sound 对象中,并且将其存储到变量 music 中。接下来调用 play() 函数播放音乐,参数 −1 表示会一直循环播放。

通过上述代码,我们就为游戏添加了背景音乐。然后调用 runGame 函数,传递给它的参数是变量 screen 和 snakeSpeedClock,这个函数负责游戏运行,后面还会详细介绍这个函数。

当这个函数执行完后,就会调用 music.stop() 函数来停止背景音乐播放。然后,调用 gameOver 函数结束游戏,传递给它的参数是变量 screen。

startGame() 函数

这个函数负责控制我们的程序启动,它接收的参数是窗口的 pygame.Surface 对象。我们来看一下该函数的代码。
def startGame(screen):
    gameStart = pygame.image.load("gameStart.png") 
    screen.blit(gameStart, (70, 30)) 
    font = pygame.font.SysFont("SimHei", 40)
    tip = font.render("按任意键开始游戏", True, (65, 105, 225))  
    screen.blit(tip, (240, 550))
    pygame.display.update()
    while True:                                     #键盘监听事件
        for event in pygame.event.get():            #关闭窗口
            if event.type == pygame.QUIT:
                terminate()   
            elif event.type == pygame.KEYDOWN:
                if (event.key == pygame.K_ESCAPE):  #按下ESC键
                    terminate()
                else:
                    return
首先,调用 image() 函数在 Pygame 窗口中加载“gameStart.png”图片,并创建一个名为 gameStart 的 Surface 对象。然后,调用 blit() 函数,将像素从一个 Surface 复制到另一个 Surface 之上。就是把 gameStart 对象复制到 Screen 这个 Surface 上。通过 blit() 将 gameStart 复制到指定位置(70, 30)。

然后,调用 pygame.font.SysFont 函数来创建 Font 对象,并将其赋值给名为 font 的变量,这个对象允许我们以 40 点的 SimHei 字体绘制到 Pygame 的 Surface 上。

接下来,在所创建的 font 对象上使用 render() 命令,把字符串“按任意键开始游戏”绘制到 Surface 上。然后,调用 blit() 函数,将像素从一个 Surface 复制到另一个 Surface 之上。screen.blit(tip, (240, 550)) 负责把 tip 对象复制到 screen 这个 Surface 上的指定位置。

调用pygame.display.update()函数,把绘制到Surface对象上的所有内容都显示到窗口上。

接下来,为了保持 Pygame 事件循环一直运行,我们使用while循环。在事件循环中,判断事件类型如果是QUIT(关闭窗口),就调用 terminate() 函数终止程序,我们稍后会介绍 terminate() 函数。否则,如果事件类型是 KEYDOWN,那么事件对象将有一个 key 属性来识别按下的是哪个键。

如果 key 属性等于 K_ESCAPE,表示用户按下的是 Esc 键,意味着玩家希望结束程序,那么程序的处理方式和点击关闭窗口一样,调用 terminate() 函数终止程序。否则,表示用户按下的是其他键,退出这个函数,表示游戏开始运行。

当这个函数执行后,会出现如图 1 所示的游戏界面,这个时候,玩家可以按 Esc 键关闭程序,如果按其他的任意键则会开始玩游戏。


图 1

runGame() 函数

这个函数控制着游戏程序运行,它接受的参数是窗口的 pygame.Surface 对象和 Pygame 的时钟对象。

runGame() 函数的代码如下所示:
def runGame(screen,snakeSpeedClock):    
    startX = random.randint(3, mapWidth - 8)
    startY = random.randint(3, mapHeight - 8)
    snakeCoords = [{"x": startX, "y": startY}, 
                   {"x": startX - 1, "y": startY},
                   {"x": startX - 2, "y": startY}]

    direction = RIGHT      

    food = {"x": random.randint(0, mapWidth - 1), "y": random.randint(0, mapHeight - 1)}   

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                terminate()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT and direction != RIGHT:
                    direction = LEFT                   
                elif event.key == pygame.K_RIGHT  and direction != LEFT:
                    direction = RIGHT                   
                elif event.key == pygame.K_UP and direction != DOWN:
                    direction = UP
                elif event.key == pygame.K_DOWN and direction != UP:
                    direction = DOWN     
                elif event.key == pygame.K_ESCAPE:
                    terminate()
        moveSnake(direction, snakeCoords)  #移动贪吃蛇
        isEattingFood(snakeCoords, food)   #判断贪吃蛇是否吃到食物
        ret = isAlive(snakeCoords)         #判断贪吃蛇是否还活着
        if not ret:
            break                          #贪吃蛇已经死了,游戏结束
        gameRun = pygame.image.load("background.png")
        screen.blit(gameRun, (0, 0))
        drawFood(screen, food)
        drawSnake(screen, snakeCoords)
        drawScore(screen, len(snakeCoords) - 3)
        pygame.display.update()
        snakeSpeedClock.tick(snakeSpeed)  #控制帧速率

首先使用 random.randint() 函数在 3 到 mapWidth−8 之间选取一个随机整数赋值给变量 startX,在 3 到 mapHeight−8 之间选取一个随机整数赋值给变量 startY,这两个变量分别表示贪吃蛇初始的 x 坐标和 y 坐标。选取随机数的目的是让贪吃蛇出现的位置不是固定的,这样就增加了游戏的不确定性。

然后,用嵌套字典的一个列表来表示贪吃蛇。每个字典表示地图上的一个坐标,{'x': startX, 'y': startY} 表示蛇头的位置,{'x': startX - 1, 'y': startY} 和 {'x': startX - 2, 'y': startY} 表示蛇的身体。这是一条水平放置的蛇,蛇头靠右,有两节蛇身。

将 direction 设置为 RIGHT,表示方向向右。RIGHT 是我们前面定义过的方向变量。

然后使用 random.randint() 函数在 0 到 mapWidth−1 之间选取一个随机整数作为字典中 x 键的值,在 0 到 mapHeight−1 之间选取一个随机整数作为字典中 y 键的值,将字典赋值给变量 food。用这个变量表示食物的坐标位置。

接下来,为了保持 Pygame 事件循环一直运行,我们使用 while 循环。在事件循环中,判断事件类型,如果是 QUIT,那么调用 terminate() 函数终止程序。否则,如果事件类型是 KEYDOWN,那么事件对象将有一个 key 属性来识别按下的是哪个键。

如果 key 属性等于 K_LEFT,表示用户按下的是向左方向键,并且 direction != RIGHT,那么将变量 direction 设置为 LEFT,也就是将方向设置为向左。这里,direction != RIGHT 的含义是蛇头不向右,因为蛇头向右的话,是没有办法再将方向设置为向左的(因为要避免贪吃蛇直接掉头导致头部和身体相碰撞的情况),只有蛇头向上、向下或向左的时候,我们才可以将蛇头方向设置为向左。

如果 key 属性等于 K_RIGHT,并且 direction != LEFT,那么将变量 direction 设置为 RIGHT;如果 key 属性等于 K_UP,并且 direction != DOWN,那么将变量 direction 设置为 UP;如果 key 属性等于 K_DOWN,并且 direction !=UP,那么将变量 direction 设置为 DOWN;

如果 key 属性等于 K_ESCAPE,表示用户按下的是 Esc  键,意味着玩家希望结束程序,那么处理方式和玩家点击关闭窗口是一样的,调用 terminate() 函数终止程序。

然后,调用 moveSnake() 函数移动贪吃蛇,该函数的参数是变量 direction 和 snakeCoords,稍后我们还会详细介绍 moveSnake() 函数。
 
接下来调用 isEattingFood() 函数,判断贪吃蛇是否吃到食物,该函数参数是变量 snakeCoords 和 food,稍后我们还会详细介绍 isEattingFood() 函数。

然后调用 isAlive() 函数,判断贪吃蛇是否死亡,该函数的参数是变量 snakeCoords,并且将它的返回结果赋值给变量 ret,稍后会详细介绍 isAlive() 函数。判断变量 ret 是否是 True,如果不是,跳出 while 循环,表示贪吃蛇已经死了,游戏结束。

调用 image.load() 函数加载游戏背景图片,并创建一个名为 gameRun 的 Surface 对象。调用 blit() 函数,把 gameRun 对象复制到 screen 这个 Surface 上,指定位置是左上角。

然后调用 drawFood() 函数绘制食物,该函数的参数是变量 screen 和 food,稍后我们还会介绍 drawFood() 函数。

然后调用 drawSnake() 函数绘制贪吃蛇,该函数的参数是变量 screen 和 snakeCoords,稍后我们还会详细介绍 drawFood() 函数。

接下来调用 drawScore() 函数绘制分数,该函数的参数是变量 screen 和 len(snakeCoords) – 3 的结果。len(snakeCoords) 表示贪吃蛇的长度。减去 3,是因为贪吃蛇最初有一个蛇头和两节身体,也就是 snakeCoords 初始有 3 个元素,减去 3 就是新增的身体部分,也就是相应的得分。

调用 pygame.display.update() 函数,把绘制到 Surface 对象上的所有内容,都显示到窗口上。调用时钟对象的 tick() 方法,表示游戏运行的帧速率是 snakeSpeed FPS,即每秒 snakeSpeed 次。

当 runGame() 函数执行后,会出现如图 2 所示的游戏界面。

P ython runGame() 函数
图 2