轉載註明鏈接:https://www.cnblogs.com/liquancai/p/13269428.html 基於 pygame 設計貪吃蛇游戲 貪吃蛇游戲通過玩家控制蛇移動,不斷吃到食物增長,直到碰到蛇身或邊界游戲結束。其運行效果如下所示: 游戲開始時,先導入可能需要用到的包。 import ...
轉載註明鏈接: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()
游戲運行結束時畫面如下: