1. 程式人生 > 實用技巧 >基於 pygame 設計貪吃蛇遊戲

基於 pygame 設計貪吃蛇遊戲

轉載註明連結:https://www.cnblogs.com/liquancai/p/13269428.html

基於 pygame 設計貪吃蛇遊戲

貪吃蛇遊戲通過玩家控制蛇移動,不斷吃到食物增長,直到碰到蛇身或邊界遊戲結束。其執行效果如下所示:

遊戲開始時,先匯入可能需要用到的包。

import time
import random
import pygame
from pygame.locals import *

輸入下面兩行來啟用並初始化 pygame,這樣 pygame 在改程式中就可以使用了。

pygame.init()
fps_clock = pygame.time.Clock()

第 1 行告訴 pygame 初始化,第 2 行建立一個名為 fps_clock 的變數,改變數用來控制重新整理遊戲介面(即 遊戲迴圈執行)的速度。然後用下面的兩行程式碼新建一個 pygame 顯示層(遊戲元素畫布)。第 3~6 行分別定義了遊戲結束畫面顯示的 “ Game Over ”,及其字型、大小、位置等。

screen = pygame.display.set_mode(SCREEN_RECT.size)	# SCREEN_RECT 是遊戲介面的 rect。
pygame.display.set_caption("貪吃蛇")
gameover_font = pygame.font.SysFont('arial', 100)
gameover_text = gameover_font.render("Game Over", True, grey)
gameover_rect = gameover_text.get_rect()
gameover_rect.center = SCREEN_RECT.center

接下來定義一些顏色,雖然這一步不是必須的,但它會減少程式中的程式碼量。下面程式碼定義了程式中用到的顏色。

red = pygame.Color(255, 0, 0)
black = pygame.Color(0, 0, 0)
white = pygame.Color(255, 255, 255)
grey = pygame.Color(150, 150, 150)

下面的幾行程式碼初始化了程式中用到的一些變數,這是很重要的一步,因為如果遊戲開始時,這些變數為空,python 將無法正常執行。

snake_position = [100, 100]			# 蛇頭位置
# 蛇身序列座標,OFFSET 可以看作蛇運動速度,
snake_segments = [[100, 100], [100-OFFSET, 100], [100-2*OFFSET, 100]]	
target = [300, 300]			# 食物位置
flag = 0			# 是否吃到食物,1為是,0為否
direction = 'right'			# 運動方向,初始設定為右
temp_direction = direction 		# 待改變運動方向

由上可以看出,用列表來表示蛇頭、蛇身序列和食物的座標。

至此程式的開頭部分已經完成,接下來進入主要部分。該程式執行在一個無限迴圈(一個永不退出的 while 迴圈)中,直到蛇撞到了牆或者自己才會結束遊戲。

首先用 while True 開始主迴圈。沒有其他的比較條件,python 會檢測 True 是否為真。如果 True 一直為真,迴圈會一直進行,直到滿足遊戲結束條件後退出迴圈。

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            exit()
    keys_pressed = pygame.key.get_pressed()
    if keys_pressed[K_RIGHT]:
        temp_direction = 'right'
    if keys_pressed[K_LEFT]:
        temp_direction = 'left'
    if keys_pressed[K_UP]:
        temp_direction = 'up'
    if keys_pressed[K_DOWN]:
        temp_direction = 'down'

for 迴圈用來檢測 pygame 退出事件。if event.type == QUIT 告訴 python 如果 pygame 發出了 QUIT 資訊(當用戶按下游戲介面右上角的 × 按鈕時),通知 pygame 和 python 程式結束並退出。

keys_pressed = pygame.key.get_pressed() 用來檢測使用者鍵盤按鍵,通過判斷 keys_pressed 中相應按鍵值(1或0),來改變 temp_direction 的值,從而控制蛇的運動方向。

程式開始時,蛇會按照 temp_direction 預設的值向右移動,直到使用者按下鍵盤的方向鍵改變其方向。

在程式開始的初始化部分有一個 direction 變數,這個變數協同 temp_direction 檢測使用者發出的命令是否有效。蛇是不能立即向後運動的(如果發生該情況,蛇會死亡,同時遊戲結束)。為了防止這樣的情況發生,將使用者發出的請求(儲存在 temp_direction 裡)和目前的方向(儲存在 direction 裡)進行比較,如果方向相反,忽略該命令,蛇會繼續按原方向運動。如下程式碼所示:

if ((temp_direction == 'right' and direction != 'left')
   		or (temp_direcion == 'left' and direction != 'right')
   		or (temp_direction == 'up' and direction != 'down')
   		or (temp_direction == 'down' and direction != 'up')):
	direction = temp_direction

這樣就保證了使用者輸入的合法性,蛇(在螢幕上顯示為一系列塊)就能夠按照使用者的輸入移動。每次轉彎時,蛇會向該方向移動一小節。每個小節畫素值為 OFFSET(使用者可以自己設定)。使用者按下按鍵即告訴 pygame 在任何方向移動一小節。程式碼如下:

if direction == 'right':
    snake_position[0] += OFFSET
if direction == 'left':
    snake_position[0] -= OFFSET
if direction == 'up':
    snake_position[1] -= OFFSET
if direction == 'down':
    snake_position[1] += OFFSET

snake_position 為蛇頭的新位置,程式開始處的另一個列表變數 snake_segments 卻不是這樣。該列表儲存蛇身體的位置(頭部後邊),隨著蛇吃掉食物導致長度增加,列表會增加長度同時提高遊戲難度。隨著遊戲的進行,避免蛇頭撞到身體的難度變大。如果蛇頭撞到身體,蛇會死亡,同時遊戲結束。此時用下面的程式碼使蛇的身體增長:

snake_segments.insert(0, list(snake_position))

這裡用 insert() 方法向 snake_segments 列表(存有蛇身的位置)中新增新的專案。每當 python 執行到這一行,它就會將蛇的身體增長一節,同時這一節放在蛇的頭部,在玩家看來蛇在增長。當然,使用者只希望蛇吃到食物時才增長,否則蛇會一直變長。輸入下面的幾行程式碼:

if snake_position[0] == target[0] and snake_position[1] == target[1]:
    flag = 1
else:
    snake_segments.pop()

第 1 條 if 語句檢測蛇頭部的 x 和 y 座標是否等於食物的座標。如果相等,該食物就會被蛇吃掉,同時 flag 變數置為 1。else 語句告訴 python 如果食物沒有被吃掉,將 snake_segments 列表中最早的專案 pop 出來。

pop 語句簡單、易用,它刪除列表中末尾的專案(並返回該專案),從而使列表縮短一項。在 snake_segments 列表裡,它使 python 刪掉距離頭部最遠的一部分。在玩家看來,蛇整體在移動而不會增長。實際上,它在一端增長小節,在另一端刪除小節。由於有 else 語句,pop 語句只有在蛇沒有吃到食物使執行。如果蛇吃到了食物,列表中的最後一項不會被刪掉,所以會增加一小節。

現在,蛇就可以通過吃食物來讓自己變長了。但是如果遊戲中只有一個食物難免會有些無聊,所以若蛇吃了一個食物,用下面的程式碼增加一個新的食物到遊戲介面中:

if flag:
    x = random.randint(1, SCREEN_RECT.width // OFFSET)
    y = random.randint(1, SCREEN_RECT.height // OFFSET)
    target = [int(x*OFFSET), int(y*OFFSET)]
    flag = 0

這部分程式碼通過判斷變數 flag 是否為 1(不為 0)來判斷食物是否被蛇吃掉了,如果被吃掉,使用程式開始引入的 random 模組獲取一個隨機的位置。然後將這個位置和蛇的每個小節的長度(畫素寬和高均為OFFSET)相乘來確定它在遊戲介面中的位置。隨機地放置食物是很重要的,防止使用者預先知道下一個食物出現的位置。最後將 flag 變數置為 0,以保證每個時刻介面上只有一個食物。

現在有了讓蛇移動和生長的程式碼,包括食物被吃和新建的操作(在遊戲中稱為食物重生),但是還沒有在介面上畫東西。輸入一下程式碼,可以將蛇和食物顯示在遊戲介面上。

screen.fill(black)
for each in snake_segments:
    pygame.draw.rect(screen, white, Rect(each[0], each[1], OFFSET, OFFSET))
pygame.draw.rect(screen, red, Rect(target[0], target[1], OFFSET, OFFSET))
pygame.display.update()

這些程式碼讓 pygame 填充背景色為黑色,蛇的頭部和身體為白色,食物為紅色。最後一行的 pygame.display.update() 讓 pygame 更新介面(如果沒有這條語句,使用者將看不到任何東西。每次在介面上畫玩物件時,記得使用 pygame.display.update() 讓使用者看到更新的介面)。

現在還沒有涉及蛇死亡的程式碼。如果遊戲中的角色永遠也死不了,玩家很快會感到無聊,所以用下面的程式碼設定一些讓蛇死亡的場景:

if (snake_position[0] < 0 or snake_position[0] > SCREEN_RECT.width-OFFSET 
		or snake_position[1] < 0 or snake_position[1] > SCREEN_RECT.height-OFFSET):
    screen.blit(gameover_text, gameover_rect)
    pygame.display.update()
    time.sleep(2)
    pygame.quit()
    exit()

if 語句檢查蛇是否走出了遊戲視窗四周的邊界,如果走出邊界,則遊戲結束,列印遊戲結束資訊,並且在結束介面暫停 2s ,再退出遊戲,關閉介面。

如果蛇頭撞到了自己身體的任何部分,也會讓蛇死亡,遊戲結束。所以遊戲結束的程式碼還有下面一種情況:

for each in snake_segments[1:]:
    if snake_position[0] == each[0] and snake_position[1] == each[1]:
        screen.blit(gameover_text, gameover_rect)
        pygame.display.update()
        time.sleep(2)
        pygame.quit()
        exit()

這裡的 for 語句遍歷蛇的每一小節的位置(從列表的第二項開始到最後一項),同時和當前蛇頭的位置比較。這裡用 snake_segments[1:] 來保證從列表的第 2 項開始遍歷。列表的第 1 項為頭部位置,如果從第 1 項開始比較,那麼遊戲一開始就死亡了。

最後,只需要設定 fps_clock 變數的值即可控制遊戲迴圈的速度。

fps_clock.tick(20)

貪吃蛇遊戲的完整原始碼如下:

import time
import random
import pygame
from pygame.locals import *



pygame.init()
SCREEN_RECT = pygame.Rect(0, 0, 1000, 750)
OFFSET = 10
fps_clock = pygame.time.Clock()
screen = pygame.display.set_mode(SCREEN_RECT.size)
pygame.display.set_caption("貪吃蛇")
gameover_font = pygame.font.SysFont("arial", 100)
gameover_text = gameover_font.render("Game Over", True, (150, 150, 150))
gameover_rect = gameover_text.get_rect()
gameover_rect.center = SCREEN_RECT.center
snake_position = [100, 100]
snake_segments = [[100, 100], [100 - OFFSET, 100], [100 - 2 * OFFSET, 100]]
target_position = [300, 300]
flag = 0
direction = 'right'
temp_direction = direction
while True:
    fps_clock.tick(20)
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            exit()
    keys_pressed = pygame.key.get_pressed()
    if keys_pressed[K_RIGHT]:
        temp_direction = 'right'
    if keys_pressed[K_LEFT]:
        temp_direction = 'left'
    if keys_pressed[K_UP]:
        temp_direction = 'up'
    if keys_pressed[K_DOWN]:
        temp_direction = 'down'
    if (((temp_direction == 'right') and (direction != 'left'))
            or ((temp_direction == 'left') and (direction != 'right'))
            or ((temp_direction == 'up') and (direction != 'down'))
            or (temp_direction == 'down') and (direction != 'up')):
        direction = temp_direction
    if direction == 'right':
        snake_position[0] += OFFSET
    if direction == 'left':
        snake_position[0] -= OFFSET
    if direction == 'up':
        snake_position[1] -= OFFSET
    if direction == 'down':
        snake_position[1] += OFFSET
    snake_segments.insert(0, list(snake_position))
    if (snake_position[0] == target_position[0]) and (snake_position[1] == target_position[1]):
        flag = 1
    else:
        snake_segments.pop()
    if flag:
        x = random.randrange(1, SCREEN_RECT.width // OFFSET)
        y = random.randint(1, SCREEN_RECT.height // OFFSET)
        target_position = [int(x * OFFSET), int(y * OFFSET)]
        flag = 0
    screen.fill((0, 0, 0))
    for each in snake_segments:
        pygame.draw.rect(screen, (255, 255, 255), Rect(each[0], each[1], OFFSET, OFFSET))
    pygame.draw.rect(screen, (255, 0, 0), Rect(target_position[0], target_position[1], OFFSET, OFFSET))
    pygame.display.update()
    if ((snake_position[0] < 0)
            or (snake_position[0] > SCREEN_RECT.width - OFFSET)
            or (snake_position[1] < 0)
            or (snake_position[1] > SCREEN_RECT.height - OFFSET)):
        screen.blit(gameover_text, gameover_rect)
        pygame.display.update()
        time.sleep(2)
        pygame.quit()
        exit()
    for each in snake_segments[1:]:
        if (snake_segments[0] == each[0]) and (snake_segments[1] == each[1]):
            screen.blit(gameover_text, gameover_rect)
            pygame.display.update()
            time.sleep(2)
            pygame.quit()
            exit()
            

遊戲執行結束時畫面如下: