python中pygame簡單利用——開發貪吃蛇
如何利用python開發一個貪吃蛇
文章目錄
前言
今天將用python開發一個貪吃蛇遊戲,主要是面向物件開發和pygame的基本使用
提示:以下是本篇文章正文內容,下面案例可供參考
一、pygame是什麼
本次python運用PyCharm2020,3x64版本編寫,python的版本為3.9
pygame是轉為開發電子遊戲而設計的跨平臺的Python包(Package)所謂模組,是指能夠獨立完成一定功能的程式集合,程式設計師利用pygame提供的API,可以方便地實現,比如說你可以建立圖形介面,監聽使用者鍵盤或滑鼠操作,播放音訊等等,這三個在本章中都有運用到。
關於pygame的安裝,一開始費了很多時間,在python的官網下載,並看教程,反反覆覆失敗了n次,最後發現在python3.9版本中安裝的方法很簡單隻需要一行指令即可
雖然版本不對,但是仍可執行。。。
驗證:在命令提示符輸入以下命令,如果能夠看到aliens遊戲正常啟動並執行,就說明已經安裝成功了。
python3 -m pygame.examples.aliens
二、貪吃蛇遊戲規則
2.1開始和結束
1.貪吃蛇一開始出生在左上角,只有一節身體一個頭
2.蛇如果碰到了自己的身體或者碰到了遊戲邊界,那麼就直接死亡
3.如果死亡或者想要暫停可以按下空格鍵
2.2怎麼運動和控制
1.我們使用監聽鍵盤上的方向鍵(↑、↓、←、→)來控制蛇的運動軌跡
2.3得分
1.得分設為吃到一個豆子得5分,初始為0分
2.食物必須滿足是在遊戲視窗隨機生成的,如果蛇頭跟食物碰到了,那就代表蛇吃到了食物,然後食物再次重新整理隨機位置
3.食物出現30s內,貪吃蛇沒吃到,那麼食物就重新整理
4.遊戲會隨著你蛇的增長,也會變快
程式碼如下(示例):
2.4 建立四個類
根據遊戲的規則我們要建立4種物件分別是遊戲物件、蛇、標籤、食物
三、開發過程
3.1主要模組
新建專案及檔案準備
本文創立了兩個模組,一個是game.py,還有一個是game_items.py,並且還需要將需要的音樂素材和圖片素材都放在同一個資料夾中,
pygame的初始化和推出
pygame為了程式設計師更加方便地使用包中模組,提供了兩個方法——init和quit
·quit方法可以取消初始化之前已經初始化過的模組,所以quit方法不是必須呼叫的
程式碼如下
import pygame #匯入pygame
from game_items import *
if __name__ == '__main__':
pygame.init() # 初始化所有模組
# 遊戲程式碼
Game().start() # 建立遊戲物件並且啟動遊戲
pygame.quit() # 取消初始化所有模組
pygame.display.update()
for event in pygame.event.get():
# 判斷事件型別是否是退出事件
if event.type == pygame.QUIT:
print("遊戲退出...")
# quit 解除安裝所有的模組
pygame.quit()
# exit() 直接終止當前正在執行的程式
exit()
遊戲主視窗
因為要實現互動介面,所以進入貪吃蛇遊戲前,我設定了一個開始介面,點選了開始遊戲即可進入遊戲,進入遊戲的過程就是重新整理視窗用到pygame中的pygame.display.update()
使用set_mode方法可以非常方便的建立一個遊戲主視窗
window=pygame.display.set_mode((800,600))
pygame.display.set_caption("Greedy Snake")
clock =pygame.time.Clock()
start_window = pygame.Surface(window.get_size()) # 充當開始介面的畫布
start_window2 = pygame.Surface(window.get_size()) # 充當第一關的畫布介面暫時佔位(可以理解為遊戲開始了)
start_window = start_window.convert()
start_window2 = start_window2.convert()
start_window.fill((255,255,255)) # 白色畫布
start_window2.fill((0,0,0))
遊戲介面的圖形插入
我們可以使用如下程式碼將圖片插入
# 載入各個素材圖片 並且賦予變數名
i1 = pygame.image.load("InitialStart.png")
i1.convert()
i11 = pygame.image.load("ChosenStart.png")
i11.convert()
遊戲迴圈和遊戲時鐘
要做到遊戲程式啟動執行之後,不會立即退出,需要在遊戲程式中加入一個遊戲迴圈,即無限迴圈
def start(self):
while True:
pass
當我們不設定一個遊戲時鐘時,我們會發現,cpu 的佔用率極高,大約有20%,重新整理幀率只要能夠達到每秒60幀,就能達到一定效果,pygame的time模組中專門提供了一個Clock類,可以非常方便地設定重新整理幀率pygame.time.Clock
事件監聽
遊戲迴圈中需要做四件事情:
事件監聽、繪製遊戲元素、更新顯示、設定重新整理率
在同一時刻,可能發生多個時間,因此應該使用for來遍歷事件列表;使用event.type可以判斷時間地型別:例如退出時間、按鍵事件等等;如果按鍵事件,使用event.key可以判斷具體的按鍵
# 事件監聽
for event in pygame.event.get(): # 遍歷同一時刻發生的事件列表
if event.type == pygame.QUIT: # 判斷退出事件
return
elif event.type == pygame.KEYDOWN: # 判斷按鍵事件
if event.key == pygame.K_ESCAPE:
return
elif event.key == pygame.K_SPACE:
if self.is_game_over:
self.reset_game()
else:
self.is_pause = not self.is_pause
繪製圖形
pygame的draw模組中專門提供了一系列方法,可以繪製圖形以蛇類為例
def draw(self, window):
# 遍歷繪製每一節身體
for idx, rect in enumerate(self.body_list):
pygame.draw.rect(window,
self.color,
rect.inflate(-2, -2), # 縮小矩形區域
idx == 0) # 蛇頭繪製邊框不填充
隨機食物
在game_item.py模組的頂部,匯入random模組,以方便使用隨機數程式碼如下import random
在Food類中定義random_rect方法,隨機確定遊戲視窗的任意小個子設定食物出現的位置,程式碼如下:
class Food(object):
"""食物類"""
def __init__(self):
self.color = (0, 255, 0) # 綠色食物
self.score = 5 # 每顆食物得分 5 分
self.rect = pygame.Rect(0, 0, CELL_SIZE, CELL_SIZE) # 食物位置
self.random_rect() # 設定食物隨機位置
def random_rect(self):
col = SCREEN_RECT.w / CELL_SIZE - 1 # 螢幕上小格子的列數
row = SCREEN_RECT.h / CELL_SIZE - 1 # 螢幕上小格子的行數
x = random.randint(0, col) * CELL_SIZE
y = random.randint(0, row) * CELL_SIZE
self.rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
# 食物初始不可見
self.rect.inflate_ip(-CELL_SIZE, -CELL_SIZE)
pygame.time.set_timer(FOOD_UPDATE_EVENT, 30000) # 設定更新食物事件
定時器的設定
按照先前的設計,如果30s內貪吃蛇沒有吃到食物,那麼食物要再次重新整理。現在如果暫時不考慮貪吃蛇吃食物的情況,要實現遊戲規則需求,我們只要每隔30s呼叫一下random_rect方法即可,這時候我們就可以用到pygame 的time模組中提供的set_timer方法,就是專門用來定時器事件的。
FOOD_UPDATE_EVENT = pygame.USEREVENT # 食物更新事件
蛇死亡判定思路
既要判斷捨得死亡又要記錄死亡時的狀態
①在Snake類定義is_dead方法:如果碰到邊界或者身體測返回True
②完善update方法:一旦發現移動身體之後貪吃蛇掛了,則回覆之前的身體資料,並且返回Flase,表示無法移動
def is_dead(self):
"""判斷貪吃蛇是否死亡
:return: 死亡返回 True,否則返回 False
"""
# 1. 記錄蛇頭的矩形區域
head = self.body_list[0]
# 2. 判斷蛇頭是否移出螢幕
if not SCREEN_RECT.contains(head):
return True
# 3. 判斷是否與身體其他部分重疊
for body in self.body_list[1:]:
if head.contains(body):
return True
return False
加入背景音樂
①先將所需要的背景音樂下載至同一個資料夾,通過pygame 的模組呼叫,程式碼如下
# 音樂的路徑
file=r'浪子康 - 小星星(咚鼓版)(翻自 汪蘇瀧).mp3'
file1=r'林俊杰 - 交換餘生.mp3'
# 初始化
pygame.mixer.init()
# 載入音樂檔案
track = pygame.mixer.music.load(file)
pygame.mixer.music.queue(file1)
# 開始播放音樂流
pygame.mixer.music.play()
3.2原始碼
game.py程式碼如下:
import pygame
from game_items import *
# 音樂的路徑
file=r'浪子康 - 小星星(咚鼓版)(翻自 汪蘇瀧).mp3'
file1=r'林俊杰 - 交換餘生.mp3'
# 初始化
pygame.mixer.init()
# 載入音樂檔案
track = pygame.mixer.music.load(file)
pygame.mixer.music.queue(file1)
# 開始播放音樂流
pygame.mixer.music.play()
pygame.init()
window=pygame.display.set_mode((800,600))
pygame.display.set_caption("Greedy Snake")
clock =pygame.time.Clock()
start_window = pygame.Surface(window.get_size()) # 充當開始介面的畫布
start_window2 = pygame.Surface(window.get_size()) # 充當第一關的畫布介面暫時佔位(可以理解為遊戲開始了)
start_window = start_window.convert()
start_window2 = start_window2.convert()
start_window.fill((255,255,255)) # 白色畫布
start_window2.fill((0,0,0))
# 載入各個素材圖片 並且賦予變數名
i1 = pygame.image.load("InitialStart.png")
i1.convert()
i11 = pygame.image.load("ChosenStart.png")
i11.convert()
# 以下為選擇開始介面滑鼠檢測結構。
n1 = True
while n1:
clock.tick(30)
buttons = pygame.mouse.get_pressed()
x1, y1 = pygame.mouse.get_pos()
if x1 >= 227 and x1 <= 555 and y1 >= 261 and y1 <=327:
start_window.blit(i11, (200, 240))
if buttons[0]:
n1 = False
window.blit(start_window,(0,0))
pygame.display.update()
# 下面是監聽退出動作
# 監聽事件
for event in pygame.event.get():
# 判斷事件型別是否是退出事件
if event.type == pygame.QUIT:
print("遊戲退出...")
# quit 解除安裝所有的模組
pygame.quit()
# exit() 直接終止當前正在執行的程式
exit()
# 以下可以寫貪吃蛇的程式碼了
n2 = True
while n2:
clock.tick(30)
from game_items import * # 匯入遊戲元素模組中的所有類和全域性變數
class Game(object):
"""遊戲類"""
def __init__(self):
self.main_window = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Greedy Snake")
self.score_label = Label() # 得分文字標籤
self.tip_label = Label(24, False) # 提示標籤
self.is_game_over = False # 遊戲結束標記
self.is_pause = False # 遊戲暫停標記
self.food = Food() # 食物
self.snake = Snake() # 貪吃蛇
print(self.snake.body_list)
def reset_game(self):
"""遊戲復位"""
self.is_game_over = False # 遊戲結束標記
self.is_pause = False # 遊戲暫停標記
self.food.random_rect() # 重新設定食物位置
self.snake.reset_snake() # 設定蛇屬性
def start(self):
"""開始貪吃蛇遊戲"""
clock = pygame.time.Clock() # 遊戲時鐘
while True:
# 事件監聽
for event in pygame.event.get(): # 遍歷同一時刻發生的事件列表
if event.type == pygame.QUIT: # 判斷退出事件
return
elif event.type == pygame.KEYDOWN: # 判斷按鍵事件
if event.key == pygame.K_ESCAPE:
return
elif event.key == pygame.K_SPACE:
if self.is_game_over:
self.reset_game()
else:
self.is_pause = not self.is_pause
# 僅在遊戲狀態才會更新食物、移動蛇的位置以及改變蛇的運動方向
if not self.is_pause and not self.is_game_over:
if event.type == FOOD_UPDATE_EVENT: # 判斷更新食物事件
self.food.random_rect()
if event.type == SNAKE_UPDATE_EVENT: # 判斷貪吃蛇移動事件
self.is_game_over = not self.snake.update()
if event.type == pygame.KEYDOWN: # 判斷按鍵事件
# 判斷是否方向鍵
if event.key in (pygame.K_LEFT, pygame.K_RIGHT,
pygame.K_UP, pygame.K_DOWN):
self.snake.change_dir(event.key)
# 依次繪製遊戲元素
self.main_window.fill(BACKGROUND_COLOR)
# 判斷遊戲狀態
if self.is_game_over:
self.tip_label.draw(self.main_window, "遊戲結束,按空格鍵開啟新遊戲...")
elif self.is_pause:
self.tip_label.draw(self.main_window, "遊戲暫停,按空格鍵繼續...")
else:
# 判斷是否吃到食物
if self.snake.has_eat(self.food):
self.food.random_rect()
self.score_label.draw(self.main_window, "得分:%d" % self.snake.score)
self.food.draw(self.main_window)
self.snake.draw(self.main_window)
# 更新顯示
pygame.display.update()
clock.tick(60) # 重新整理幀率
if __name__ == '__main__':
pygame.init() # 初始化所有模組
# 遊戲程式碼
Game().start() # 建立遊戲物件並且啟動遊戲
pygame.quit() # 取消初始化所有模組
pygame.display.update()
for event in pygame.event.get():
# 判斷事件型別是否是退出事件
if event.type == pygame.QUIT:
print("遊戲退出...")
# quit 解除安裝所有的模組
pygame.quit()
# exit() 直接終止當前正在執行的程式
exit()
game_items.py程式碼如下:
import random
import pygame
# 全域性變數定義
SCREEN_RECT = pygame.Rect(0, 0, 640, 480) # 遊戲視窗矩形區域
CELL_SIZE = 20 # 小格子大小
BACKGROUND_COLOR = (255,255, 255) # 主視窗背景顏色
SCORE_TEXT_COLOR = (0, 0, 0) # 分數文字顏色
TIP_TEXT_COLOR = (0, 0, 255) # 提示文字顏色
FOOD_UPDATE_EVENT = pygame.USEREVENT # 食物更新事件
SNAKE_UPDATE_EVENT = pygame.USEREVENT + 1 # 貪吃蛇移動事件
class Label(object):
"""文字標籤類"""
def __init__(self, size=48, is_score=True):
"""初始化方法
:param size: 字型大小
:param is_score: 是否分數
"""
self.font = pygame.font.SysFont("simhei", size) # 黑體字
self.is_score = is_score
def draw(self, window, text):
"""在視窗中繪製文字內容
:param window: 遊戲主視窗
:param text: 要顯示的文字內容
"""
# 1. 使用字型渲染文字內容
color = SCORE_TEXT_COLOR if self.is_score else TIP_TEXT_COLOR
text_surface = self.font.render(text, True, color)
# 獲得文字影象的矩形區域
text_rect = text_surface.get_rect()
# 獲得主視窗的矩形區域
window_rect = window.get_rect()
# 設定位置
if self.is_score: # 分數文字顯示在右下角
text_rect.bottomright = window_rect.bottomright
else: # 其他文字顯示在中間
text_rect.center = window_rect.center
# 2. 在遊戲視窗中繪製渲染結果
window.blit(text_surface, text_rect)
class Food(object):
"""食物類"""
def __init__(self):
self.color = (0, 255, 0) # 綠色食物
self.score = 5 # 每顆食物得分 5 分
self.rect = pygame.Rect(0, 0, CELL_SIZE, CELL_SIZE) # 食物位置
self.random_rect() # 設定食物隨機位置
def random_rect(self):
col = SCREEN_RECT.w / CELL_SIZE - 1 # 螢幕上小格子的列數
row = SCREEN_RECT.h / CELL_SIZE - 1 # 螢幕上小格子的行數
x = random.randint(0, col) * CELL_SIZE
y = random.randint(0, row) * CELL_SIZE
self.rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
# 食物初始不可見
self.rect.inflate_ip(-CELL_SIZE, -CELL_SIZE)
pygame.time.set_timer(FOOD_UPDATE_EVENT, 30000) # 設定更新食物事件
def draw(self, window):
# # 判斷寬度是否達到小格子寬度
if self.rect.w < CELL_SIZE:
self.rect.inflate_ip(2, 2) # 向四周各自放大 1 個畫素
pygame.draw.ellipse(window, self.color, self.rect)
class Snake(object):
"""貪吃蛇類"""
def __init__(self):
self.dir = pygame.K_RIGHT # 初始向右運動
self.score = 0 # 初始得分
self.time_interval = 250 # 運動間隔時間
self.color = (255, 144, 255) # 身體顏色-紫粉
self.body_list = [] # 身體列表
self.reset_snake()
def reset_snake(self):
"""重置蛇屬性"""
self.dir = pygame.K_RIGHT
self.score = 0
self.time_interval = 250
self.body_list.clear() # 清空身體列表
for i in range(2): # 新增一節身體
self.add_node()
def add_node(self):
"""在蛇的運動方向上,增加一節身體"""
# 1. 判斷是否有身體
if self.body_list:
head = self.body_list[0].copy()
else:
head = pygame.Rect(-CELL_SIZE, 0, CELL_SIZE, CELL_SIZE)
# 2. 根據運動方向,調整 head 的位置
if self.dir == pygame.K_RIGHT:
head.x += CELL_SIZE
elif self.dir == pygame.K_LEFT:
head.x -= CELL_SIZE
elif self.dir == pygame.K_UP:
head.y -= CELL_SIZE
elif self.dir == pygame.K_DOWN:
head.y += CELL_SIZE
# 3. 將蛇頭插入到身體列表第 0 項
self.body_list.insert(0, head)
# 4. 設定貪吃蛇移動定時器
pygame.time.set_timer(SNAKE_UPDATE_EVENT, self.time_interval)
def draw(self, window):
# 遍歷繪製每一節身體
for idx, rect in enumerate(self.body_list):
pygame.draw.rect(window,
self.color,
rect.inflate(-2, -2), # 縮小矩形區域
idx == 0) # 蛇頭繪製邊框不填充
def update(self):
"""移動貪吃蛇的整個身體
一旦發現貪吃蛇移動後會死亡,則恢復整個身體資料
:return: 移動成功返回 True,貪吃蛇死亡表示不能移動,返回 False
"""
# 1. 備份身體列表
body_list_copy = self.body_list.copy()
# 2. 移動身體
self.add_node() # 沿著運動方向在蛇頭位置增加一節身體
self.body_list.pop() # 刪除蛇尾
# 3. 判斷是否死亡,如果是,恢復備份的身體
if self.is_dead():
self.body_list = body_list_copy
return False
return True
def change_dir(self, to_dir):
"""改變貪吃蛇的運動方向
:param to_dir: 要變化的方向
"""
hor_dirs = (pygame.K_RIGHT, pygame.K_LEFT) # 水平方向
ver_dirs = (pygame.K_UP, pygame.K_DOWN) # 垂直方向
# 判斷當前運動方向及要修改的方向
if ((self.dir in hor_dirs and to_dir not in hor_dirs) or
(self.dir in ver_dirs and to_dir not in ver_dirs)):
self.dir = to_dir
def has_eat(self, food):
"""判斷蛇頭是否與食物相遇 - 吃到食物
:param food: 食物物件
:return: 是否吃到食物
"""
if self.body_list[0].contains(food.rect):
self.score += food.score # 增加分數
# 修改運動時間間隔
if self.time_interval > 100:
self.time_interval -= 50
self.add_node() # 增加一節身體
return True
return False
def is_dead(self):
"""判斷貪吃蛇是否死亡
:return: 死亡返回 True,否則返回 False
"""
# 1. 記錄蛇頭的矩形區域
head = self.body_list[0]
# 2. 判斷蛇頭是否移出螢幕
if not SCREEN_RECT.contains(head):
return True
# 3. 判斷是否與身體其他部分重疊
for body in self.body_list[1:]:
if head.contains(body):
return True
return False
總結
此次由於時間的倉促,在開發貪吃蛇遊戲的過程中,雖然遊戲整體可以執行,但是互動仍有待提高,其中存在了一些問題還沒有來得及去解決,比如死亡時候bgm要暫停,或者是玩家自己暫停遊戲,bgm也要停止,這些都沒有時間去完善,再就是遊戲過於單一,沒有設計出遊戲難度的選擇,只有開始遊戲這一選項,需要日後學習完善。
以下是展示視訊