pygame.mixer 是 Python 遊戲開發與多媒體應用中最常用的聲音播放模組,支援音樂串流播放與音效即時觸發兩種模式。本文系統整理基礎播放流程、音樂 vs 音效差異、常用控制 API、錯誤處理與實戰封裝類別。
| 特性 | pygame.mixer.music (音樂) | pygame.mixer.Sound (音效) |
|---|---|---|
| 用途 | 背景音樂、長音頻 | 短音效、UI 聲音、槍聲 |
| 載入方式 | 串流 (不全載入記憶體) | 完整載入記憶體 |
| 同時播放 | 單一軌道 (同一時間只能一首) | 多通道並發 (預設 8 通道) |
| 格式支援 | MP3, OGG, MOD 等 | WAV, OGG (建議 WAV 免解碼) |
| 控制 API | play, pause, stop, fadeout, set_volume | play, stop, fadeout, set_volume |
import pygame
pygame.init()
pygame.mixer.init()
# 載入並播放音樂
pygame.mixer.music.load("background.mp3")
pygame.mixer.music.play()
# 等待播放結束(簡易阻塞)
while pygame.mixer.music.get_busy():
pygame.time.Clock().tick(10)
pygame.quit()
import pygame
pygame.init()
pygame.mixer.init()
# 載入音效
shot = pygame.mixer.Sound("shot.wav")
# 觸發播放
shot.play()
# 等待音效結束
while shot.get_num_channels() > 0:
pygame.time.Clock().tick(10)
pygame.quit()
import pygame
import os
from typing import Dict, Optional
class AudioManager:
"""統一管理音樂與音效"""
def __init__(self, frequency=44100, size=-16, channels=2, buffer=512):
pygame.mixer.pre_init(frequency, size, channels, buffer)
pygame.mixer.init()
self.sounds: Dict[str, pygame.mixer.Sound] = {}
self.music_volume = 0.7
self.sfx_volume = 1.0
pygame.mixer.music.set_volume(self.music_volume)
def load_sound(self, name: str, filepath: str) -> bool:
"""載入音效並快取"""
if not os.path.exists(filepath):
print(f"[Audio] 檔案不存在: {filepath}")
return False
try:
self.sounds[name] = pygame.mixer.Sound(filepath)
self.sounds[name].set_volume(self.sfx_volume)
return True
except pygame.error as e:
print(f"[Audio] 載入失敗 {name}: {e}")
return False
def load_music(self, filepath: str) -> bool:
"""載入背景音樂"""
if not os.path.exists(filepath):
print(f"[Audio] 音樂檔案不存在: {filepath}")
return False
try:
pygame.mixer.music.load(filepath)
pygame.mixer.music.set_volume(self.music_volume)
return True
except pygame.error as e:
print(f"[Audio] 音樂載入失敗: {e}")
return False
def play_sound(self, name: str, loops: int = 0, maxtime: int = 0, fade_ms: int = 0) -> Optional[pygame.mixer.Channel]:
"""播放音效,回傳 Channel 供後續控制"""
sound = self.sounds.get(name)
if not sound:
print(f"[Audio] 音效未載入: {name}")
return None
channel = sound.play(loops, maxtime, fade_ms)
return channel
def play_music(self, loops: int = -1, start: float = 0.0, fade_ms: int = 0):
"""播放背景音樂,loops=-1 循環播放"""
pygame.mixer.music.play(loops, start, fade_ms)
def stop_music(self, fade_ms: int = 0):
pygame.mixer.music.fadeout(fade_ms) if fade_ms else pygame.mixer.music.stop()
def pause_music(self):
pygame.mixer.music.pause()
def unpause_music(self):
pygame.mixer.music.unpause()
def set_music_volume(self, volume: float):
self.music_volume = max(0.0, min(1.0, volume))
pygame.mixer.music.set_volume(self.music_volume)
def set_sfx_volume(self, volume: float):
self.sfx_volume = max(0.0, min(1.0, volume))
for sound in self.sounds.values():
sound.set_volume(self.sfx_volume)
def stop_all_sounds(self):
pygame.mixer.stop()
def cleanup(self):
self.stop_all_sounds()
pygame.mixer.music.stop()
self.sounds.clear()
pygame.mixer.quit()
# === 使用範例 ===
if __name__ == "__main__":
audio = AudioManager()
# 載入資源
audio.load_sound("jump", "assets/jump.wav")
audio.load_sound("coin", "assets/coin.wav")
audio.load_music("assets/bgm.ogg")
# 播放
audio.play_music(loops=-1) # 循環背景音樂
audio.play_sound("jump")
# 遊戲主迴圈中...
import time
time.sleep(5)
audio.cleanup()
# 取得 Channel 物件
channel = sound.play()
# 後續控制該 Channel
channel.set_volume(0.5)
channel.pause()
channel.unpause()
channel.stop()
channel.fadeout(1000) # 1 秒淡出
# 設定結束回調
def on_sound_end():
print("音效播放結束")
channel.set_endevent(pygame.USEREVENT + 1)
# 主迴圈中檢查 event.type == pygame.USEREVENT + 1
def crossfade_music(audio_mgr, new_music_path, fade_ms=2000):
"""平滑切換背景音樂"""
audio_mgr.stop_music(fade_ms)
# 等待淡出完成(簡易)
pygame.time.wait(fade_ms)
audio_mgr.load_music(new_music_path)
audio_mgr.play_music(fade_ms=fade_ms)
def play_3d_sound(sound, x_pos, listener_x=0, max_dist=100):
"""
x_pos: -1.0 (左) 到 1.0 (右)
簡易立體聲平移
"""
left_vol = max(0, 1 - x_pos) if x_pos > 0 else 1
right_vol = max(0, 1 + x_pos) if x_pos < 0 else 1
# Sound 無直接立體聲控制,需用兩個單聲道 Sound 或 Channel.set_volume
channel = sound.play()
if channel:
# pygame 2.0+ 支援 Channel.set_volume(left, right)
channel.set_volume(left_vol, right_vol)
import numpy as np
def generate_tone(freq=440, duration=0.5, volume=0.5, sample_rate=44100):
"""生成正弦波音效"""
t = np.linspace(0, duration, int(sample_rate * duration), False)
wave = np.sin(2 * np.pi * freq * t) * volume * 32767
wave = wave.astype(np.int16)
# 轉為雙聲道
stereo = np.column_stack((wave, wave))
return pygame.mixer.Sound(buffer=stereo.tobytes())
# 使用
tone = generate_tone(880, 0.3)
tone.play()
| 格式 | 用途 | 優點 | 缺點 |
|---|---|---|---|
| WAV (PCM) | 音效 | 免解碼、延遲極低、相容性最好 | 檔案大 |
| OGG Vorbis | 音樂/音效 | 壓縮率高、開源、支援循環點 | 需解碼、略有延遲 |
| MP3 | 音樂 | 通用、壓縮率高 | 授權問題、不支援無縫循環 |
| FLAC | 音樂 | 無損壓縮 | 解碼較慢、不支援循環 |
建議:音效用 WAV (16-bit, 44.1kHz, mono),音樂用 OGG。
| 問題 | 原因 | 解決 |
|---|---|---|
| 無聲音 | 未呼叫 pygame.mixer.init()、音量為 0、裝置被佔用 | 檢查 init 順序、音量設定、關閉其他播放器 |
| 音效延遲 | 用 music 播放短音效、格式需解碼(MP3/OGG) | 音效用 Sound + WAV、預載入快取 |
| 同時播放截斷 | 通道數不足 (預設 8) | pygame.mixer.set_num_channels(32) |
| MP3 不支援 | 系統缺少 MP3 解碼器 | 改用 OGG、安裝 libmpg123 |
| 程式結束聲音截斷 | 未等待播放完成 | 用 while get_busy() 等待或設定 endevent |
PyGame 声音播放核心:音樂用 mixer.music (串流、單軌)、音效用 mixer.Sound (記憶體、多通道)。關鍵最佳實務:pre_init 設定參數、WAV 格式音效、快取 Sound 物件、Channel 精確控制、fadeout 平滑切換。封裝 AudioManager 類別可統一資源管理、音量控制、生命週期,是遊戲與多媒體專案的標準做法。