Python Pygame 钢琴块游戏:从零开始构建桌面游戏教程
引言
欢迎来到这份关于使用 Python 和 Pygame 构建桌面“钢琴块”游戏的教程!这款游戏旨在完全复刻经典的钢琴块玩法,让您在享受编程乐趣的同时,锻炼反应速度和集中注意力。无论您是 Python 游戏开发初学者,还是希望通过实际项目巩固 Pygame 技能,本项目都将为您提供一个实践的绝佳平台。
在本游戏中,黑色方块会从屏幕顶部不断落下,您的目标是快速点击这些黑色方块以得分,同时必须避免点击白色方块或错过任何一个黑色方块。一旦出现失误,游戏便会立即结束。这种引人入胜且富有挑战性的玩法,将是您练习和巩固基本编程概念、提升 Python 游戏开发技能的宝贵练习。
通过本教程,您不仅将学会构建一个激动人心的节奏游戏,更将深入理解游戏开发的核心概念,包括游戏循环、事件处理、图形渲染、碰撞检测、状态管理和计分系统。
学习目标 (What You’ll Learn)
完成本教程后,您将能够:
- 掌握 Pygame 基础: 使用 Pygame 初始化窗口、加载资源、处理颜色、绘制图形和文本。
- 理解游戏循环: 实现并管理游戏的主循环,包括事件处理、游戏状态更新和图形渲染。
- 实现游戏机制: 学习方块的生成、下落、用户交互(点击)、得分和游戏结束逻辑。
- 处理用户输入: 通过鼠标点击或键盘输入实现对游戏元素的响应。
- 管理游戏状态: 在开始、游戏中和游戏结束等不同状态之间进行切换。
- 实现计分系统: 跟踪玩家分数,并显示当前分数和最高分数。
- 处理难度递增: 实现游戏速度随时间逐渐增加的逻辑。
- 代码组织: 学习如何将游戏代码模块化,提高可维护性和可读性。
- 调试与故障排除: 掌握游戏开发中常见的调试技巧,并学会分析和解决程序中遇到的问题。
前置知识 (Prerequisites)
为确保您能顺利完成本教程,建议您具备以下基础:
- Python 基础语法: 对 Python 变量、数据类型、条件语句、循环、函数、列表、字典等有基本了解。
- 面向对象编程 (OOP) 概念: 初步理解类、对象、方法和属性的概念。
- 无需 Pygame 经验: 本教程将从 Pygame 的基本用法开始讲解。
- 基本的编程思维: 具备解决问题、耐心阅读和实践代码的热情。
1. 项目概览与核心技术栈
1.1 关于钢琴块游戏
这是一个激动人心的节奏游戏,考验玩家的速度、准确性和反应能力。游戏中,黑色块会从屏幕顶部不断落下,玩家必须快速按下相应的按键才能击中它们,以免它们消失。错过块或按错键都会结束游戏,这无疑增加了游戏的挑战性。该项目使用 Python 的 Pygame 库开发,拥有流畅的动画、灵敏的操控和引人入胜的游戏循环,让玩家保持专注并获得乐趣。这是一个练习事件处理、碰撞检测和游戏循环等编程概念的好方法。
1.2 核心信息与技术栈
- 语言 (Language): Python
- 游戏库 (Game Library): Pygame (Python 的游戏开发库)
- 开发环境 (Coding Tool): 内置 Python IDLE 或任何支持 Python 的 IDE (如 VS Code, PyCharm)
- 应用类型 (Type): 桌面应用程序 / 游戏 (Desktop Application / Game)
- 数据存储 (Database Used): 无 (本项目将分数等数据存储在内存中)
- 目的 (Purpose): 仅供教育目的使用。
1.3 项目文件结构概览
对于这样一个相对简单的游戏,我们可以将所有代码放在一个文件中,以保持简洁。
piano_tiles_game/
├── piano_tiles.py # 应用程序的入口点和所有游戏逻辑
└── README.md # 项目说明 (可选)
└── SimHei.ttf # 【可选】一个中文字体文件,如 SimHei.ttf 或其他,用于显示中文 (见安装指南)
1.4 Pygame 基础概念
Pygame 是一个 Python 模块,旨在帮助您编写视频游戏。它提供了一系列功能,使您能够轻松处理图形、声音、用户输入等。
- 初始化 (
pygame.init()): 启动 Pygame 模块。这是每个 Pygame 程序的第一步。 - 窗口/屏幕设置 (
pygame.display.set_mode()): 创建游戏的显示窗口,并设置其尺寸。 - 颜色 (RGB 元组): 在 Pygame 中,颜色通常用 (红, 绿, 蓝) 的元组表示,每个值介于 0 到 255 之间。例如,(255, 0, 0) 是红色,(0, 0, 0) 是黑色,(255, 255, 255) 是白色。
- 矩形 (
pygame.Rect): Pygame 中的一个重要对象,用于表示屏幕上的矩形区域。它不仅可以用于绘制矩形,还可以用于检测碰撞和管理位置。 - 事件处理 (
pygame.event.get()): 获取并处理用户输入(如鼠标点击、键盘按键)和系统事件(如窗口关闭)。 - 游戏循环 (
while True): 游戏的核心,不断重复执行以下步骤:处理输入 -> 更新游戏状态 -> 渲染图形。 - 绘图 (
screen.fill(),pygame.draw.rect()): 在屏幕上填充背景颜色,或绘制矩形等基本图形。 - 更新显示 (
pygame.display.flip()或pygame.display.update()): 将绘制的内容显示到屏幕上。 - 时钟/帧率控制 (
pygame.time.Clock): 用于控制游戏的帧率(FPS),确保游戏在不同机器上以一致的速度运行。 - 字体 (
pygame.font.Font): 用于在屏幕上渲染文本,例如显示分数或游戏提示。
1.5 游戏机制概述
该游戏的核心机制围绕着从屏幕顶部不断下落的黑色方块。
- 方块下落: 屏幕被逻辑地划分为多列,方块会在这些列中随机生成并匀速下落。
- 用户交互: 玩家通过鼠标点击或键盘按键来“击中”下落的黑色方块。
- 计分: 成功点击一个黑色方块,玩家得分增加。
- 游戏结束条件:
- 错过一个黑色方块,让它下落到屏幕底部。
- 点击了一个白色方块。
- 难度递增: 随着游戏进行,方块下落的速度会逐渐加快,增加游戏的挑战性。
2. 核心功能实现详解
现在,我们将逐步深入到项目的核心功能实现。本节将结合代码逻辑,详细讲解如何构建用户界面、实现游戏机制、处理交互和计分。
2.1 用户界面 (UI) 构建
游戏的 UI 相对简洁,主要由游戏窗口、不断下落的方块、屏幕上方的计分板以及游戏结束时的提示信息构成。
-
步骤 1: Pygame 初始化与窗口创建
- 首先调用
pygame.init()初始化所有 Pygame 模块。 - 使用
pygame.display.set_mode()创建一个固定大小的游戏窗口,并设置其尺寸。 - 定义游戏背景颜色,通常是黑色或深色。
- 首先调用
-
步骤 2: 绘制方块
- 游戏中的方块是基本的矩形,通过
pygame.draw.rect()函数绘制。 - 方块分为黑色和白色两种,通过不同的颜色值进行区分。
- 它们会从屏幕顶部生成,并随着游戏循环逐渐向下移动。
- 游戏中的方块是基本的矩形,通过
-
步骤 3: 计分板与文本显示
- 使用
pygame.font.Font()创建一个字体对象。 - 利用字体对象渲染当前分数和最高分数的文本。
- 这些文本会显示在游戏窗口的顶部,确保玩家能实时看到自己的得分。
- 使用
-
步骤 4: 游戏开始/结束界面
- 游戏开始前会显示一个欢迎界面,包含游戏标题和开始游戏的提示。
- 游戏结束后,会显示一个“游戏结束”界面,展示最终分数、最高分数以及重新开始游戏的选项。
**

**
2.2 游戏机制与逻辑
游戏的核心在于不断生成和管理下落的方块,并响应玩家的点击。
-
2.2.1 游戏状态管理
- 为了在“开始界面”、“游戏进行中”和“游戏结束”之间切换,我们需要定义不同的游戏状态(例如,使用字符串常量
START,PLAYING,GAME_OVER)。 - 游戏循环会根据当前状态执行不同的逻辑和渲染。
- 为了在“开始界面”、“游戏进行中”和“游戏结束”之间切换,我们需要定义不同的游戏状态(例如,使用字符串常量
-
2.2.2 方块生成与下落
- 定义一个
Tile类或字典结构来存储每个方块的信息:位置 (x, y)、颜色、宽度、高度。 - 在游戏进行中,定时在屏幕顶部的随机一列生成新的方块(确保每一行只有一个黑色方块)。
- 在每次游戏循环迭代中,更新所有方块的
y坐标,使其向下移动。
- 定义一个
-
2.2.3 用户交互与事件处理
- 在游戏循环中,通过
pygame.event.get()监听鼠标点击事件 (pygame.MOUSEBUTTONDOWN)。 - 当鼠标点击发生时,获取点击的
(x, y)坐标。 - 遍历当前屏幕上所有的方块,检查点击坐标是否与某个方块的
pygame.Rect区域发生重叠(碰撞检测)。
- 在游戏循环中,通过
-
2.2.4 碰撞检测与得分
- 如果玩家点击了一个方块:
- 如果点击的是黑色方块: 增加玩家分数,并将该方块标记为已点击(或移除),以防止重复点击。
- 如果点击的是白色方块: 游戏结束。
- 如果一个黑色方块下落到屏幕底部而没有被点击,游戏结束。
- 如果玩家点击了一个方块:
-
2.2.5 难度递增
- 随着游戏分数的增加或时间的推移,逐步提高方块的下落速度。这可以通过调整方块每次更新
y坐标的增量来实现。 - 例如,每达到一定的分数阈值,就将
tile_speed增加一个小的量。
- 随着游戏分数的增加或时间的推移,逐步提高方块的下落速度。这可以通过调整方块每次更新
2.3 计分与游戏结束
-
计分逻辑:
- 初始化
current_score为 0。 - 每次正确点击黑色方块时,
current_score加 1。 - 同时维护一个
high_score,记录玩家的最高得分(在当前会话中)。
- 初始化
-
游戏结束条件:
- 当
current_game_state变为GAME_OVER时,显示游戏结束界面。 - 如果
current_score大于high_score,则更新high_score。
- 当
-
游戏结束界面:
- 显示“游戏结束”标题、最终得分、最高得分。
- 提供一个“重新开始”按钮或提示,让玩家可以再次进行游戏。
**

**
3. 代码风格与最佳实践
良好的代码习惯是编写高质量、可维护软件的关键。
3.1 PEP 8 规范
遵循 Python 官方的代码风格指南 (PEP 8) 是编写易读、一致性代码的基础。
- 命名约定:
- 变量名和函数名: 使用
snake_case(例如tile_speed,handle_input)。 - 类名: 使用
CamelCase(例如PianoTilesGame)。 - 常量名: 使用全大写和下划线 (例如
SCREEN_WIDTH,BLACK_COLOR)。
- 变量名和函数名: 使用
- 代码布局: 每行代码长度不超过 79 字符;适当使用空行分隔代码块;使用 4 个空格作为缩进。
- 导入: 将导入语句放在文件顶部,通常按照标准库、第三方库的顺序排列。
3.2 模块化与类设计
- 虽然是单文件游戏,但可以将不同职责的代码封装到类和函数中。
- 例如,创建一个
PianoTilesGame类来封装游戏的所有逻辑(初始化、事件处理、更新、渲染等)。 - 内部函数和变量使用
_前缀表示私有成员,增强代码的可读性和结构性。
3.3 使用常量
将屏幕尺寸、颜色、方块尺寸、初始速度、帧率等固定值定义为常量(全大写),而不是直接在代码中硬编码。这样做的好处是:
- 提高可读性: 常量名比裸露的数字更具描述性。
- 易于修改: 当需要调整这些值时,只需修改常量定义处即可,无需搜索整个代码。
3.4 错误处理与健壮性
- Pygame 初始化错误: Pygame 初始化时可能失败(例如,缺乏必要的图形驱动)。使用
try-except或pygame.quit()确保程序能优雅地退出。 - 防御性编程: 考虑用户可能进行的意外操作,例如快速连击或在不合时宜的时候点击,并确保游戏逻辑能够正确处理。
3.5 注释和 Docstrings
- 为复杂的逻辑块、不明显的算法和重要的数据结构添加注释。
- 为类和函数添加 Docstrings (文档字符串),解释它们的功能、参数、返回值。
完整项目代码
为了方便您自行保存、上传和分享,以下是本项目的完整 Python 代码文件。请在您的电脑上创建一个新文件夹(例如 piano_tiles_game),并将以下代码块的内容保存为 piano_tiles.py 文件。
piano_tiles.py (已修复中文字体乱码问题)
import pygame
import random
import sys
import os # 导入 os 模块以处理文件路径
# --- 常量 ---
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 600
NUM_LANES = 4
TILE_HEIGHT = SCREEN_HEIGHT // 4 # 每个方块的初始高度,占屏幕高度的 1/4
TILE_WIDTH = SCREEN_WIDTH // NUM_LANES
# 颜色
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (100, 100, 100)
LIGHT_GRAY = (200, 200, 200)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
# 游戏状态
GAME_STATE_START = 0
GAME_STATE_PLAYING = 1
GAME_STATE_GAME_OVER = 2
# 初始游戏速度 (每帧移动像素)
INITIAL_TILE_SPEED = 3
SPEED_INCREASE_INTERVAL = 10 # 每增加 10 分提高一次速度
SPEED_INCREASE_AMOUNT = 0.5 # 每次速度增加量
# --- 方块类 ---
class Tile(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, color):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.color = color
self.is_black = (color == BLACK)
self.clicked = False # 用于防止对同一个方块重复点击
def update(self, speed):
self.rect.y += speed
# --- 钢琴块游戏类 ---
class PianoTilesGame:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Python 钢琴块") # 设置窗口标题为中文
self.clock = pygame.time.Clock()
self.running = True
# --- 字体加载改进,支持中文字符 ---
font_path = None
# 尝试查找系统中的常见中文字体 (主要针对 Windows 系统)
if sys.platform == "win32":
potential_system_fonts = ["SimHei", "Microsoft YaHei", "Arial Unicode MS"]
for f_name in potential_system_fonts:
try:
matched_font_path = pygame.font.match_font(f_name, bold=False, italic=False)
if matched_font_path:
font_path = matched_font_path
# print(f"已加载系统字体: {f_name} from {matched_font_path}") # 调试信息
break
except Exception:
continue
# 如果系统字体未找到,尝试加载程序目录下的中文字体文件
bundled_font_name = "SimHei.ttf" # 假设中文字体文件名为 SimHei.ttf
bundled_font_path = os.path.join(os.path.dirname(__file__), bundled_font_name)
if font_path is None and os.path.exists(bundled_font_path):
font_path = bundled_font_path
# print(f"已加载捆绑字体: {bundled_font_path}") # 调试信息
if font_path is None:
print("警告: 未找到中文支持字体。请确保系统安装有中文字体,或将 'SimHei.ttf' 等中文字体文件放入游戏目录。将使用默认字体,可能显示乱码。")
# 如果都没有找到,fallback 到 Pygame 默认字体 (可能会是乱码)
# 使用加载到的字体路径,或者 None (表示使用默认字体)
self.font_large = pygame.font.Font(font_path, 74)
self.font_medium = pygame.font.Font(font_path, 50)
self.font_small = pygame.font.Font(font_path, 36)
# --- 字体加载改进结束 ---
self.all_tiles = pygame.sprite.Group()
self.game_state = GAME_STATE_START
self.score = 0
self.high_score = 0 # 当前会话的最高分
self.tile_speed = INITIAL_TILE_SPEED
self._reset_game()
def _reset_game(self):
"""重置游戏变量以开始新一轮游戏。"""
self.score = 0
self.tile_speed = INITIAL_TILE_SPEED
self.all_tiles.empty()
self._generate_initial_tiles()
self.missed_black_tile = False
self.clicked_wrong_tile = False
def _generate_new_row(self):
"""生成一行新的方块,其中包含一个随机的黑色方块。"""
black_lane = random.randint(0, NUM_LANES - 1)
for i in range(NUM_LANES):
x = i * TILE_WIDTH
y = -TILE_HEIGHT # 新方块在屏幕顶部上方生成,以便向下坠落
color = BLACK if i == black_lane else WHITE
new_tile = Tile(x, y, TILE_WIDTH, TILE_HEIGHT, color)
self.all_tiles.add(new_tile)
def _generate_initial_tiles(self):
"""
修复后的初始方块生成逻辑。
生成足够数量的方块以填满屏幕,并确保它们在屏幕上方或刚好在屏幕内,不会立即触发游戏结束。
"""
self.all_tiles.empty() # 清除所有现有方块
# 需要生成多少行方块来覆盖屏幕,并预留一些行在屏幕上方以便平滑下落
num_visible_rows = SCREEN_HEIGHT // TILE_HEIGHT # 屏幕内可见的行数 (例如 600/150 = 4)
num_buffer_rows_above = 2 # 额外在屏幕上方预留的行数,用于方块下落缓冲
total_rows_to_generate = num_visible_rows + num_buffer_rows_above # 总共需要生成的行数 (例如 4+2=6)
for row_idx in range(total_rows_to_generate):
black_lane = random.randint(0, NUM_LANES - 1)
for i in range(NUM_LANES):
x = i * TILE_WIDTH
# 计算方块的 Y 坐标:
# 最顶部的行 (row_idx=0) 会在 -TILE_HEIGHT * num_buffer_rows_above 的位置 (例如 -150*2 = -300)
# 最底部的行 (row_idx=total_rows_to_generate-1) 会在 SCREEN_HEIGHT - TILE_HEIGHT 的位置 (例如 600-150 = 450)
# 这样确保初始状态下所有方块都在屏幕上方或屏幕内,且不会立即触发游戏结束条件
y = -TILE_HEIGHT * num_buffer_rows_above + (row_idx * TILE_HEIGHT)
color = BLACK if i == black_lane else WHITE
new_tile = Tile(x, y, TILE_WIDTH, TILE_HEIGHT, color)
self.all_tiles.add(new_tile)
def _handle_input(self):
"""处理所有 Pygame 事件 (鼠标点击, 退出)。"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
if self.game_state == GAME_STATE_START:
if event.type == pygame.MOUSEBUTTONDOWN:
self.game_state = GAME_STATE_PLAYING
self._reset_game() # 在开始游戏前重置游戏状态和方块
elif self.game_state == GAME_STATE_PLAYING:
if event.type == pygame.MOUSEBUTTONDOWN:
clicked_pos = event.pos # 鼠标点击的 (x, y) 坐标
self._check_tile_click(clicked_pos)
elif self.game_state == GAME_STATE_GAME_OVER:
if event.type == pygame.MOUSEBUTTONDOWN:
# 检查是否点击了重新开始按钮
mouse_x, mouse_y = event.pos
button_width = 150
button_height = 50
button_x = (SCREEN_WIDTH - button_width) // 2
button_y = (SCREEN_HEIGHT - button_height) // 2 + 100 # 按钮位于分数下方
if button_x <= mouse_x <= button_x + button_width and \
button_y <= mouse_y <= button_y + button_height:
self.game_state = GAME_STATE_PLAYING
self._reset_game() # 重新开始游戏
def _check_tile_click(self, clicked_pos):
"""
修复后的点击检测逻辑。
检查是否点击了方块,并更新分数或游戏状态。
移除了之前限制点击区域的 `if tile.rect.y > SCREEN_HEIGHT // 2:` 条件。
"""
# 找出所有与点击位置碰撞的可见方块
candidate_tiles = []
for tile in self.all_tiles:
# 只考虑仍在屏幕上方的方块 (避免点击已落出屏幕的方块)
if tile.rect.collidepoint(clicked_pos) and tile.rect.y < SCREEN_HEIGHT:
candidate_tiles.append(tile)
if not candidate_tiles: # 如果点击了空白区域,不触发任何操作
return
clicked_black_tile = None
clicked_white_tile = None
# 遍历所有被点击的方块,找出最底部(最“活跃”)的未点击黑色方块,或任意白色方块
for tile in candidate_tiles:
if tile.is_black and not tile.clicked:
# 找到最底部的未点击黑色方块 (通常是玩家应该点击的目标)
if clicked_black_tile is None or tile.rect.y > clicked_black_tile.rect.y:
clicked_black_tile = tile
elif not tile.is_black:
# 如果点击了白色方块,则这是一个错误的点击
clicked_white_tile = tile
if clicked_black_tile: # 如果点击了一个有效的黑色方块
self.score += 1
clicked_black_tile.clicked = True
clicked_black_tile.image.fill(GRAY) # 改变颜色表示已被点击
self.all_tiles.remove(clicked_black_tile) # 移除已被点击的方块
# 增加速度
if self.score > 0 and self.score % SPEED_INCREASE_INTERVAL == 0:
self.tile_speed += SPEED_INCREASE_AMOUNT
elif clicked_white_tile: # 如果点击了白色方块,游戏结束
self.clicked_wrong_tile = True
self.game_state = GAME_STATE_GAME_OVER
else: # 如果点击了某个方块,但它不是未点击的黑色方块 (例如点击了已变灰的黑色方块),也视为错误点击
self.clicked_wrong_tile = True
self.game_state = GAME_STATE_GAME_OVER
def _check_and_generate_rows(self):
"""
移除屏幕外的方块,并在需要时生成新的方块行。
"""
tiles_to_remove = []
for tile in self.all_tiles:
# 检查是否有黑色方块越过屏幕底部
if tile.rect.y >= SCREEN_HEIGHT:
if tile.is_black and not tile.clicked: # 黑色方块未点击就到底部,游戏结束
self.missed_black_tile = True
self.game_state = GAME_STATE_GAME_OVER
tiles_to_remove.append(tile) # 将越过屏幕的方块标记为待移除
# 移除已标记的方块
for tile in tiles_to_remove:
self.all_tiles.remove(tile)
# 检查是否需要生成新的行 (当最上方的方块行移动到一定位置时)
# 获取当前所有方块中最高的 Y 坐标
top_most_y = min([t.rect.y for t in self.all_tiles]) if self.all_tiles else 0
# 如果最高的方块 Y 坐标已经大于等于 0 (即最高方块已经进入屏幕),
# 则在屏幕上方生成一行新方块,以保持连续性
if top_most_y >= 0:
self._generate_new_row()
def _update_game_state(self):
"""根据当前游戏状态更新游戏逻辑。"""
if self.game_state == GAME_STATE_PLAYING:
self.all_tiles.update(self.tile_speed) # 更新所有方块的位置
self._check_and_generate_rows() # 检查并生成新的方块行
# 检查游戏是否结束
if self.missed_black_tile or self.clicked_wrong_tile:
self.game_state = GAME_STATE_GAME_OVER
if self.score > self.high_score:
self.high_score = self.score # 更新最高分
def _draw_elements(self):
"""在屏幕上绘制所有游戏元素。"""
self.screen.fill(LIGHT_GRAY) # 填充背景色
# 绘制分界线
for i in range(1, NUM_LANES):
pygame.draw.line(self.screen, GRAY, (i * TILE_WIDTH, 0), (i * TILE_WIDTH, SCREEN_HEIGHT), 2)
# 绘制所有方块
self.all_tiles.draw(self.screen)
# 绘制分数
score_text = self.font_small.render(f"分数: {self.score}", True, BLACK)
self.screen.blit(score_text, (10, 10))
high_score_text = self.font_small.render(f"最高分: {self.high_score}", True, BLACK)
self.screen.blit(high_score_text, (SCREEN_WIDTH - high_score_text.get_width() - 10, 10))
def _draw_start_screen(self):
"""绘制开始界面。"""
self.screen.fill(WHITE)
title_text = self.font_large.render("钢琴块", True, BLACK)
self.screen.blit(title_text, (SCREEN_WIDTH // 2 - title_text.get_width() // 2, SCREEN_HEIGHT // 4))
instructions_text = self.font_small.render("点击黑色方块得分,避免白色方块", True, BLACK)
self.screen.blit(instructions_text, (SCREEN_WIDTH // 2 - instructions_text.get_width() // 2, SCREEN_HEIGHT // 2))
start_text = self.font_medium.render("点击任意位置开始", True, RED)
self.screen.blit(start_text, (SCREEN_WIDTH // 2 - start_text.get_width() // 2, SCREEN_HEIGHT * 3 // 4))
def _draw_game_over_screen(self):
"""绘制游戏结束界面。"""
self.screen.fill(WHITE)
game_over_text = self.font_large.render("游戏结束", True, RED)
self.screen.blit(game_over_text, (SCREEN_WIDTH // 2 - game_over_text.get_width() // 2, SCREEN_HEIGHT // 4))
final_score_text = self.font_medium.render(f"最终分数: {self.score}", True, BLACK)
self.screen.blit(final_score_text, (SCREEN_WIDTH // 2 - final_score_text.get_width() // 2, SCREEN_HEIGHT // 2 - 50))
high_score_text = self.font_small.render(f"最高分: {self.high_score}", True, BLACK)
self.screen.blit(high_score_text, (SCREEN_WIDTH // 2 - high_score_text.get_width() // 2, SCREEN_HEIGHT // 2))
# 重新开始按钮
button_width = 150
button_height = 50
button_x = (SCREEN_WIDTH - button_width) // 2
button_y = (SCREEN_HEIGHT - button_height) // 2 + 100
pygame.draw.rect(self.screen, GREEN, (button_x, button_y, button_width, button_height))
restart_text = self.font_small.render("重新开始", True, WHITE)
self.screen.blit(restart_text, (button_x + (button_width - restart_text.get_width()) // 2,
button_y + (button_height - restart_text.get_height()) // 2))
def run(self):
"""主游戏循环。"""
while self.running:
self._handle_input()
if self.game_state == GAME_STATE_PLAYING:
self._update_game_state()
if self.game_state == GAME_STATE_START:
self._draw_start_screen()
elif self.game_state == GAME_STATE_PLAYING:
self._draw_elements()
elif self.game_state == GAME_STATE_GAME_OVER:
self._draw_game_over_screen()
pygame.display.flip() # 更新整个显示界面到屏幕
self.clock.tick(60) # 限制游戏帧率为 60 FPS
pygame.quit()
sys.exit()
if __name__ == "__main__":
game = PianoTilesGame()
game.run()
5. 安装与运行指南
5.1 环境搭建
-
Python 安装:
- 确保您的计算机上已安装 Python 3.6 或更高版本。
- Windows 用户: 访问 Python 官方网站 (https://www.python.org/downloads/) 下载并安装最新版本。安装时请勾选“Add Python to PATH”选项。
- macOS / Linux 用户: 通常预装了 Python,但建议通过 Homebrew (
brew install python3) 或包管理器 (sudo apt install python3) 安装最新版本。 - 验证安装: 打开命令行或终端,输入
python --version和python3 --version来检查 Python 版本。
-
Pygame 安装:
- Pygame 是一个第三方库,需要通过 pip 进行安装。打开命令行或终端,输入以下命令:
pip install pygame - 如果您的系统同时存在 Python 2 和 Python 3,请确保为 Python 3 安装 Pygame:
pip3 install pygame - 验证 Pygame: 在 Python 解释器中输入
import pygame。如果没有报错,则表示 Pygame 已正确安装。
- Pygame 是一个第三方库,需要通过 pip 进行安装。打开命令行或终端,输入以下命令:
5.2 获取项目代码
请参照上一节完整项目代码部分,将提供的代码复制并保存到您新建的 piano_tiles_game 文件夹中。
- 步骤:
- 创建一个新的项目文件夹,例如
piano_tiles_game。 - 在该文件夹内,创建一个空白的 Python 文件,命名为
piano_tiles.py。 - 将上述提供的完整代码,完整地复制并粘贴到
piano_tiles.py文件中。
- 重要提示: 请确保所有Python文件都以 UTF-8 编码 保存,且代码的缩进是正确的(Python使用缩进来定义代码块,通常是4个空格)。
- 创建一个新的项目文件夹,例如
5.3 解决中文字体乱码问题(关键步骤)
由于 Pygame 默认字体通常不支持中文字符,您需要确保程序能够找到一个合适的中文字体。代码中已经加入了自动检测逻辑:
- 自动检测系统字体: 程序会首先尝试在 Windows 系统中查找一些常见的中文字体(如“SimHei”、“Microsoft YaHei”)。如果找到并成功加载,则无需额外操作。
- 加载程序目录下的字体文件(推荐方式): 如果自动检测系统字体失败,程序会尝试在
piano_tiles.py文件所在的目录中寻找一个名为SimHei.ttf的字体文件。- 操作: 您可以从互联网上下载一个免费的中文字体文件(例如 思源黑体 (Source Han Sans) 或 Windows 系统自带的 SimHei.ttf、Microsoft YaHei.ttf)。
- 将下载的字体文件(例如
SimHei.ttf)复制到piano_tiles.py所在的piano_tiles_game文件夹中。 - 如果您下载的字体文件不是
SimHei.ttf这个名字,请将其重命名为SimHei.ttf,或者修改piano_tiles.py中bundled_font_name = "SimHei.ttf"这一行,将其改为您实际使用的字体文件名。
完成上述字体设置后,程序应该就能正常显示中文了。
5.4 运行应用程序
- 导航到项目根目录: 打开命令行或终端,使用
cd命令进入您的项目文件夹(例如cd piano_tiles_game)。 - 运行
piano_tiles.py文件:
或者,如果您的系统上同时存在 Python 2 和 Python 3,请明确指定:python piano_tiles.pypython3 piano_tiles.py - 游戏窗口应该会立即弹出,并显示开始界面。点击任意位置即可开始游戏。
5.5 常见问题与调试技巧
- Pygame 模块未找到 (
ModuleNotFoundError: No module named 'pygame'):- 原因: Pygame 库没有正确安装。
- 解决方案: 确保您已按照 5.1 节的说明使用
pip install pygame(或pip3 install pygame) 成功安装了 Pygame。
- 中文字体仍显示乱码:
- 原因: 程序未能找到或加载到合适的中文字体。
- 解决方案: 仔细检查 5.3 节的字体设置步骤。确保
SimHei.ttf文件已放置在piano_tiles.py同目录下,或您已将代码中的字体文件名修改为正确的文件名。在控制台输出中,可以查看是否有“警告: 未找到中文支持字体”的信息。
- 黑屏或窗口不响应:
- 原因: 可能是游戏循环没有正确更新显示,或者事件处理堵塞。
- 解决方案: 检查
pygame.display.flip()和self.clock.tick(60)是否在游戏循环的末尾正确调用。
- 游戏逻辑错误:
print()语句: 在代码的关键位置插入print()语句,输出变量的值(例如方块的y坐标、玩家分数),函数的执行路径。这是 Pygame 调试最直接的方法。- IDE 调试器: 如果您使用 VS Code 或 PyCharm 等 IDE,它们通常内置了强大的调试器。您可以在代码中设置断点,然后逐步执行代码,检查变量状态。
- 错误信息分析: 当程序抛出异常时,仔细阅读控制台输出的错误信息 (Traceback)。它会指明错误类型、错误发生的文件名和行号,是定位问题的关键线索。
6. 总结与展望
恭喜您!您已经完成了这个 Python Pygame 钢琴块游戏的构建。
通过这个项目,您不仅成功搭建了一个功能齐全的桌面游戏,更重要的是,您深入掌握了 Pygame 游戏开发的核心技能,理解了游戏循环、事件处理、图形渲染、状态管理和计分系统等重要的游戏编程概念。
6.1 知识点回顾
- Pygame 基础: 初始化、窗口创建、颜色、矩形、字体(包括中文字体加载)。
- 游戏循环: 事件处理、状态更新、图形渲染的循环模式。
- 游戏状态管理: 如何在开始、进行中、结束等不同游戏状态之间切换。
- 对象导向编程: 使用
Tile类管理游戏中的方块。 - 用户输入处理: 响应鼠标点击事件。
- 游戏机制: 方块生成与下落、碰撞检测、得分逻辑、难度递增。
- 图形渲染: 在屏幕上绘制方块、文本。
- 代码质量: 接触了 PEP 8 规范、常量使用、模块化思想。
6.2 进一步的优化与扩展 (Next Steps)
本教程为您奠定了坚实的基础,但一个完整的游戏还有很多可以改进和扩展的地方。您可以尝试以下挑战,继续提升您的技能:
- 添加音效和背景音乐:
- 为方块点击添加音效,为游戏添加背景音乐,增强沉浸感。
- 使用
pygame.mixer模块实现。
- 更复杂的方块模式:
- 不仅仅是单行单块,可以尝试多块、特殊方块(例如长方块)。
- 引入不同颜色的方块,每个颜色有不同的效果。
- 计分系统增强:
- 实现连击(Combo)机制,连续正确点击可获得额外分数。
- 持久化最高分:将最高分保存到文件(例如 JSON 文件),以便下次启动游戏时加载。
- 更精美的UI和动画:
- 替换矩形方块为图片精灵,使游戏看起来更专业。
- 为方块点击或游戏结束添加粒子效果或过渡动画。
- 增加游戏模式:
- 时间挑战模式:在规定时间内尽可能得分。
- 无限模式:没有明确的目标,只看你能坚持多久。
- 优化性能: 对于更复杂的场景,学习如何优化 Pygame 绘图和事件处理,以保持流畅的帧率。
- 键盘控制: 允许玩家使用键盘按键(例如 A, S, D, F 对应四列)来点击方块。
希望这份详尽的教程能够为您在 Python 游戏开发领域打开一扇新的大门。持续学习,不断实践,您将成为一名优秀的开发者!
更多推荐
所有评论(0)