🎹 Python钢琴节奏大师 · 第7章
系列:Python钢琴节奏大师:从零到游戏开发实战[1]章节:第7章 / 共12章标题:键盘映射篇-从鼠标点击到双手弹奏的进化上一章:第6章-声音交互篇[2]下一章:第8章-节奏游戏篇[3]
键盘弹钢琴——把手放键盘上
今日目标:学习键盘事件处理,将电脑键盘按键映射到钢琴键,实现用键盘弹奏,支持同时按多个键。
💡 本章核心:用键盘弹钢琴,实现真正的弹奏体验
🎹 效果预览
完成本篇学习后,你可以:
- 用电脑键盘
A S D F G H J 弹奏白键 do re mi fa sol la si - 用
W E T Y U 弹奏黑键 #do #re #fa #sol #la
┌────────────────────────────────────────────────────────────┐│ ││ 键盘布局: ││ ││ W E T Y U ││ #do #re #fa #sol #la ││ ││ A S D F G H J ││ do re mi fa sol la si ││ ││ ┌───┬───┬───┐ ┌───┬───┬───┬───┐ ││ │ │ ■ │ │ │ │ ■ │ ■ │ │ ││ │ A │ W │ S │ │ D │ E │ F │ G │ ← 按键盘对应键! ││ │ │ │ │ │ │ │ │ │ ││ │do │#re│re │ │mi │#fa│fa │sol│ ││ └───┴───┴───┘ └───┴───┴───┴───┘ ││ ││ 🎹 把左手放在键盘上,开始演奏! 🎹 │└────────────────────────────────────────────────────────────┘
🤔 为什么要用键盘弹钢琴?
鼠标 vs 键盘
真实钢琴 vs 电脑键盘
真实钢琴有88个键,电脑键盘只有几十个键,但我们只用一个八度(12个键)来演示,完全够用!
📝 第一步:设计键盘布局
键位映射设计
我们把左手自然放在键盘上,让每个手指都能轻松按到:
左手小指 无名指 中指 食指 食指 ↓ ↓ ↓ ↓ ↓ A S D F G H J do re mi fa sol la si (C4) (D4) (E4) (F4) (G4) (A4) (B4) W E T Y U #do #re #fa #sol #la (C#4) (D#4) (F#4) (G#4) (A#4) ↑ ↑ ↑ ↑ ↑ 中指 无名指 中指 无名指 小指
为什么要这样设计?
- W、E 在 S、D 上方:黑键在白键上方,符合直觉
📝 第二步:键盘事件基础
键盘事件类型
| | |
|---|
pygame.KEYDOWN | | |
pygame.KEYUP | | |
检测键盘事件
for event in pygame.event.get():if event.type == pygame.QUIT: running = False# 按键按下elif event.type == pygame.KEYDOWN:print(f"按下了按键,编号:{event.key}")print(f"按键名称:{pygame.key.name(event.key)}")# 按键松开elif event.type == pygame.KEYUP:print(f"松开了按键:{pygame.key.name(event.key)}")
常用按键的常量
Pygame为每个按键定义了常量:
| | |
|---|
| pygame.K_a | |
| pygame.K_s | |
| pygame.K_d | |
| pygame.K_f | |
| pygame.K_g | |
| pygame.K_h | |
| pygame.K_j | |
| pygame.K_w | |
| pygame.K_e | |
| pygame.K_t | |
| pygame.K_y | |
| pygame.K_u | |
判断特定按键
if event.type == pygame.KEYDOWN:if event.key == pygame.K_a:print("按了A键!")# 播放 doelif event.key == pygame.K_s:print("按了S键!")# 播放 re
📝 第三步:建立键位映射
用字典存储映射关系
字典是Python的一种数据结构,用 键:值 的形式存储数据。
# 白键映射:键盘按键 -> 白键编号WHITE_KEY_MAPPING = { pygame.K_a: 0, # A -> do (0) pygame.K_s: 1, # S -> re (1) pygame.K_d: 2, # D -> mi (2) pygame.K_f: 3, # F -> fa (3) pygame.K_g: 4, # G -> sol (4) pygame.K_h: 5, # H -> la (5) pygame.K_j: 6, # J -> si (6)}# 黑键映射:键盘按键 -> 黑键编号BLACK_KEY_MAPPING = { pygame.K_w: 0, # W -> #do (0) pygame.K_e: 1, # E -> #re (1) pygame.K_t: 2, # T -> #fa (2) pygame.K_y: 3, # Y -> #sol (3) pygame.K_u: 4, # U -> #la (4)}
使用映射
if event.type == pygame.KEYDOWN:# 检查是否是白键if event.key in WHITE_KEY_MAPPING: key_index = WHITE_KEY_MAPPING[event.key]print(f"白键 {WHITE_KEY_NAMES[key_index]}") white_sounds[key_index].play() white_key_pressed[key_index] = True# 检查是否是黑键elif event.key in BLACK_KEY_MAPPING: key_index = BLACK_KEY_MAPPING[event.key]print(f"黑键 {BLACK_KEY_NAMES[key_index]}") black_sounds[key_index].play() black_key_pressed[key_index] = True
📝 第四步:处理按键松开
为什么要处理 KEYUP?
实现按键松开
elif event.type == pygame.KEYUP:# 检查是否是白键if event.key in WHITE_KEY_MAPPING: key_index = WHITE_KEY_MAPPING[event.key] white_key_pressed[key_index] = False# 检查是否是黑键elif event.key in BLACK_KEY_MAPPING: key_index = BLACK_KEY_MAPPING[event.key] black_key_pressed[key_index] = False
📝 第五步:显示键盘提示
在琴键上显示对应按键
让用户知道每个琴键对应哪个键盘按键。
# 白键对应的键盘字符WHITE_KEY_CHARS = ["A", "S", "D", "F", "G", "H", "J"]# 黑键对应的键盘字符BLACK_KEY_CHARS = ["W", "E", "T", "Y", "U"]
在绘制时显示
# 绘制白键时,显示键盘字符char_text = small_font.render(WHITE_KEY_CHARS[i], True, GRAY)char_x = x + (WHITE_KEY_WIDTH - char_text.get_width()) // 2char_y = START_Y + 20# 顶部screen.blit(char_text, (char_x, char_y))
🎯 完整代码
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("键盘钢琴 - 用A-J弹奏")# ========== 颜色定义 ==========BLACK = (0, 0, 0)WHITE = (255, 255, 255)GRAY = (200, 200, 200)DARK_GRAY = (50, 50, 50)YELLOW = (255, 255, 0)LIGHT_BLUE = (100, 150, 255)# ========== 钢琴键参数 ==========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_KEY_CHARS = ["A", "S", "D", "F", "G", "H", "J"]BLACK_KEY_CHARS = ["W", "E", "T", "Y", "U"]# ========== 键盘映射字典 ==========WHITE_KEY_MAPPING = { pygame.K_a: 0, pygame.K_s: 1, pygame.K_d: 2, pygame.K_f: 3, pygame.K_g: 4, pygame.K_h: 5, pygame.K_j: 6,}BLACK_KEY_MAPPING = { pygame.K_w: 0, pygame.K_e: 1, pygame.K_t: 2, pygame.K_y: 3, pygame.K_u: 4,}# ========== 加载音效 ==========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}") 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)title_font = pygame.font.SysFont(None, 48)# ========== 游戏状态 ==========white_key_pressed = [False] * WHITE_KEY_COUNTblack_key_pressed = [False] * BLACK_KEY_COUNT# ========== 游戏循环 ==========running = Truewhile running:# ========== 事件处理 ==========for event in pygame.event.get():if event.type == pygame.QUIT: running = False# ========== 键盘按下 ==========elif event.type == pygame.KEYDOWN:# 白键if event.key in WHITE_KEY_MAPPING: key_index = WHITE_KEY_MAPPING[event.key] white_key_pressed[key_index] = Trueif white_sounds[key_index]: white_sounds[key_index].play()print(f"🎹 键盘 {WHITE_KEY_CHARS[key_index]} → 白键 {WHITE_KEY_NAMES[key_index]}")# 黑键elif event.key in BLACK_KEY_MAPPING: key_index = BLACK_KEY_MAPPING[event.key] black_key_pressed[key_index] = Trueif black_sounds[key_index]: black_sounds[key_index].play()print(f"🎹 键盘 {BLACK_KEY_CHARS[key_index]} → 黑键 {BLACK_KEY_NAMES[key_index]}")# ========== 键盘松开 ==========elif event.type == pygame.KEYUP:# 白键if event.key in WHITE_KEY_MAPPING: key_index = WHITE_KEY_MAPPING[event.key] white_key_pressed[key_index] = False# 黑键elif event.key in BLACK_KEY_MAPPING: key_index = BLACK_KEY_MAPPING[event.key] black_key_pressed[key_index] = 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): 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): black_key_pressed[i] = Trueif black_sounds[i]: black_sounds[i].play()breakelif event.type == pygame.MOUSEBUTTONUP: white_key_pressed = [False] * WHITE_KEY_COUNT black_key_pressed = [False] * BLACK_KEY_COUNT# ========== 绘制 ========== screen.fill(DARK_GRAY)# 标题 title = title_font.render("键盘钢琴", True, WHITE) screen.blit(title, (SCREEN_WIDTH // 2 - title.get_width() // 2, 30))# 提示文字 hint1 = small_font.render("白键:A S D F G H J", True, LIGHT_BLUE) hint2 = small_font.render("黑键: W E T Y U", True, LIGHT_BLUE) screen.blit(hint1, (SCREEN_WIDTH // 2 - hint1.get_width() // 2, 90)) screen.blit(hint2, (SCREEN_WIDTH // 2 - hint2.get_width() // 2, 110))# 绘制白键for i inrange(WHITE_KEY_COUNT): x = START_X + i * WHITE_KEY_WIDTH# 颜色:按下时黄色,否则白色if white_key_pressed[i]: color = YELLOW border_color = (255, 200, 0)else: color = WHITE border_color = GRAY pygame.draw.rect(screen, color, (x, START_Y, WHITE_KEY_WIDTH, WHITE_KEY_HEIGHT)) pygame.draw.rect(screen, border_color, (x, START_Y, WHITE_KEY_WIDTH, WHITE_KEY_HEIGHT), 2)# 键盘字符(顶部) char_text = small_font.render(WHITE_KEY_CHARS[i], True, DARK_GRAY) char_x = x + (WHITE_KEY_WIDTH - char_text.get_width()) // 2 char_y = START_Y + 15 screen.blit(char_text, (char_x, char_y))# 音名(底部) name_text = font.render(WHITE_KEY_NAMES[i], True, BLACK) name_x = x + (WHITE_KEY_WIDTH - name_text.get_width()) // 2 name_y = START_Y + WHITE_KEY_HEIGHT - 50 screen.blit(name_text, (name_x, name_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) border_color = (150, 150, 150)else: color = BLACK border_color = (100, 100, 100) pygame.draw.rect(screen, color, (black_key_x, START_Y, BLACK_KEY_WIDTH, BLACK_KEY_HEIGHT)) pygame.draw.rect(screen, border_color, (black_key_x, START_Y, BLACK_KEY_WIDTH, BLACK_KEY_HEIGHT), 2)# 键盘字符(顶部) char_text = small_font.render(BLACK_KEY_CHARS[i], True, WHITE) char_x = black_key_x + (BLACK_KEY_WIDTH - char_text.get_width()) // 2 char_y = START_Y + 15 screen.blit(char_text, (char_x, char_y))# 音名(底部) name_text = small_font.render(BLACK_KEY_NAMES[i], True, WHITE) name_x = black_key_x + (BLACK_KEY_WIDTH - name_text.get_width()) // 2 name_y = START_Y + BLACK_KEY_HEIGHT - 30 screen.blit(name_text, (name_x, name_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) pygame.display.flip()pygame.quit()
🔍 代码重点解析
1. 字典映射
WHITE_KEY_MAPPING = { pygame.K_a: 0, pygame.K_s: 1, ...}
为什么用字典?
2. in 关键字
if event.key in WHITE_KEY_MAPPING:
检查字典中是否存在某个键。如果按下的键盘按键在映射字典里,就处理它。
3. 同时支持键盘和鼠标
代码中保留了鼠标点击功能,所以你可以:
4. 按键状态管理
white_key_pressed = [False] * WHITE_KEY_COUNT
用列表记录每个键的状态,实现:
🎨 动手改造
改造1:添加更多键位
扩展到14个白键(两个八度):
# 第二组(高音)WHITE_KEY_MAPPING_2 = { pygame.K_z: 7, # 高音 do pygame.K_x: 8, # 高音 re pygame.K_c: 9, # 高音 mi ...}# 合并两个字典WHITE_KEY_MAPPING.update(WHITE_KEY_MAPPING_2)
改造2:按键提示闪烁
新用户不知道键位?添加提示动画:
# 显示"按 A 键弹奏 do"的提示if frame_count < 60: # 前1秒显示 hint = font.render("按 A 键开始", True, YELLOW) screen.blit(hint, (300, 150))
改造3:弹奏记录
记录用户弹了什么:
played_notes = [] # 记录弹奏的音符if event.type == pygame.KEYDOWN:if event.key in WHITE_KEY_MAPPING: key_index = WHITE_KEY_MAPPING[event.key] played_notes.append(WHITE_KEY_NAMES[key_index])print(f"已弹奏:{' '.join(played_notes)}")
改造4:简易乐谱模式
显示要弹的音符,让用户跟着弹:
song = ["do", "re", "mi", "do", "do", "re", "mi", "do"] # 小星星current_note = 0# 显示当前要弹的音符hint = font.render(f"下一个:{song[current_note]}", True, YELLOW)screen.blit(hint, (350, 150))# 弹对了就下一个if WHITE_KEY_NAMES[key_index] == song[current_note]: current_note += 1
❓ 常见问题
Q1:按了键盘没反应
检查清单:
Q2:同时按多个键,只有一个响
原因:默认声音通道不够
解决:增加通道数
pygame.mixer.set_num_channels(16) # 默认8,改成16
Q3:按键松开后颜色没变
检查:KEYUP 事件是否正确处理?
elif event.type == pygame.KEYUP:if event.key in WHITE_KEY_MAPPING: key_index = WHITE_KEY_MAPPING[event.key] white_key_pressed[key_index] = False# 记得改回False!
Q4:某些按键无法使用
原因:某些键被系统占用(如F1-F12、Win键等)
解决:换其他键,推荐用字母键和数字键
Q5:键盘和鼠标冲突
现象:用键盘时鼠标点击失效
解决:检查事件处理逻辑,确保两者都能触发
🎯 课后练习
练习1:双手弹奏
右手用键盘上半部分(Y-U-I-O-P),左手用下半部分(A-S-D-F-G),实现双手弹奏。
练习2:速度测试
显示用户每秒能按多少个键,测试手速。
练习3:和弦提示
显示常用和弦的按键组合:
C和弦:A + D + G(do + mi + sol)G和弦:G + J + W(sol + si + re)
练习4(挑战):实现滑音
按住一个键的同时按另一个键,实现滑音效果。
🎬 下篇预告
第8篇:《音符下落——做个"节奏大师"》
明天我们将:
从钢琴模拟器变成真正的节奏游戏!
📦 本课资源
- 完整代码:
07_keyboard_piano.py pygame.key.name() - 获取按键名称
🎉 恭喜你完成第7课!
今天你已经:
现在你可以像弹钢琴一样,用双手在键盘上演奏了!明天进入节奏游戏模式!
本系列文章面向零基础中小学生,每篇都有详细步骤和配图说明。遇到问题欢迎在评论区留言!关注回复“钢琴”免费获取源代码下载地址。