Python利用Pygame實(shí)現(xiàn)一個(gè)經(jīng)典貪吃蛇游戲
項(xiàng)目概述
本文通過 Pygame 實(shí)現(xiàn)一個(gè)經(jīng)典貪吃蛇游戲(Snake)。
在這個(gè)游戲中,玩家控制一條在網(wǎng)格上不斷移動(dòng)的蛇,吃掉食物來增加身體長度并累積分?jǐn)?shù)。地圖上隨機(jī)出現(xiàn)限時(shí)金色獎(jiǎng)勵(lì)食物,得分越高關(guān)卡越高,蛇的移動(dòng)速度隨之加快。碰到邊界或自身即游戲失敗,目標(biāo)是在不撞墻、不咬到自己的情況下盡可能多地吃到食物。其中:
- 移動(dòng)控制:方向鍵 / W、A、S、D 鍵控制蛇的移動(dòng)方向;不能直接反向移動(dòng)(防止立即自咬)。
- 普通食物:紅色圓點(diǎn),每吃一個(gè)得
10 × 當(dāng)前關(guān)卡分,蛇身增長一格,同時(shí)有概率觸發(fā)獎(jiǎng)勵(lì)食物出現(xiàn)。 - 獎(jiǎng)勵(lì)食物:金色閃爍星形食物,每個(gè)得
50 × 當(dāng)前關(guān)卡分,限時(shí)存在(約 8 個(gè)移動(dòng)周期),超時(shí)自動(dòng)消失。 - 關(guān)卡遞進(jìn):得分每滿 100 分自動(dòng)升一級(jí)(最高 10 級(jí)),移動(dòng)速度隨關(guān)卡線性提升(初始 8 FPS,每級(jí) +2)。
- 鍵盤操作:方向鍵 / WASD:控制方向;R:重新開始。
- 勝利/失敗條件:蛇頭撞到邊界或碰到自身即游戲結(jié)束;游戲無上限關(guān)卡,挑戰(zhàn)最高分。

游戲?qū)崿F(xiàn)
初始化與基礎(chǔ)設(shè)置
游戲啟動(dòng)時(shí)初始化 Pygame 并定義屏幕尺寸、網(wǎng)格參數(shù)和顏色常量。
pygame.init()
WIDTH, HEIGHT = 540, 540
COLS, ROWS = 18, 18
CELL = WIDTH // COLS
screen = pygame.display.set_mode((WIDTH, HEIGHT + 60))
pygame.display.set_caption("Snake")
clock = pygame.time.Clock()
游戲區(qū)域?yàn)?540×540 像素的 18×18 網(wǎng)格,每格 30×30 像素;屏幕額外留出頂部 60 像素作為 HUD 信息欄。
顏色定義
DARK_BG = (10, 15, 13) # 游戲區(qū)深色背景 GRID_C = (14, 26, 20) # 網(wǎng)格線顏色 GREEN1 = (93, 202, 165) # 蛇頭顏色 GREEN2 = (29, 158, 117) # 蛇身基礎(chǔ)色 RED = (226, 75, 74) # 普通食物 GOLD = (250, 199, 117) # 獎(jiǎng)勵(lì)食物 WHITE = (220, 220, 220) # HUD 主文字 GRAY = (100, 120, 110) # HUD 次要文字
字體加載
CHINESE_FONT_PATH = r"C:/Windows/Fonts/simsun.ttc" font_sm = pygame.font.Font(CHINESE_FONT_PATH, 22) font_md = pygame.font.Font(CHINESE_FONT_PATH, 32) font_big = pygame.font.Font(CHINESE_FONT_PATH, 56)
直接加載系統(tǒng)中文字體文件,保證中文 HUD 文字正常顯示??缙脚_(tái)時(shí)可替換為 pygame.font.SysFont(None, size)。
核心類設(shè)計(jì)
游戲主類(Game)
本游戲?qū)⑺羞壿嫾性?Game 類中,通過狀態(tài)變量管理蛇、食物、關(guān)卡和分?jǐn)?shù)。
構(gòu)造函數(shù) __init__:
def __init__(self):
self.best = 0
self.reset()
best 最高分在游戲?qū)ο笊芷趦?nèi)持久保留,重開后仍顯示歷史最高分。
重置方法 reset:
def reset(self):
self.snake = [(9, 9), (8, 9), (7, 9)] # 初始蛇身(列, 行)坐標(biāo)列表
self.direction = (1, 0) # 當(dāng)前移動(dòng)方向(向右)
self.next_dir = (1, 0) # 下一幀生效方向(防止中間幀連續(xù)輸入導(dǎo)致反向)
self.score = 0
self.level = 1
self.speed = 8 # 初始移動(dòng)頻率(FPS tick)
self.food = self._spawn()
self.bonus = None
self.bonus_ttl = 0
self.game_over = False
self.frame = 0
使用 next_dir 緩存輸入而非直接修改 direction,避免在同一幀內(nèi)連續(xù)按兩個(gè)方向鍵時(shí)穿越自身的經(jīng)典 Bug。
食物生成 _spawn:
def _spawn(self, exclude=None):
cells = [(c, r) for c in range(COLS) for r in range(ROWS)
if (c, r) not in self.snake]
if exclude and exclude in cells:
cells.remove(exclude)
return random.choice(cells)
先從全部網(wǎng)格中排除蛇身占據(jù)的格子,再隨機(jī)取一個(gè)作為食物位置。exclude 參數(shù)可在生成獎(jiǎng)勵(lì)食物時(shí)同時(shí)排除普通食物位置,防止兩者重疊。
獎(jiǎng)勵(lì)食物觸發(fā) _try_spawn_bonus:
def _try_spawn_bonus(self):
if self.bonus is None and random.random() < 0.35:
self.bonus = self._spawn(self.food)
self.bonus_ttl = self.speed * 8
每次吃到普通食物后,以 35% 概率嘗試生成獎(jiǎng)勵(lì)食物。bonus_ttl(生命幀數(shù))設(shè)為當(dāng)前速度的 8 倍,使獎(jiǎng)勵(lì)食物的可見時(shí)間在不同關(guān)卡下大致等效于"8 步內(nèi)"。
事件處理handle_events
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
self.reset()
dirs = {
pygame.K_UP: (0, -1), pygame.K_w: (0, -1),
pygame.K_DOWN: (0, 1), pygame.K_s: (0, 1),
pygame.K_LEFT: (-1, 0), pygame.K_a: (-1, 0),
pygame.K_RIGHT: (1, 0), pygame.K_d: (1, 0),
}
if event.key in dirs:
nd = dirs[event.key]
if (nd[0] + self.direction[0], nd[1] + self.direction[1]) != (0, 0):
self.next_dir = nd
反向檢測邏輯:若新方向向量與當(dāng)前方向向量之和為 (0, 0),說明是正反方向(如左+右),則忽略該輸入,防止蛇直接 180° 掉頭咬到自身第二節(jié)身體。
核心更新update
update 方法集中處理蛇的移動(dòng)、碰撞檢測、吃食物和關(guān)卡晉升邏輯:
def update(self):
if self.game_over:
return
self.direction = self.next_dir
hx = self.snake[0][0] + self.direction[0]
hy = self.snake[0][1] + self.direction[1]
# ① 碰壁或碰自身 → 游戲結(jié)束
if not (0 <= hx < COLS and 0 <= hy < ROWS) or (hx, hy) in self.snake:
self.game_over = True
if self.score > self.best:
self.best = self.score
return
head = (hx, hy)
self.snake.insert(0, head)
grew = False
# ② 吃到普通食物
if head == self.food:
self.score += 10 * self.level
self.food = self._spawn()
self._try_spawn_bonus()
grew = True
# ③ 吃到獎(jiǎng)勵(lì)食物
elif self.bonus and head == self.bonus:
self.score += 50 * self.level
self.bonus = None
self.bonus_ttl = 0
grew = True
# 未吃到食物則去除尾部(維持蛇長)
if not grew:
self.snake.pop()
# ④ 獎(jiǎng)勵(lì)食物計(jì)時(shí)
if self.bonus:
self.bonus_ttl -= 1
if self.bonus_ttl <= 0:
self.bonus = None
# ⑤ 關(guān)卡晉升
new_level = self.score // 100 + 1
if new_level > self.level:
self.level = min(new_level, 10)
self.speed = 8 + (self.level - 1) * 2
if self.score > self.best:
self.best = self.score
self.frame += 1
蛇的移動(dòng)通過"頭插尾刪"實(shí)現(xiàn):每幀在列表頭部插入新頭部坐標(biāo),若未吃到食物則同時(shí)刪除尾部坐標(biāo),從而在不復(fù)制整條蛇的情況下高效模擬移動(dòng)。
繪制方法draw
HUD 信息欄:
pygame.draw.rect(screen, (18, 28, 22), (0, 0, WIDTH, 60))
pygame.draw.line(screen, GREEN2, (0, 60), (WIDTH, 60), 1)
screen.blit(font_sm.render(f"SCORE {self.score}", True, WHITE), (14, 18))
screen.blit(font_sm.render(f"BEST {self.best}", True, GRAY), (14, 38))
screen.blit(font_sm.render(f"LV {self.level}", True, GREEN1), (WIDTH - 90, 18))
screen.blit(font_sm.render("R=重來", True, GRAY), (WIDTH - 90, 38))
網(wǎng)格背景:
for r in range(ROWS):
for c in range(COLS):
pygame.draw.rect(screen, GRID_C,
(c * CELL, 60 + r * CELL, CELL, CELL), 1)
逐格繪制單像素邊框,形成淡色網(wǎng)格,不遮擋游戲元素。
蛇的繪制:
for i, (c, r) in enumerate(self.snake):
rect = pygame.Rect(c * CELL + 2, 60 + r * CELL + 2, CELL - 4, CELL - 4)
color = GREEN1 if i == 0 else (
20, int(80 + (1 - i / len(self.snake)) * 100), 60)
pygame.draw.rect(screen, color, rect, border_radius=4)
蛇頭用亮綠色 GREEN1 突出顯示;蛇身顏色隨節(jié)序線性變暗(綠色通道從 180 漸降至 80),形成由頭到尾的漸變視覺效果。每節(jié)縮進(jìn) 2px 并加圓角,避免視覺上連成一片。
蛇眼繪制:
if i == 0:
dx, dy = self.direction
ex = c * CELL + CELL // 2 + dy * 5
ey = 60 + r * CELL + CELL // 2 + dx * 5 - abs(dy) * 4
pygame.draw.circle(screen, DARK_BG, (ex - dy * 4, ey + dx * 4), 2)
pygame.draw.circle(screen, DARK_BG, (ex + dy * 4, ey - dx * 4), 2)
眼睛位置通過當(dāng)前方向向量 (dx, dy) 動(dòng)態(tài)計(jì)算,始終出現(xiàn)在蛇頭朝向一側(cè)的兩邊,隨移動(dòng)方向自動(dòng)旋轉(zhuǎn),賦予蛇頭表情感。
獎(jiǎng)勵(lì)食物閃爍效果:
if self.bonus:
alpha = 180 + int(60 * abs(pygame.time.get_ticks() % 600 / 300 - 1))
surf = pygame.Surface((CELL, CELL), pygame.SRCALPHA)
pygame.draw.circle(surf, (*GOLD, alpha), (CELL // 2, CELL // 2), CELL // 2 - 3)
screen.blit(surf, (bc * CELL, 60 + br * CELL))
star = font_sm.render("★", True, (255, 230, 80))
screen.blit(star, (bx - star.get_width() // 2, by - star.get_height() // 2))
利用 pygame.time.get_ticks() 取模生成周期為 600ms 的三角波,將透明度在 180~240 之間往復(fù)變化,實(shí)現(xiàn)自然的心跳閃爍效果,吸引玩家注意。
繪制順序?yàn)椋篐UD → 網(wǎng)格 → 蛇 → 食物 → 獎(jiǎng)勵(lì)食物 → 結(jié)束遮罩,嚴(yán)格保證層次正確。
主循環(huán) run:
def run(self):
while True:
self.handle_events()
self.update()
self.draw()
clock.tick(self.speed if not self.game_over else 30)
游戲進(jìn)行中以 self.speed(8~26 FPS)驅(qū)動(dòng)主循環(huán),控制蛇的移動(dòng)節(jié)奏;游戲結(jié)束后切換為固定 30 FPS,保持結(jié)束畫面流暢渲染而不占用過多 CPU。
全部代碼
import pygame
import random
import sys
pygame.init()
WIDTH, HEIGHT = 540, 540
COLS, ROWS = 18, 18
CELL = WIDTH // COLS
BLACK = (0, 0, 0)
DARK_BG = (10, 15, 13)
GRID_C = (14, 26, 20)
GREEN1 = (93, 202, 165) # 蛇頭
GREEN2 = (29, 158, 117) # 蛇身
RED = (226, 75, 74) # 食物
GOLD = (250, 199, 117) # 獎(jiǎng)勵(lì)食物
WHITE = (220, 220, 220)
GRAY = (100, 120, 110)
CHINESE_FONT_PATH = r"C:/Windows/Fonts/simsun.ttc"
# 使用具體字體文件(如果系統(tǒng)有):
font_sm = pygame.font.Font(CHINESE_FONT_PATH, 22)
font_md = pygame.font.Font(CHINESE_FONT_PATH, 32)
font_big = pygame.font.Font(CHINESE_FONT_PATH, 56)
screen = pygame.display.set_mode((WIDTH, HEIGHT + 60))
pygame.display.set_caption("Snake")
clock = pygame.time.Clock()
class Game:
def __init__(self):
self.best = 0
self.reset()
def reset(self):
self.snake = [(9, 9), (8, 9), (7, 9)]
self.direction = (1, 0)
self.next_dir = (1, 0)
self.score = 0
self.level = 1
self.speed = 8 # FPS tick rate
self.food = self._spawn()
self.bonus = None
self.bonus_ttl = 0
self.game_over = False
self.frame = 0
def _spawn(self, exclude=None):
cells = [(c, r) for c in range(COLS) for r in range(ROWS)
if (c, r) not in self.snake]
if exclude and exclude in cells:
cells.remove(exclude)
return random.choice(cells)
def _try_spawn_bonus(self):
if self.bonus is None and random.random() < 0.35:
self.bonus = self._spawn(self.food)
self.bonus_ttl = self.speed * 8 # 可見幀數(shù)
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
self.reset()
dirs = {
pygame.K_UP: (0, -1), pygame.K_w: (0, -1),
pygame.K_DOWN: (0, 1), pygame.K_s: (0, 1),
pygame.K_LEFT: (-1, 0), pygame.K_a: (-1, 0),
pygame.K_RIGHT: (1, 0), pygame.K_d: (1, 0),
}
if event.key in dirs:
nd = dirs[event.key]
if (nd[0] + self.direction[0], nd[1] + self.direction[1]) != (0, 0):
self.next_dir = nd
def update(self):
if self.game_over:
return
self.direction = self.next_dir
hx = self.snake[0][0] + self.direction[0]
hy = self.snake[0][1] + self.direction[1]
if not (0 <= hx < COLS and 0 <= hy < ROWS) or (hx, hy) in self.snake:
self.game_over = True
if self.score > self.best:
self.best = self.score
return
head = (hx, hy)
self.snake.insert(0, head)
grew = False
if head == self.food:
self.score += 10 * self.level
self.food = self._spawn()
self._try_spawn_bonus()
grew = True
elif self.bonus and head == self.bonus:
self.score += 50 * self.level
self.bonus = None
self.bonus_ttl = 0
grew = True
if not grew:
self.snake.pop()
if self.bonus:
self.bonus_ttl -= 1
if self.bonus_ttl <= 0:
self.bonus = None
new_level = self.score // 100 + 1
if new_level > self.level:
self.level = min(new_level, 10)
self.speed = 8 + (self.level - 1) * 2
if self.score > self.best:
self.best = self.score
self.frame += 1
def draw(self):
# --- HUD strip ---
screen.fill((8, 12, 10))
pygame.draw.rect(screen, (18, 28, 22), (0, 0, WIDTH, 60))
pygame.draw.line(screen, GREEN2, (0, 60), (WIDTH, 60), 1)
screen.blit(font_sm.render(f"SCORE {self.score}", True, WHITE), (14, 18))
screen.blit(font_sm.render(f"BEST {self.best}", True, GRAY), (14, 38))
screen.blit(font_sm.render(f"LV {self.level}", True, GREEN1), (WIDTH - 90, 18))
screen.blit(font_sm.render("R=重來", True, GRAY), (WIDTH - 90, 38))
# --- Grid ---
for r in range(ROWS):
for c in range(COLS):
pygame.draw.rect(screen, GRID_C,
(c * CELL, 60 + r * CELL, CELL, CELL), 1)
# --- Snake ---
for i, (c, r) in enumerate(self.snake):
rect = pygame.Rect(c * CELL + 2, 60 + r * CELL + 2, CELL - 4, CELL - 4)
color = GREEN1 if i == 0 else (
20, int(80 + (1 - i / len(self.snake)) * 100), 60)
pygame.draw.rect(screen, color, rect, border_radius=4)
if i == 0:
# 眼睛
dx, dy = self.direction
ex = c * CELL + CELL // 2 + dy * 5
ey = 60 + r * CELL + CELL // 2 + dx * 5 - abs(dy) * 4
pygame.draw.circle(screen, DARK_BG, (ex - dy * 4, ey + dx * 4), 2)
pygame.draw.circle(screen, DARK_BG, (ex + dy * 4, ey - dx * 4), 2)
# --- Food ---
fc, fr = self.food
cx, cy = fc * CELL + CELL // 2, 60 + fr * CELL + CELL // 2
pygame.draw.circle(screen, RED, (cx, cy), CELL // 2 - 3)
pygame.draw.circle(screen, (255, 150, 148), (cx - 2, cy - 2), 3)
# --- Bonus ---
if self.bonus:
bc, br = self.bonus
bx, by = bc * CELL + CELL // 2, 60 + br * CELL + CELL // 2
alpha = 180 + int(60 * abs(pygame.time.get_ticks() % 600 / 300 - 1))
surf = pygame.Surface((CELL, CELL), pygame.SRCALPHA)
pygame.draw.circle(surf, (*GOLD, alpha), (CELL // 2, CELL // 2), CELL // 2 - 3)
screen.blit(surf, (bc * CELL, 60 + br * CELL))
star = font_sm.render("★", True, (255, 230, 80))
screen.blit(star, (bx - star.get_width() // 2, by - star.get_height() // 2))
# --- Overlays ---
if self.game_over:
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((10, 15, 13, 180))
screen.blit(overlay, (0, 60))
t1 = font_big.render("GAME OVER", True, RED)
t2 = font_sm.render(f"得分 {self.score} 最高 {self.best}", True, WHITE)
t3 = font_sm.render("按 R 重新開始", True, GRAY)
screen.blit(t1, (WIDTH // 2 - t1.get_width() // 2, 60 + HEIGHT // 2 - 80))
screen.blit(t2, (WIDTH // 2 - t2.get_width() // 2, 60 + HEIGHT // 2 + 10))
screen.blit(t3, (WIDTH // 2 - t3.get_width() // 2, 60 + HEIGHT // 2 + 50))
pygame.display.flip()
def run(self):
while True:
self.handle_events()
self.update()
self.draw()
clock.tick(self.speed if not self.game_over else 30)
if __name__ == "__main__":
Game().run()
以上就是Python利用Pygame實(shí)現(xiàn)一個(gè)經(jīng)典貪吃蛇游戲的詳細(xì)內(nèi)容,更多關(guān)于Python Pygame貪吃蛇游戲的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python實(shí)現(xiàn)生成書法字體的示例代碼
這篇文章主要為大家詳細(xì)介紹了一個(gè)使用matplotlib生成書法文字圖片的Python方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-11-11
Python模糊查詢本地文件夾去除文件后綴的實(shí)例(7行代碼)
下面小編就為大家?guī)硪黄狿ython模糊查詢本地文件夾去除文件后綴的實(shí)例(7行代碼) 。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11
python3.6利用pyinstall打包py為exe的操作實(shí)例
今天小編就為大家分享一篇python3.6利用pyinstall打包py為exe的操作實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-10-10
用Python實(shí)現(xiàn)web端用戶登錄和注冊功能的教程
這篇文章主要介紹了用Python實(shí)現(xiàn)web端用戶登錄和注冊功能的教程,需要的朋友可以參考下2015-04-04
pytest實(shí)現(xiàn)多種調(diào)用方式
pytest是一個(gè)非常成熟的全功能的Python測試框架,本文主要介紹了pytest多種調(diào)用方式,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12
缺失值可能是數(shù)據(jù)科學(xué)中最不受歡迎的值,然而,它們總是在身邊。忽略缺失值也是不合理的,因此我們需要找到有效且適當(dāng)?shù)靥幚硭鼈兊姆椒?。本文總結(jié)了四個(gè)Python查詢?nèi)笔е档姆椒ǎ枰目梢詤⒖家幌?/div> 2022-05-05
Python實(shí)現(xiàn)將CSV轉(zhuǎn)換為帶格式的Excel
在日常的數(shù)據(jù)處理和報(bào)表生成工作中,CSV 格式因其簡潔性而被廣泛采用,本文將介紹如何使用 Python 將 CSV 文件轉(zhuǎn)換為 Excel 文件,希望對(duì)大家有所幫助2025-07-07最新評(píng)論

