🎹 Python钢琴节奏大师 · 第6章
系列:Python钢琴节奏大师:从零到游戏开发实战[1]章节:第6章 / 共12章标题:声音交互篇-Pygame音效播放与鼠标点击检测上一章:第5章-钢琴进阶篇[2]下一章:第7章-键盘映射篇[3]
让琴键会"响"——Pygame播放声音
今日目标:下载钢琴音效,学习Pygame声音播放,实现点击琴键发出对应音高,让钢琴真正"活"起来。
💡 本章核心:给钢琴添加声音,实现点击发声的交互
🎹 效果预览
完成本篇学习后,你可以:
┌────────────────────────────────────────────────────────────┐│ ││ ■ ■ ■ ■ ■ ││ ■ ■ ■ ■ ■ ││ ■ ■ ■ ■ ■ ││ ■ ■ ■ ■ ■ ││ ┌───┼───┼───┐ ┌───┼───┼───┼───┐ ││ │ │ ■ │ │ │ │ ■ │ ■ │ │ ││ │ 0 │1│2│ 3 │ │ 4 │5│6│ 7 │ ← 点击这些键! ││ │ │ │ │ │ │ │ │ │ 会发出声音 ││ │ │ │ │ │ │ │ │ │ ││ │do │#re│mi │ │fa │#sol│la │si │ ││ └───┴───┴───┘ └───┴───┴───┴───┘ ││ ││ 🎵 点击琴键,开始演奏吧! 🎵 │└────────────────────────────────────────────────────────────┘
🤔 声音是怎么播放的?
计算机如何发出声音?
计算机播放声音的过程:
什么是WAV文件?
WAV是Windows的标准音频格式:
对于钢琴游戏,WAV是最简单可靠的选择。
📝 第一步:准备音效文件
下载钢琴音效
你需要12个音效文件(7个白键 + 5个黑键):
音效资源获取方式
方式1:网上下载
搜索关键词:"piano wav samples free download"
推荐网站:
方式2:自己录制
用钢琴App或电子琴录制12个音,导出为WAV格式。
创建sounds文件夹
- 在你的项目文件夹里,新建一个文件夹叫
sounds
python-piano-game/├── 06_piano_sound.py # 你的代码├── sounds/ # 音效文件夹│ ├── c4.wav│ ├── d4.wav│ ├── e4.wav│ ├── f4.wav│ ├── g4.wav│ ├── a4.wav│ ├── b4.wav│ ├── cs4.wav│ ├── ds4.wav│ ├── fs4.wav│ ├── gs4.wav│ └── as4.wav
📝 第二步:初始化声音系统
Pygame声音模块
Pygame有两个声音相关的模块:
| | |
|---|
pygame.mixer | | |
pygame.mixer.music | | |
钢琴键声音用 pygame.mixer。
初始化代码
在 pygame.init() 之后,添加:
# 初始化声音系统(频率44100Hz,16位,双声道,缓冲区4096)pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=4096)
参数说明:
简化版(使用默认参数):
pygame.mixer.init()
📝 第三步:加载声音文件
加载单个声音
# 加载一个声音文件sound_do = pygame.mixer.Sound("sounds/c4.wav")
加载所有声音
用列表存储所有声音:
# 白键音效文件列表WHITE_SOUNDS = ["c4.wav", "d4.wav", "e4.wav", "f4.wav", "g4.wav", "a4.wav", "b4.wav"]# 黑键音效文件列表BLACK_SOUNDS = ["cs4.wav", "ds4.wav", "fs4.wav", "gs4.wav", "as4.wav"]# 加载所有白键音效white_sounds = []for filename in WHITE_SOUNDS: sound = pygame.mixer.Sound(f"sounds/{filename}") white_sounds.append(sound)# 加载所有黑键音效black_sounds = []for filename in BLACK_SOUNDS: sound = pygame.mixer.Sound(f"sounds/{filename}") black_sounds.append(sound)
使用列表推导式(更简洁)
# 一行代码加载所有白键音效white_sounds = [pygame.mixer.Sound(f"sounds/{f}") for f in WHITE_SOUNDS]# 一行代码加载所有黑键音效 black_sounds = [pygame.mixer.Sound(f"sounds/{f}") for f in BLACK_SOUNDS]
📝 第四步:播放声音
基本播放
# 播放声音sound_do.play()
播放指定音效
# 播放第0个白键的声音(do)white_sounds[0].play()# 播放第2个黑键的声音(#fa)black_sounds[2].play()
停止播放
# 停止某个声音sound_do.stop()# 停止所有声音pygame.mixer.stop()
📝 第五步:检测鼠标点击
鼠标事件类型
| |
|---|
pygame.MOUSEBUTTONDOWN | |
pygame.MOUSEBUTTONUP | |
pygame.MOUSEMOTION | |
获取鼠标位置
# 获取当前鼠标位置(x, y)mouse_x, mouse_y = pygame.mouse.get_pos()
检测点击事件
for event in pygame.event.get():if event.type == pygame.QUIT: running = False# 检测鼠标按下elif event.type == pygame.MOUSEBUTTONDOWN:# 获取鼠标位置 mouse_x, mouse_y = event.posprint(f"鼠标点击位置:({mouse_x}, {mouse_y})")
📝 第六步:判断点击了哪个键
判断点是否在矩形内
Pygame提供了一个方便的函数:
# 创建一个矩形对象rect = pygame.Rect(x, y, width, height)# 判断点是否在矩形内if rect.collidepoint(mouse_x, mouse_y):print("点击了矩形!")
应用到钢琴键
# 检测白键点击for i inrange(WHITE_KEY_COUNT): x = START_X + i * WHITE_KEY_WIDTH key_rect = pygame.Rect(x, START_Y, WHITE_KEY_WIDTH, WHITE_KEY_HEIGHT)if key_rect.collidepoint(mouse_x, mouse_y):print(f"点击了白键 {i} ({WHITE_KEY_NAMES[i]})") white_sounds[i].play() # 播放对应音效break# 找到就停止,避免重复检测# 检测黑键点击(注意:黑键要在白键之后检测,因为黑键在上面)for i inrange(BLACK_KEY_COUNT): white_key_index = BLACK_KEY_POSITIONS[i] white_key_x = START_X + white_key_index * WHITE_KEY_WIDTH black_key_x = white_key_x + WHITE_KEY_WIDTH - BLACK_KEY_WIDTH // 2 key_rect = pygame.Rect(black_key_x, START_Y, BLACK_KEY_WIDTH, BLACK_KEY_HEIGHT)if key_rect.collidepoint(mouse_x, mouse_y):print(f"点击了黑键 {i} ({BLACK_KEY_NAMES[i]})") black_sounds[i].play() # 播放对应音效break
重要:先检测白键,再检测黑键!
因为黑键在白键上面,如果先检测黑键,点击黑键时会同时触发白键检测。
🎯 完整代码
import pygame# ========== 初始化 ==========pygame.init()# 初始化声音系统pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=4096)# ========== 窗口设置 ==========SCREEN_WIDTH = 800SCREEN_HEIGHT = 600screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))pygame.display.set_caption("会发声的钢琴")# ========== 颜色定义 ==========BLACK = (0, 0, 0)WHITE = (255, 255, 255)GRAY = (200, 200, 200)DARK_GRAY = (50, 50, 50)YELLOW = (255, 255, 0) # 按键按下时的颜色# ========== 钢琴键参数 ==========WHITE_KEY_WIDTH = 80WHITE_KEY_HEIGHT = 300WHITE_KEY_COUNT = 7START_X = 100START_Y = 200BLACK_KEY_WIDTH = 50BLACK_KEY_HEIGHT = 180BLACK_KEY_COUNT = 5BLACK_KEY_POSITIONS = [0, 1, 3, 4, 5]WHITE_KEY_NAMES = ["do", "re", "mi", "fa", "sol", "la", "si"]BLACK_KEY_NAMES = ["#do", "#re", "#fa", "#sol", "#la"]# ========== 加载音效 ==========WHITE_SOUNDS = ["c4.wav", "d4.wav", "e4.wav", "f4.wav", "g4.wav", "a4.wav", "b4.wav"]BLACK_SOUNDS = ["cs4.wav", "ds4.wav", "fs4.wav", "gs4.wav", "as4.wav"]print("正在加载音效...")try: white_sounds = [pygame.mixer.Sound(f"sounds/{f}") for f in WHITE_SOUNDS] black_sounds = [pygame.mixer.Sound(f"sounds/{f}") for f in BLACK_SOUNDS]print("音效加载完成!")except FileNotFoundError as e:print(f"错误:找不到音效文件 {e}")print("请确保sounds文件夹存在,且包含所有wav文件")# 创建空的声音列表,避免程序崩溃 white_sounds = [None] * WHITE_KEY_COUNT black_sounds = [None] * BLACK_KEY_COUNT# ========== 字体 ==========font = pygame.font.SysFont(None, 36)small_font = pygame.font.SysFont(None, 24)# ========== 游戏循环 ==========running = True# 记录哪个键被按下(用于显示效果)white_key_pressed = [False] * WHITE_KEY_COUNTblack_key_pressed = [False] * BLACK_KEY_COUNTwhile running:# ========== 事件处理 ==========for event in pygame.event.get():if event.type == pygame.QUIT: running = False# 鼠标按下事件elif event.type == pygame.MOUSEBUTTONDOWN: mouse_x, mouse_y = event.pos# 先检测白键 key_found = Falsefor i inrange(WHITE_KEY_COUNT): x = START_X + i * WHITE_KEY_WIDTH key_rect = pygame.Rect(x, START_Y, WHITE_KEY_WIDTH, WHITE_KEY_HEIGHT)if key_rect.collidepoint(mouse_x, mouse_y):print(f"🎵 白键 {WHITE_KEY_NAMES[i]}") white_key_pressed[i] = Trueif white_sounds[i]: white_sounds[i].play() key_found = Truebreak# 再检测黑键(如果白键没点到)ifnot key_found:for i inrange(BLACK_KEY_COUNT): white_key_index = BLACK_KEY_POSITIONS[i] white_key_x = START_X + white_key_index * WHITE_KEY_WIDTH black_key_x = white_key_x + WHITE_KEY_WIDTH - BLACK_KEY_WIDTH // 2 key_rect = pygame.Rect(black_key_x, START_Y, BLACK_KEY_WIDTH, BLACK_KEY_HEIGHT)if key_rect.collidepoint(mouse_x, mouse_y):print(f"🎵 黑键 {BLACK_KEY_NAMES[i]}") black_key_pressed[i] = Trueif black_sounds[i]: black_sounds[i].play()break# 鼠标松开事件(重置按键状态)elif event.type == pygame.MOUSEBUTTONUP: white_key_pressed = [False] * WHITE_KEY_COUNT black_key_pressed = [False] * BLACK_KEY_COUNT# ========== 绘制 ========== screen.fill(DARK_GRAY)# 绘制白键for i inrange(WHITE_KEY_COUNT): x = START_X + i * WHITE_KEY_WIDTH# 如果被按下,显示黄色,否则白色if white_key_pressed[i]: color = YELLOWelse: color = WHITE pygame.draw.rect(screen, color, (x, START_Y, WHITE_KEY_WIDTH, WHITE_KEY_HEIGHT)) pygame.draw.rect(screen, GRAY, (x, START_Y, WHITE_KEY_WIDTH, WHITE_KEY_HEIGHT), 1)# 音名 text = font.render(WHITE_KEY_NAMES[i], True, BLACK) text_x = x + (WHITE_KEY_WIDTH - text.get_width()) // 2 text_y = START_Y + WHITE_KEY_HEIGHT - 50 screen.blit(text, (text_x, text_y))# 绘制黑键for i inrange(BLACK_KEY_COUNT): white_key_index = BLACK_KEY_POSITIONS[i] white_key_x = START_X + white_key_index * WHITE_KEY_WIDTH black_key_x = white_key_x + WHITE_KEY_WIDTH - BLACK_KEY_WIDTH // 2# 如果被按下,显示深灰色,否则黑色if black_key_pressed[i]: color = (80, 80, 80)else: color = BLACK pygame.draw.rect(screen, color, (black_key_x, START_Y, BLACK_KEY_WIDTH, BLACK_KEY_HEIGHT)) pygame.draw.rect(screen, (100, 100, 100), (black_key_x, START_Y, BLACK_KEY_WIDTH, BLACK_KEY_HEIGHT), 1)# 音名 text = small_font.render(BLACK_KEY_NAMES[i], True, WHITE) text_x = black_key_x + (BLACK_KEY_WIDTH - text.get_width()) // 2 text_y = START_Y + BLACK_KEY_HEIGHT - 30 screen.blit(text, (text_x, text_y))# 分隔线for i inrange(1, WHITE_KEY_COUNT): x = START_X + i * WHITE_KEY_WIDTH pygame.draw.line(screen, BLACK, (x, START_Y), (x, START_Y + WHITE_KEY_HEIGHT), 2)# 提示文字 hint = small_font.render("点击琴键演奏", True, WHITE) screen.blit(hint, (SCREEN_WIDTH // 2 - hint.get_width() // 2, 100)) pygame.display.flip()pygame.quit()
🔍 代码重点解析
1. 声音加载错误处理
try: white_sounds = [pygame.mixer.Sound(f"sounds/{f}") for f in WHITE_SOUNDS]except FileNotFoundError as e:print(f"错误:找不到音效文件 {e}") white_sounds = [None] * WHITE_KEY_COUNT
为什么需要try-except?
如果sounds文件夹不存在或文件缺失,程序会崩溃。用try-except可以优雅地处理错误。
2. 按键状态记录
white_key_pressed = [False] * WHITE_KEY_COUNT
这是一个布尔列表,记录每个键是否被按下。用于:
3. 点击检测顺序
# 先检测白键for i inrange(WHITE_KEY_COUNT): ...if key_rect.collidepoint(mouse_x, mouse_y): ...break# 找到就退出# 再检测黑键ifnot key_found: # 只有没点到白键时才检测黑键for i inrange(BLACK_KEY_COUNT): ...
关键点:
4. 视觉反馈
if white_key_pressed[i]: color = YELLOW # 按下时变黄else: color = WHITE # 平时是白色
让用户知道哪个键被按下了。
🎨 动手改造
改造1:添加音量控制
# 设置音量(0.0到1.0)white_sounds[0].set_volume(0.5) # 50%音量
改造2:循环播放
# -1表示无限循环white_sounds[0].play(-1)# 停止播放white_sounds[0].stop()
改造3:淡入效果
# 播放时淡入500毫秒white_sounds[0].play(fade_ms=500)
改造4:播放背景音乐
# 加载背景音乐pygame.mixer.music.load("sounds/background.mp3")pygame.mixer.music.play(-1) # 循环播放pygame.mixer.music.set_volume(0.3) # 音量30%
❓ 常见问题
Q1:没有声音
检查清单:
Q2:声音延迟
解决:减小buffer大小
pygame.mixer.init(buffer=512) # 默认4096,改小减少延迟
Q3:点击没反应
检查:
Q4:同时按多个键只有一声
原因:Pygame默认单声道
解决:使用多通道
# 设置8个通道pygame.mixer.set_num_channels(8)# 播放时指定通道pygame.mixer.Channel(0).play(sound1)pygame.mixer.Channel(1).play(sound2)
Q5:声音文件格式不支持
解决:转换为WAV格式
可以用格式工厂、Audacity等工具转换。
🎯 课后练习
练习1:录制自己的音效
用手机录制12个音,转换为WAV格式,替换默认音效。
练习2:添加按键动画
按键按下时,键向下移动几个像素,模拟真实钢琴效果。
练习3:显示按键历史
在屏幕上方显示最近按下的5个音。
练习4(挑战):实现录音功能
记录用户弹奏的音符和时长,保存到文件,可以回放。
🎬 下篇预告
第7篇:《键盘弹钢琴——把手放键盘上》
明天我们将:
不用鼠标,直接上手弹!
📦 本课资源
pygame.mixer.Sound() - 加载声音rect.collidepoint() - 点是否在矩形内pygame.mouse.get_pos() - 获取鼠标位置
🎉 恭喜你完成第6课!
今天你已经:
你的钢琴会"说话"了!明天学习用键盘弹奏!
本系列文章面向零基础中小学生,每篇都有详细步骤和配图说明。遇到问题欢迎在评论区留言!关注回复“钢琴”免费获取源代码下载地址。