当前位置:首页>python>【Python钢琴节奏大师·第7讲】键盘映射篇-从鼠标点击到双手弹奏的进化

【Python钢琴节奏大师·第7讲】键盘映射篇-从鼠标点击到双手弹奏的进化

  • 2026-10-05 00:38:20
【Python钢琴节奏大师·第7讲】键盘映射篇-从鼠标点击到双手弹奏的进化

🎹 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)          ↑        ↑               ↑        ↑        ↑       中指      无名指           中指     无名指    小指

为什么要这样设计?

  1. A-J 连续:左手自然放置,手指不别扭
  2. W、E 在 S、D 上方:黑键在白键上方,符合直觉
  3. T、Y、U 在 F、G、H 上方:另一组黑键
  4. 跳过 R:因为 mi-fa 之间没有黑键!

📝 第二步:键盘事件基础

键盘事件类型

事件类型
含义
用途
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为每个按键定义了常量:

按键
常量
值
A
pygame.K_a
97
S
pygame.K_s
115
D
pygame.K_d
100
F
pygame.K_f
102
G
pygame.K_g
103
H
pygame.K_h
104
J
pygame.K_j
106
W
pygame.K_w
119
E
pygame.K_e
101
T
pygame.K_t
116
Y
pygame.K_y
121
U
pygame.K_u
117

判断特定按键

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?

  1. 视觉反馈:按键松开时,琴键恢复原色
  2. 状态管理:记录哪些键正在被按下
  3. 后续功能:为"长按持续发声"做准备

实现按键松开

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:按了键盘没反应

检查清单:

  1. 窗口是否获得焦点?(点击一下窗口)
  2. 是否用了中文输入法?(切换到英文)
  3. 按键是否大写锁定?(试试小写)
  4. 代码中是否正确定义了映射?

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
  • 核心概念:
    • 键盘事件(KEYDOWN/KEYUP)
    • 字典映射
    • 多输入方式(键盘+鼠标)
  • 关键函数:
    • pygame.key.name() - 获取按键名称
    • key in dict - 检查键是否在字典中
    • dict[key] - 获取对应的值

🎉 恭喜你完成第7课!

今天你已经:

  • ✅ 学会了键盘事件处理
  • ✅ 建立了键位映射系统
  • ✅ 实现了键盘弹奏钢琴
  • ✅ 支持同时按多个键(和弦)

现在你可以像弹钢琴一样,用双手在键盘上演奏了!明天进入节奏游戏模式!


本系列文章面向零基础中小学生,每篇都有详细步骤和配图说明。遇到问题欢迎在评论区留言!关注回复“钢琴”免费获取源代码下载地址。

最新文章

随机文章