貪吃蛇小遊戲

by pygame

在vscode終端打入以下指令

開啟vscode終端 : ctrl + `

pip install pygame

檢查pygame有沒有安裝成功

python -m pygame.examples.aliens

按ctrl + shift + p 並輸入

Python: Select Interpreter

選擇和剛剛pygame對應版本的python就好囉!

前置作業完成,讓我們開始製作遊戲吧!~~

Step1. 畫出蛇蛇並使其動起來

建立遊戲視窗

# 主迴圈外
import pygame # 導入函式庫

pygame.init() # 初始化pygame

window_x = 780 # 設定遊戲視窗大小
window_y = 780
window = pygame.display.set_mode((window_x, window_y)) # (())記得

pygame.display.set_caption("貪吃蛇小遊戲") # 定義視窗名稱

設定結束條件

# 主迴圈
running = True
while running:
    # 偵測事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
pygame.quit() # 結束pygame

pygame.event.get()用法

# 返回一個儲存所有未處理事件的列表
pygame.event.get()
# 檢查輸入的事件是不是關閉視窗(結束遊戲)
if event.type == pygame.QUIT:

怎麼畫出方塊

# 主迴圈內
unit = 30
black = (0,0,0)
green = (143,206,0)
window.fill(black) # 將畫布填滿黑色,把上幀的圖像清掉
pygame.draw.rect(window, green, (0, 0, unit, unit)) # 畫出30*30的綠色矩形
pygame.display.update() # 更新畫布

pygame.draw.rect()用法

pygame.draw.rect(surface, color, rect) # 劃出矩形的函式
# surface:要畫的地方
# color:(RGB)顏色
# rect:(左上角x座標, 左上角y座標, 高度, 寬度)

方塊出來了

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    window.fill(black)
    pygame.draw.rect(window, green, (0, 0, unit, unit))
    pygame.display.update()
pygame.quit()

畫出蛇蛇

# 在主迴圈之前設定初始蛇身
snake_body = [[2 * unit, 0], [unit, 0], [0, 0]]

# 在主迴圈裡面繪製蛇身
for body in snake_body:
    pygame.draw.rect(window, green, (body[0], body[1], unit, unit))

讓蛇蛇動起來

我們該怎麼讓蛇蛇動起來呢?
-->先伸長再縮短

蛇頭伸長蛇身縮短

# 主迴圈外
snake_head = [2 * unit, 0]
# 主迴圈內
snake_head[0] += unit # 新的座標
snake_body.insert(0, list(snake_head)) # 插入新的座標/伸長
snake_body.pop() # 刪除最後的座標/縮短

insert(), pop()用法

a_list.insert(index, element)
# index : 插入位置的索引
# element : 要插入的元素

a_list.pop(index)
# index : 要移除元素的索引值(默認為-1)

設定執行速度

# 延遲1秒(單位為毫秒)
pygame.time.delay(1000)

# 在主迴圈之前
game_speed = 10 # 每秒跑10次迴圈

# 在主迴圈裡面
pygame.time.delay(1000 // game_speed)

迴圈每秒只能跑10次,為了避免輸入的事件無法在10次迴圈內完成,我們需要預先處理佇列中的事件

# 紀錄迴圈開始前的時間
start_time = pygame.time.get_ticks()

# (迴圈當下執行的時間 - 迴圈開始前的時間) < (1000 // game_speed)
while pygame.time.get_ticks() - start_time < 1000 // game_speed:
    pygame.event.pump() # 處理佇列中的事件

改變蛇蛇移動方向

* 重點:新的移動方向和上次的移動方向不能差180度

# 在主迴圈之前
direction = 'RIGHT' # 預設方向
new_direction = direction # 新方向

# 在主迴圈裡面

# 偵測鍵盤事件
if event.key == pygame.K_d:
    new_direction = 'RIGHT'
elif event.key == pygame.K_a:
    new_direction = 'LEFT'
elif event.key == pygame.K_s:
    new_direction = 'DOWN'
elif event.key == pygame.K_w:
    new_direction = 'UP'

如果不想用wasd想使用數字鍵旁的方向鍵
可以把pygame.K_d改成pygame.K_RIGHT(以此類推)

# 判斷轉彎方向是否合理
if new_direction == 'RIGHT' and direction != 'LEFT':
    direction = 'RIGHT'
elif new_direction == 'LEFT' and direction != 'RIGHT':
    direction = 'LEFT'
elif new_direction == 'DOWN' and direction != 'UP':
    direction = 'DOWN'
elif new_direction == 'UP' and direction != 'DOWN':
    direction = 'UP'

目前code

import pygame

black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)

pygame.init()
window_x = 800
window_y = 600
window = pygame.display.set_mode((window_x, window_y))
pygame.display.set_caption("貪吃蛇小遊戲")

unit = 30

snake_head = [0, 0]
snake_body = [[0, 0]]
snake_length = 3

direction = 'RIGHT'
new_direction = direction

game_speed = 10
running = True
while running:

    start_time = pygame.time.get_ticks()
    while pygame.time.get_ticks() - start_time < 1000 // game_speed:
        pygame.event.pump()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_d:
                new_direction = 'RIGHT'
            elif event.key == pygame.K_a:
                new_direction = 'LEFT'
            elif event.key == pygame.K_s:
                new_direction = 'DOWN'
            elif event.key == pygame.K_w:
                new_direction = 'UP'
    if new_direction == 'RIGHT' and direction != 'LEFT':
        direction = 'RIGHT'
    elif new_direction == 'LEFT' and direction != 'RIGHT':
        direction = 'LEFT'
    elif new_direction == 'DOWN' and direction != 'UP':
        direction = 'DOWN'
    elif new_direction == 'UP' and direction != 'DOWN':
        direction = 'UP'

    if direction == 'RIGHT':
        snake_head[0] += unit
    elif direction == 'LEFT':
        snake_head[0] -= unit
    elif direction == 'DOWN':
        snake_head[1] += unit
    elif direction == 'UP':
        snake_head[1] -= unit
    snake_body.insert(0, list(snake_head))
    snake_body.pop()

    window.fill(black)

    for body in snake_body:
        pygame.draw.rect(window, green, (body[0], body[1], unit, unit))

    pygame.display.update()

pygame.quit()

2. 讓蛇蛇能吃果實長大

random函式庫

import random

# 隨機返回0到1之間不包含1的浮點數
random.random()

# 隨機返回0到10之間所有整數(包含10)
random.randint(0, 10)

# 返回從0到10之間的整數,每次的間隔為2,不包含10
r3 = [random.randrange(0, 10, 2) for k in range(10)]
print(r3)
# [0, 4, 6, 0, 8, 4, 8, 0, 8, 6]
r3 = []
for i in range(10):
    list.append(random.randrange(0, 10, 2))
print(r3)

生成果實

果實生成的座標以unit為間隔隨機生成

# 在主迴圈之前 / 隨機初始座標
fruit = [random.randrange(0, window_x, unit),
         random.randrange(0, window_y, unit)]
# 在主迴圈裡面 / 繪製果實
pygame.draw.rect(window, red, (fruit[0], fruit[1], unit, unit))

避免果實長在蛇身上

# 以遞迴重複確認
def new_fruit():
    new_pos = [random.randrange(0, window_x, unit),
               random.randrange(0, window_y, unit)]
    if new_pos in snake_body:
        return new_fruit()
    else:
        return new_pos
    
# 初始化果實座標
fruit = new_fruit()

長身體

  1. 身體伸長 --> 蛇頭伸長,蛇身不變
  2. 吃到果實就再產生一組新的果實座標
if snake_head == fruit:
    fruit = [random.randrange(0, window_x, unit),
             random.randrange(0, window_y, unit)]
else:
    snake_body.pop()

碰撞判定

  1. 不能撞到東西
# x軸邊界
if not(0 <= snake_head[0] < window_x):
    break
# y軸邊界
if not(0 <= snake_head[1] < window_y):
    break
  1. 不能碰到身體
# snake_body[1:]不包含頭的座標
if snake_head in snake_body[1:]:
    break

目前程式碼

import pygame
import random


def new_fruit():
    new_pos = [random.randrange(0, window_x, unit),
               random.randrange(0, window_y, unit)]
    if new_pos in snake_body:
        return new_fruit()
    else:
        return new_pos

black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)

pygame.init()
window_x = 720
window_y = 630
window = pygame.display.set_mode((window_x, window_y))
pygame.display.set_caption("Pygame貪食蛇")

unit = 30

snake_head = [0, 0]
snake_body = [[0, 0]]
snake_length = 3
for i in range(snake_length - 1):
    snake_head[0] += unit
    snake_body.insert(0, list(snake_head))
direction = 'RIGHT'
new_direction = direction

fruit = new_fruit()

game_speed = 10
running = True
while running:

    start_time = pygame.time.get_ticks()
    while pygame.time.get_ticks() - start_time < 1000 // game_speed:
        pygame.event.pump()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_d:
                new_direction = 'RIGHT'
            elif event.key == pygame.K_a:
                new_direction = 'LEFT'
            elif event.key == pygame.K_s:
                new_direction = 'DOWN'
            elif event.key == pygame.K_w:
                new_direction = 'UP'
    if new_direction == 'RIGHT' and direction != 'LEFT':
        direction = 'RIGHT'
    elif new_direction == 'LEFT' and direction != 'RIGHT':
        direction = 'LEFT'
    elif new_direction == 'DOWN' and direction != 'UP':
        direction = 'DOWN'
    elif new_direction == 'UP' and direction != 'DOWN':
        direction = 'UP'

    if direction == 'RIGHT':
        snake_head[0] += unit
    elif direction == 'LEFT':
        snake_head[0] -= unit
    elif direction == 'DOWN':
        snake_head[1] += unit
    elif direction == 'UP':
        snake_head[1] -= unit

    snake_body.insert(0, list(snake_head))

    if snake_head == fruit:
        fruit = new_fruit()
    else:
        snake_body.pop()
    if not (0 <= snake_head[0] < window_x):
        break
    if not(0 <= snake_head[1] < window_y):
        break
    if snake_head in snake_body[1:]:
        break

    window.fill(black)

    for body in snake_body:
        pygame.draw.rect(window, green, (body[0], body[1], unit, unit))

    pygame.draw.rect(window, red, (fruit[0], fruit[1], unit, unit))

    pygame.display.update()

pygame.quit()

3.物件導向設計

目標:優化程式架構和製作記分板。

記分板

宣告一個用來計算分數的變數,接著把文字貼到視窗上

# 在主迴圈之前
score = 0

# 在主迴圈裡面
if snake_head == fruit: # 吃到果實加1分
    score += 1 
text_font = pygame.font.SysFont("", 60)  # 字體及大小
#"":預設字體
text_surface = text_font.render(str(score), True, white)  # 文字和顏色
#str(score):要顯示的文字
#true:讓文字邊緣更平滑、清晰
text_rect = text_surface.get_rect()  # 取得矩形的值
window.blit(text_surface, text_rect)  # 以左上為錨點顯示文字

但以上方法太麻煩了 ! 所以我們用class簡化

class Text:
    def __init__(self, txt, size): #self:物件本身
        font = pygame.font.SysFont("", size)
        self.surface = font.render(txt, True, white)
        self.rect = self.surface.get_rect()
        window.blit(self.surface, self.rect)

Text(str(score), 60) # 一行搞定

class的用處類似收納櫃,將類似性質的物件歸納在一起

物件導向

物件(Object):class的實例,包含屬性和方法。每個物件都是獨立的實體,有自己的狀態和功能。

類別(Class):物件的屬性和方法。可創建多個相似的物件。

屬性(Attributes):物件的數據或狀態,用變數表示。

方法(Methods):物件的行為或功能,定義在class中的函數

蛇蛇

class Snake:
    def __init__(self, color, head):
        self.color = color # 蛇蛇的顏色
        self.head = head #舌頭初始位置
        self.body = [head] #蛇伸初始位置
        self.direction = 'RIGHT' #初始移動方向
        self.new_direction = 'RIGHT' #新的移動方向
        length = 4 # 這是常數不用寫成屬性(蛇身初始長度)
        for i in range(length - 1):
            self.head[0] += unit
            self.body.insert(0, list(self.head))

果實

class Fruit:
    def __init__(self):
        self.pos = self.new_fruit()

    def new_fruit(self):
        new_pos = [random.randrange(0, window_x, unit),
                   random.randrange(0, window_y, unit)]
        if new_pos in snake.body:
            return self.new_fruit()
        else:
            return new_pos

new_fruit()是一個函式,函式無法直接修改外部的變數,所以需要讓它回傳座標值。而在class裡可以用method直接修改屬性值。

def __init__(self):
    self.pos = [0, 0] #果實初始位置
    self.spawn() #生成果實位置

def spawn(self):
    self.pos = [random.randrange(0, window_x, unit),
                random.randrange(0, window_y, unit)] #隨機生成果實位置
    if self.pos in snake.body:
        self.spawn() #如果果實生成在蛇身上,重新生成果實

重新開始

原本的程式在輸了之後會直接結束,每次都需要重新執行一次。我們可以在遊戲結束後確認是否重新開始

while True:
    # 宣告變數
    ...
    
    # 遊戲主程式
    running = True
    while running:
        ...
        for event in pygame.event.get():
            if event.type == pygame.QUIT: #偵測玩家是否嘗試關閉遊戲視窗
                running = False #迴圈停止
        ...

按下enter重新開始

    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RETURN:
                    running = False   #結束程式        
pygame.quit() # 可以刪除

sys函式庫提供一系列的功能,包括退出當前程式。

import sys
sys.exit() # 退出程式

為了exit()把整個sys函式庫導入會增加程式負擔,我們可以用from sys import exit單獨導入函式。

from sys import exit
exit() # 前綴可以省略因為已經直接從sys導入了。

在退出程式前,要記得先退出pygame。沒退出的話.....
也不會怎樣,但還是養成習慣比較好~

# 遊戲主程式
running = True
while running:
    ...
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit() # 退出Pygame
            exit() #退出程式
    ...

# 按下Enter重新開始
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RETURN:
                running = False #按下Enter鍵重新開始遊戲

目前code

import pygame
import random
from sys import exit


class Text:
    def __init__(self, txt, size):
        font = pygame.font.SysFont("", size)
        self.surface = font.render(txt, True, white)
        self.rect = self.surface.get_rect()
        window.blit(self.surface, self.rect)
class Snake:
    def __init__(self, color, head):
        self.color = color
        self.head = head
        self.body = [head]
        self.direction = 'RIGHT'
        self.new_direction = 'RIGHT'
        length = 4
        for i in range(length - 1):
            self.head[0] += unit
            self.body.insert(0, list(self.head))

class Fruit:
    def __init__(self):
        self.pos = [0, 0]
        self.spawn()

    def spawn(self):
        self.pos = [random.randrange(0, window_x, unit),
                    random.randrange(0, window_y, unit)]
        if self.pos in snake.body:
            self.spawn()


black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)

pygame.init()
window_x = 720
window_y = 630
window = pygame.display.set_mode((window_x, window_y))
pygame.display.set_caption("Pygame貪食蛇")

game_speed = 10
unit = 30
while True:

    score = 0
    snake = Snake(green, [0, 0])
    fruit = Fruit()

    while True:

        start_time = pygame.time.get_ticks()
        while pygame.time.get_ticks() - start_time < 1000 // game_speed:
            pygame.event.pump()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_d:
                    snake.new_direction = 'RIGHT'
                elif event.key == pygame.K_a:
                    snake.new_direction = 'LEFT'
                elif event.key == pygame.K_s:
                    snake.new_direction = 'DOWN'
                elif event.key == pygame.K_w:
                    snake.new_direction = 'UP'

        if snake.new_direction == 'RIGHT' and snake.direction != 'LEFT':
            snake.direction = 'RIGHT'
        elif snake.new_direction == 'LEFT' and snake.direction != 'RIGHT':
            snake.direction = 'LEFT'
        elif snake.new_direction == 'DOWN' and snake.direction != 'UP':
            snake.direction = 'DOWN'
        elif snake.new_direction == 'UP' and snake.direction != 'DOWN':
            snake.direction = 'UP'

        if snake.direction == 'RIGHT':
            snake.head[0] += unit
        elif snake.direction == 'LEFT':
            snake.head[0] -= unit
        elif snake.direction == 'DOWN':
            snake.head[1] += unit
        elif snake.direction == 'UP':
            snake.head[1] -= unit
        snake.body.insert(0, list(snake.head))
        if snake.head == fruit.pos:
            fruit.spawn()
            score += 1
        else:
            snake.body.pop()

        if not (0 <= snake.head[0] < window_x):
            break
        if not(0 <= snake.head[1] < window_y):
            break
        if snake.head in snake.body[1:]:
            break

        window.fill(black)

        for body in snake.body:
            pygame.draw.rect(window, snake.color, (body[0], body[1], unit, unit))
        pygame.draw.rect(window, red, (fruit.pos[0], fruit.pos[1], unit, unit))

        Text(str(score), 60)

        pygame.display.update()

    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RETURN:
                    running = False

4.美化遊戲介面

目標:導入圖片和音效讓遊戲更美觀 !

遊戲背景

img = pygame.image.load("PATH/image.png").convert() # 載入圖片並轉換
window.blit(img, (0, 0)) # 在window的(0, 0)畫出圖片

convert()的用途是把圖片轉換成像素格式。

# 從resources資料夾中導入背景圖片並轉換
background = pygame.image.load("../img/background.png").convert()
![](../教學簡報/簡易遊戲code/snake/img/background.png)
# 繪製圖片
window.blit(background, (0, 0))

原本背景是全黑畫布,可以自行改為任何圖片

矩形邊框和圓角

除了可以設定座標和大小外,矩形還有邊框和圓角可以設定

pygame.draw.rect(surface, color, rect, width, border_radius)
# width - 空心矩形的邊框寬度,預設為0就代表實心。
# border radius - 矩形的圓角半徑,預設為-1代表直角

可用以下參數和顏色繪製更多細節

# 在主迴圈之前
class Snake:
    def __init__(self, c1, c2, head):
        self.color1 = c1 # 底色
        self.color2 = c2 # 邊框色
        
# 更多的顏色
black = (0, 0, 0)
grey = (85, 85, 85)
white = (255, 255, 255)
red = (229, 46, 8)
darkRed = (157, 31, 6)
green = (64, 201, 73)
darkGreen = (36, 127, 42)
blue = (78, 124, 246)
darkBlue = (9, 53, 174)
purple = (182, 72, 242)
darkPurple = (116, 12, 172)

# 在主迴圈裡面
snake = Snake(green, darkGreen, [0, 0])

接著為蛇蛇和果實加上邊框並在右下角加上陰影 !

# 蛇蛇的陰影
pygame.draw.rect(canvas, grey, (snake.head[0], snake.head[1], unit + 2, unit + 2)) 
for body in snake.body[1:]:
    pygame.draw.rect(canvas, grey, (body[0] + 1, body[1] + 1, unit, unit))
#canvas:繪製於畫布上
#snake.head[0];蛇頭x座標
#snake.head[1]:蛇頭y座標
#unit+2:蛇蛇陰影寬度+2

# 蛇頭
pygame.draw.rect(canvas, snake.color1, (snake.head[0] - 1, snake.head[1] - 1, unit + 2, unit + 2))
pygame.draw.rect(canvas, snake.color2, (snake.head[0] - 1, snake.head[1] - 1, unit + 2, unit + 2), 2)

# 蛇身
for body in snake.body[1:]:
    pygame.draw.rect(canvas, snake.color1, (body[0], body[1], unit, unit))
    pygame.draw.rect(canvas, snake.color2, (body[0], body[1], unit, unit), 2)
    
# 果實
pygame.draw.rect(canvas, grey, (fruit.pos[0] + 3, fruit.pos[1] + 3, unit - 4, unit - 4), 0, 3)
pygame.draw.rect(canvas, red, (fruit.pos[0] + 2, fruit.pos[1] + 2, unit - 4, unit - 4), 0, 3)
pygame.draw.rect(canvas, darkRed, (fruit.pos[0] + 2, fruit.pos[1] + 2, unit - 4, unit - 4), 2, 3)

透明圖片

convert_alpha()在轉換圖片時會保留透明通道,讓圖片能有透明背景。可以給蛇蛇畫臉,或製作半透明的結束畫面。

# 主迴圈之前
face = pygame.image.load("resources/face.png").convert_alpha()
game_over = pygame.image.load("resources/game_over.png").convert_alpha()

# 主迴圈裡面
window.blit(face, snake.head)

# 主迴圈之後
window.blit(game_over, (0, 0))
pygame.display.update()

新建畫布

若想把記分板顯示在遊戲區域外,可建立一層新的畫布。
用來畫遊戲區域、邊框和記分板。

# 主畫布(整個視窗)
window = pygame.display.set_mode((780, 780))

# 遊戲區域
canvas_x = 720
canvas_y = 630
canvas = pygame.Surface((canvas_x, canvas_y))

# 導入邊框圖片並轉換
border = pygame.image.load("resources/border.png").convert()

canvas是新建立的畫布,用來畫蛇蛇的遊戲區域。將主迴圈中原本的window改成canvas後,畫到主畫布上。

window.blit(border, (0, 0)) # 在主畫布畫出邊框
window.blit(canvas, (30, 120)) # 在主畫布畫出遊戲區域

文字設定

可以傳遞更多參數,如顏色和字體給文字添加更多花樣

class Text:
    def __init__(self, txt, size, color, font):
        font = pygame.font.SysFont(font, size)
        self.surface = font.render(txt, True, color)
        self.rect = self.surface.get_rect()
        window.blit(self.surface, self.rect)

要讓文字顯示在指定位置,需在畫出來之前移動矩形到指定座標,移動的方法主要分為兩種。

rect.move_ip(x, y) # 方法(method) 相對位移
rect.topleft = (x, y) # 虛擬屬性(virtual attributes) 絕對定位

move_ip() 用法

左上角為錨點,若想要以其他錨點為基準移動,可以用修改虛擬屬性的方式,矩形有九個錨點分別為:

topleft midtop topright

midleft center midright

bottomleft midbottom bottomright

class需要用到不同的錨點時,可以寫多個方法來使用

class Text:
    def __init__(self, txt, size, color, font):
        font = pygame.font.SysFont(font, size)
        self.surface = font.render(txt, True, color)
        self.rect = self.surface.get_rect()
    # 左上
    def topleft(self, x, y):
        self.rect.topleft = (x, y)
        window.blit(self.surface, self.rect)
    # 左中
    def midleft(self,x , y):
        self.rect.midleft = (x, y)
        window.blit(self.surface, self.rect)
    ...

# 記分板
Text(str(score), 45, white, "impact").midleft(90, 45)

加點音樂 !

在遊戲中撥放背景音樂和音效需要用到pygame的mixer模組

# 初始化混音器模組
pygame.mixer.init()
# 導入一個音效
sfx = pygame.mixer.Sound("PATH/sfx.wav")
# 播放
sfx.play()
# 停止
sfx.stop()

導入背景音樂、得分音效和遊戲結束音效,
用play(-1)讓背景音樂循環播放

# 在主迴圈之前
pygame.mixer.init()
bgm = pygame.mixer.Sound("../wav/bgm.wav")
fruit_sfx = pygame.mixer.Sound("../wav/fruit.wav")
game_over_sfx = pygame.mixer.Sound("../wav/game_over.wav")
bgm.play(-1) # 循環播放背景音樂

# 在主迴圈裡面
if snake.head == fruit.pos:
    fruit_sfx.play() # 果實音效
    
# 在主迴圈之後
game_over_sfx.play() # 遊戲結束音效

完整程式碼

import pygame
import random
from sys import exit


class Text:
    def __init__(self, txt, size, color, font):
        font = pygame.font.SysFont(font, size)
        self.surface = font.render(txt, True, color)
        self.rect = self.surface.get_rect()

    def midleft(self, x, y):
        self.rect.midleft = (x, y)
        window.blit(self.surface, self.rect)
class Snake:
    def __init__(self, c1, c2, head):
        self.color1 = c1
        self.color2 = c2
        self.head = head
        self.body = [head]
        self.direction = 'RIGHT'
        self.new_direction = 'RIGHT'
        length = 4
        for i in range(length - 1):
            self.head[0] += unit
            self.body.insert(0, list(self.head))

class Fruit:
    def __init__(self):
        self.pos = [0, 0]
        self.spawn()

    def spawn(self):
        self.pos = [random.randrange(0, canvas_x, unit),
                    random.randrange(0, canvas_y, unit)]
        if self.pos in snake.body:
            self.spawn()
black = (0, 0, 0)
grey = (85, 85, 85)
white = (255, 255, 255)
red = (229, 46, 8)
darkRed = (157, 31, 6)
green = (64, 201, 73)
darkGreen = (36, 127, 42)
blue = (78, 124, 246)
darkBlue = (9, 53, 174)
purple = (182, 72, 242)
darkPurple = (116, 12, 172)

pygame.init()
window = pygame.display.set_mode((780, 780))
pygame.display.set_caption("Pygame貪食蛇")
canvas_x = 720
canvas_y = 630
canvas = pygame.Surface((canvas_x, canvas_y))

background = pygame.image.load("resources/background.png").convert()
border = pygame.image.load("resources/border.png").convert()
face = pygame.image.load("resources/face.png").convert_alpha()
game_over = pygame.image.load("resources/game_over.png").convert_alpha()

pygame.mixer.init()
bgm = pygame.mixer.Sound("resources/bgm.wav")
fruit_sfx = pygame.mixer.Sound("resources/fruit.wav")
game_over_sfx = pygame.mixer.Sound("resources/game_over.wav")
bgm.play(-1)

game_speed = 10
unit = 30
while True:

    score = 0
    snake = Snake(green, darkGreen, [0, 0])
    fruit = Fruit()

    while True:

        start_time = pygame.time.get_ticks()
        while pygame.time.get_ticks() - start_time < 1000 // game_speed:
            pygame.event.pump()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_d:
                    snake.new_direction = 'RIGHT'
                elif event.key == pygame.K_a:
                    snake.new_direction = 'LEFT'
                elif event.key == pygame.K_s:
                    snake.new_direction = 'DOWN'
                elif event.key == pygame.K_w:
                    snake.new_direction = 'UP'
# while True:
#   ...
#   while True:
#       ...
        
        if snake.new_direction == 'RIGHT' and snake.direction != 'LEFT':
            snake.direction = 'RIGHT'
        elif snake.new_direction == 'LEFT' and snake.direction != 'RIGHT':
            snake.direction = 'LEFT'
        elif snake.new_direction == 'DOWN' and snake.direction != 'UP':
            snake.direction = 'DOWN'
        elif snake.new_direction == 'UP' and snake.direction != 'DOWN':
            snake.direction = 'UP'

        if snake.direction == 'RIGHT':
            snake.head[0] += unit
        elif snake.direction == 'LEFT':
            snake.head[0] -= unit
        elif snake.direction == 'DOWN':
            snake.head[1] += unit
        elif snake.direction == 'UP':
            snake.head[1] -= unit
# while True:
#   ...
#   while True:
#       ...
        
        snake.body.insert(0, list(snake.head))

        if snake.head == fruit.pos:
            fruit_sfx.play()
            fruit.spawn()
            score += 1
        else:
            snake.body.pop()

        if not (0 <= snake.head[0] < canvas_x):
            break
        if not(0 <= snake.head[1] < canvas_y):
            break
        if snake.head in snake.body[1:]:
            break
# while True:
#   ...
#   while True:
#       ...
        
        canvas.blit(background, (0, 0))

        pygame.draw.rect(canvas, grey, (snake.head[0], snake.head[1], unit + 2, unit + 2))
        for body in snake.body[1:]:
            pygame.draw.rect(canvas, grey, (body[0] + 1, body[1] + 1, unit, unit))

        pygame.draw.rect(canvas, snake.color1, (snake.head[0] - 1, snake.head[1] - 1, unit + 2, unit + 2))
        pygame.draw.rect(canvas, snake.color2, (snake.head[0] - 1, snake.head[1] - 1, unit + 2, unit + 2), 2)
# while True:
#   ...
#   while True:
#       ...
        
        for body in snake.body[1:]:
            pygame.draw.rect(canvas, snake.color1, (body[0], body[1], unit, unit))
            pygame.draw.rect(canvas, snake.color2, (body[0], body[1], unit, unit), 2)

        canvas.blit(face, snake.head)

        pygame.draw.rect(canvas, grey, (fruit.pos[0] + 3, fruit.pos[1] + 3, unit - 4, unit - 4), 0, 3)
        pygame.draw.rect(canvas, red, (fruit.pos[0] + 2, fruit.pos[1] + 2, unit - 4, unit - 4), 0, 3)
        pygame.draw.rect(canvas, darkRed, (fruit.pos[0] + 2, fruit.pos[1] + 2, unit - 4, unit - 4), 2, 3)
# while True:
#   ...
#   while True:
#       ...
        
        window.blit(border, (0, 0))
        window.blit(canvas, (30, 120))
        Text(str(score), 45, white, "impact").midleft(90, 45)

        pygame.display.update()

    game_over_sfx.play()
    window.blit(game_over, (0, 0))
    pygame.display.update()
# while True:
#   ...
#   while True:
#       ...
        
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RETURN:
                    running = False

以上~~~ 感謝各位 ~~