Python游戏开发框架:基于Pygame与状态机的模块化设计实践

Python游戏开发框架:基于Pygame与状态机的模块化设计实践
1. 项目概述为什么从游戏框架开始如果你刚接触Python游戏开发或者已经用pygame写过一些零散的小程序但总觉得代码结构混乱、难以维护和扩展那么这篇文章就是为你准备的。很多朋友学pygame都是从官网的“画个方块动起来”的教程开始的这没错它能让你快速获得成就感。但当你真的想做一个稍微复杂点的游戏比如一个包含多个关卡、有开始菜单、有暂停功能的完整小游戏时你会发现代码很快变成了一团乱麻事件处理、画面更新、游戏逻辑、资源加载全都混在一起改一处而动全身。这就是为什么我们要从“游戏框架”讲起。这里的“框架”不是指像Unity、Unreal那样的庞大引擎而是一种代码的组织模式一种约定俗成的结构。它就像给你的游戏代码搭建一个稳固的骨架让不同的功能模块比如输入、渲染、逻辑、状态各司其职清晰明了。使用Python 3.11我们能享受到更快的启动速度和更优的性能这对于实时性要求较高的游戏循环来说是个不错的加成。今天我就手把手带你搭建一个最经典、也最实用的pygame游戏框架这个框架足够轻量也足够健壮能支撑你完成大部分2D小游戏的开发。2. 核心框架设计理解游戏主循环与状态机在动手写代码之前我们必须先理解两个核心概念游戏主循环和状态机。这是所有游戏框架的基石。2.1 游戏主循环引擎的心脏游戏本质上是一个无限循环直到玩家退出。每一次循环我们都要做以下几件事处理事件检查玩家有没有按键盘、点鼠标或者点了窗口的关闭按钮。更新游戏状态根据输入和游戏规则更新所有游戏对象的位置、状态等。比如让角色根据按键移动让子弹飞出去检查碰撞。渲染画面把最新的游戏状态画到屏幕上。在pygame中一个最原始的主循环长这样import pygame pygame.init() screen pygame.display.set_mode((800, 600)) clock pygame.time.Clock() running True while running: # 1. 处理事件 for event in pygame.event.get(): if event.type pygame.QUIT: running False # 2. 更新状态 (这里暂时空着) # ... # 3. 渲染画面 screen.fill((0, 0, 0)) # 用黑色清屏 # ... 绘制其他对象 pygame.display.flip() # 更新整个屏幕 # 控制帧率 clock.tick(60) pygame.quit()这个循环很简单但所有逻辑都堆在同一个while循环里。一旦功能变多这里就会变得极其臃肿。2.2 状态机管理游戏的不同阶段一个完整的游戏通常有多个“状态”比如开始菜单状态显示标题、选项开始游戏、设置、退出。游戏进行状态玩家实际游玩的场景。暂停状态游戏暂停可能显示一个菜单。游戏结束状态显示得分、提供重玩选项。状态机就是管理这些状态切换的系统。在任一时刻游戏只处于一个“当前状态”。主循环会委托当前状态去处理事件、更新逻辑和渲染画面。当满足条件时如点击了“开始游戏”按钮我们就切换到另一个状态。为什么需要状态机想象一下如果没有状态机你就要在游戏主循环里写一大堆if语句if in_menu: ... elif in_game: ... elif is_paused: ...。代码会非常混乱状态之间的资源比如菜单图片和游戏地图也容易互相干扰。状态机通过将不同状态的逻辑封装到独立的类中实现了高内聚、低耦合让代码清晰可维护。我们的框架将围绕一个基础状态类和一个管理这些状态的状态栈来构建。这是专业游戏代码的常见模式。3. 框架搭建实战从零编写核心模块理解了原理我们开始动手。我会使用Python 3.11并确保你已经安装了pygame (pip install pygame)。我们将创建以下几个文件来组织代码main.py: 程序入口初始化pygame和主框架。game.py: 包含游戏主循环和状态机管理的核心类。states/目录存放各个游戏状态。base_state.py: 所有状态类的基类。menu_state.py: 菜单状态示例。play_state.py: 游戏进行状态示例。3.1 定义状态基类首先在states/base_state.py中定义所有状态的“蓝图”。这个基类规定了每个状态必须实现的方法。# states/base_state.py import pygame class BaseState: 所有游戏状态的基类。 子类必须实现 handle_events, update, render 和 cleanup 方法。 def __init__(self, game): 初始化状态。 :param game: Game类实例用于访问共享资源如屏幕、资源管理器等。 self.game game self.screen game.screen self.done False # 为True时表示该状态结束状态机应切换到下一个状态 self.next_state None # 状态结束后应切换到的下一个状态名 self.persist {} # 用于在状态间传递数据的字典 def startup(self, persistent): 当状态被激活进入时调用。 :param persistent: 一个字典包含了从前一个状态传递过来的数据。 self.persist persistent self.done False def handle_events(self, event): 处理单个事件。由主循环调用。 :param event: pygame.event.Event 对象 pass def update(self, dt): 更新游戏逻辑。由主循环调用。 :param dt: 距离上一帧过去的时间以秒为单位用于实现与帧率无关的运动。 pass def render(self, screen): 将状态内容渲染到屏幕上。由主循环调用。 :param screen: pygame.Surface 对象即游戏窗口。 pass def cleanup(self): 当状态被关闭退出时调用。用于释放资源。 返回一个字典其中的数据将传递给下一个状态。 self.done False return self.persist关键点解析game参数让状态可以访问游戏主对象从而共享屏幕、资源加载器等全局工具避免了使用全局变量。done和next_state这是状态机切换的核心。当一个状态如菜单的工作完成后比如玩家点了“开始”它将自己的done设为True并指定next_state为PLAY。状态管理器看到后就会执行切换。persist字典这是状态间通信的桥梁。比如在菜单状态选择了难度“hard”可以把它放进persist字典然后在游戏进行状态中读取这个值来调整敌人强度。dt参数update方法接收一个dtdelta time这是实现平滑动画的关键。如果你让物体每帧移动5像素在60帧下每秒移动300像素在30帧下每秒只移动150像素速度就不同了。正确做法是物体.x 速度 * dt这样无论帧率高低物体每秒移动的距离都是固定的。3.2 实现游戏核心与状态机管理接下来在game.py中创建我们的游戏引擎核心Game类它负责管理状态栈和运行主循环。# game.py import pygame import sys from collections import deque class Game: def __init__(self, screen_size, window_titleMy Pygame Game, fps60): pygame.init() self.screen pygame.display.set_mode(screen_size) pygame.display.set_caption(window_title) self.clock pygame.time.Clock() self.fps fps self.dt 0.0 # 帧时间差 self.running False # 状态机核心状态栈和状态工厂 self.state_stack deque() # 使用双端队列作为栈 self.state_factory {} # 状态名 - 状态类的映射 # 游戏资源可以在这里初始化比如字体、声音管理器、图像缓存等 self.font pygame.font.Font(None, 36) # 默认字体大小36 def register_state(self, state_name, state_class): 向游戏注册一个状态。 :param state_name: 状态的字符串标识符如 MENU, PLAY :param state_class: 状态类继承自BaseState未实例化 self.state_factory[state_name] state_class def push_state(self, state_name, persistentNone): 将一个新状态压入栈顶并立即激活它。 :param state_name: 要压入的状态名 :param persistent: 传递给新状态的数据字典 if persistent is None: persistent {} if state_name not in self.state_factory: raise ValueError(f状态 {state_name} 未注册) # 创建状态实例 new_state self.state_factory[state_name](self) self.state_stack.append(new_state) new_state.startup(persistent) def pop_state(self): 弹出栈顶状态结束当前状态。 返回被弹出状态的清理数据。 if self.state_stack: old_state self.state_stack.pop() persistent_data old_state.cleanup() return persistent_data return {} def change_state(self, new_state_name, persistentNone): 替换当前状态先弹出再压入新的。 这是最常用的状态切换方式。 self.pop_state() self.push_state(new_state_name, persistent) def get_active_state(self): 获取当前活动的状态栈顶状态。 if self.state_stack: return self.state_stack[-1] return None def run(self, initial_state_name): 启动游戏主循环。 :param initial_state_name: 初始状态名 self.running True self.push_state(initial_state_name) # 初始化第一个状态 # 主循环 while self.running: # 计算帧时间差 (dt) self.dt self.clock.tick(self.fps) / 1000.0 # 将毫秒转换为秒 # 1. 处理事件 for event in pygame.event.get(): if event.type pygame.QUIT: self.running False # 将事件传递给当前活动状态处理 active_state self.get_active_state() if active_state: active_state.handle_events(event) # 2. 更新当前状态 active_state self.get_active_state() if active_state: active_state.update(self.dt) # 检查当前状态是否已完成 if active_state.done: persistent_data active_state.cleanup() next_state active_state.next_state self.pop_state() if next_state: # 将当前状态的persist数据传递给下一个状态 self.push_state(next_state, persistent_data) elif not self.state_stack: # 如果栈为空且没有下一个状态则退出游戏 self.running False # 3. 渲染 self.screen.fill((30, 30, 30)) # 深灰色背景 if active_state: active_state.render(self.screen) pygame.display.flip() # 游戏结束清理 pygame.quit() sys.exit()设计思路与避坑指南状态栈 vs 单一状态为什么用栈考虑这个场景游戏进行中 - 按ESC暂停 - 弹出暂停菜单。此时游戏状态应该在栈底暂停菜单在栈顶。当关闭暂停菜单时只需弹出栈顶的暂停状态游戏状态就自动恢复了所有游戏数据都完好无损。这比用变量来回切换要优雅和可靠得多。状态工厂模式我们用一个字典state_factory来关联状态名和状态类。这样做的好处是在需要切换状态时我们只需要知道状态的名字字符串而不需要在代码中到处import具体的状态类降低了模块间的耦合。dt的计算pygame.time.Clock().tick(fps)会返回自上次调用以来经过的毫秒数。除以1000得到秒。务必在每一帧循环开始时计算并传递给update。这是实现稳定物理模拟的基础。状态切换的时机我们在update之后检查当前状态是否done。注意我们是在当前帧渲染之前执行状态清理和切换的。这意味着如果一个状态在本帧标记为done它会在本帧的渲染阶段被新状态替代。逻辑上是连贯的。3.3 创建具体的游戏状态示例现在让我们用这个框架创建两个最基本的状态菜单和游戏场景。菜单状态示例(states/menu_state.py)# states/menu_state.py import pygame from .base_state import BaseState class MenuState(BaseState): def __init__(self, game): super().__init__(game) self.options [开始游戏, 设置, 退出] self.selected_index 0 # 使用游戏核心提供的字体确保资源统一管理 self.title_font pygame.font.Font(None, 72) self.option_font self.game.font # 使用Game类中初始化的字体 def handle_events(self, event): if event.type pygame.KEYDOWN: if event.key pygame.K_UP: self.selected_index (self.selected_index - 1) % len(self.options) elif event.key pygame.K_DOWN: self.selected_index (self.selected_index 1) % len(self.options) elif event.key pygame.K_RETURN or event.key pygame.K_SPACE: self._select_option(self.selected_index) def _select_option(self, index): option_text self.options[index] if option_text 开始游戏: # 标记本状态完成并指定下一个状态为“游戏进行状态” self.done True self.next_state PLAY # 可以传递一些数据比如玩家选择的难度 self.persist[difficulty] normal elif option_text 退出: # 触发退出事件这会导致主循环中的 pygame.QUIT 处理逻辑执行 pygame.event.post(pygame.event.Event(pygame.QUIT)) def update(self, dt): # 菜单状态可能有一些动画比如选中的选项闪烁 # 这里可以更新动画计时器等 pass def render(self, screen): screen.fill((20, 20, 50)) # 深蓝色背景 # 渲染标题 title_surf self.title_font.render(我的游戏, True, (255, 255, 100)) title_rect title_surf.get_rect(center(screen.get_width()//2, 100)) screen.blit(title_surf, title_rect) # 渲染选项 for i, option in enumerate(self.options): color (255, 255, 255) if i ! self.selected_index else (255, 200, 50) text_surf self.option_font.render(option, True, color) # 在选项前画一个选择指示器 indicator if i self.selected_index else full_text indicator option text_surf self.option_font.render(full_text, True, color) text_rect text_surf.get_rect(center(screen.get_width()//2, 250 i * 60)) screen.blit(text_surf, text_rect)游戏进行状态示例(states/play_state.py)# states/play_state.py import pygame from .base_state import BaseState class PlayState(BaseState): def __init__(self, game): super().__init__(game) # 从persist中读取可能传递过来的数据 self.difficulty self.persist.get(difficulty, normal) # 简单的游戏对象一个玩家方块 self.player_rect pygame.Rect(100, 100, 50, 50) self.player_speed 300 # 像素/秒 self.player_color (100, 200, 255) def startup(self, persistent): # 基类方法已经将persist赋值给self.persist super().startup(persistent) # 可以在这里根据persist数据初始化更复杂的逻辑 print(f游戏开始难度{self.difficulty}) # 重置玩家位置 self.player_rect.center (self.screen.get_width()//2, self.screen.get_height()//2) def handle_events(self, event): if event.type pygame.KEYDOWN: if event.key pygame.K_ESCAPE: # 按ESC键我们不直接退出游戏而是切换到暂停状态。 # 这展示了状态栈的另一种用法压入新状态。 self.done True self.next_state PAUSE # 假设我们之后会注册一个PAUSE状态 # 将当前游戏数据如分数、玩家位置传递下去 self.persist[player_pos] (self.player_rect.x, self.player_rect.y) def update(self, dt): # 基于dt的移动实现帧率无关的运动 keys pygame.key.get_pressed() move_x, move_y 0, 0 if keys[pygame.K_a] or keys[pygame.K_LEFT]: move_x - self.player_speed * dt if keys[pygame.K_d] or keys[pygame.K_RIGHT]: move_x self.player_speed * dt if keys[pygame.K_w] or keys[pygame.K_UP]: move_y - self.player_speed * dt if keys[pygame.K_s] or keys[pygame.K_DOWN]: move_y self.player_speed * dt self.player_rect.x move_x self.player_rect.y move_y # 简单的边界检查 self.player_rect.clamp_ip(self.screen.get_rect()) def render(self, screen): screen.fill((40, 40, 40)) # 游戏场景背景色 # 绘制玩家 pygame.draw.rect(screen, self.player_color, self.player_rect) # 显示难度信息 info_text f难度: {self.difficulty} - 使用方向键/WASD移动ESC暂停 info_surf self.game.font.render(info_text, True, (220, 220, 220)) screen.blit(info_surf, (10, 10)) def cleanup(self): print(游戏进行状态结束清理。) # 可以在这里保存一些需要持久化的数据 return super().cleanup()3.4 组装并启动游戏最后在main.py中我们将所有部分组装起来启动游戏。# main.py from game import Game from states.menu_state import MenuState from states.play_state import PlayState if __name__ __main__: # 1. 创建游戏实例 my_game Game(screen_size(1024, 768), window_titlePygame框架实战, fps60) # 2. 注册所有可能用到的状态 my_game.register_state(MENU, MenuState) my_game.register_state(PLAY, PlayState) # 未来可以在这里注册更多状态如 my_game.register_state(PAUSE, PauseState) # 3. 运行游戏指定初始状态 my_game.run(initial_state_nameMENU)4. 框架的扩展与最佳实践一个基础的框架已经搭建完成。但要让它在实际项目中游刃有余还需要考虑更多细节。下面分享一些关键的扩展点和避坑经验。4.1 资源管理不要重复加载在多个状态中你可能会用到同样的字体、图片或声音。如果在每个状态的__init__里都加载一遍会浪费内存和加载时间。正确的做法是在Game类中实现一个中央资源管理器。# 在Game类中新增 class Game: def __init__(self, ...): # ... 其他初始化 ... self._resources { fonts: {}, images: {}, sounds: {} } def load_font(self, name, path, size): key (path, size) if key not in self._resources[fonts]: self._resources[fonts][key] pygame.font.Font(path, size) return self._resources[fonts][key] def load_image(self, name, path, alphaTrue): if path not in self._resources[images]: if alpha: image pygame.image.load(path).convert_alpha() else: image pygame.image.load(path).convert() self._resources[images][path] image return self._resources[images][path] # 在状态中这样使用 class PlayState(BaseState): def startup(self, persistent): super().startup(persistent) # 使用游戏核心的资源管理器加载确保全局唯一 self.player_image self.game.load_image(player, assets/player.png)这样无论多少个状态需要assets/player.png它只在内存中保存一份。4.2 输入处理抽象与封装直接在状态的handle_events里写大量的if event.key pygame.K_XXX会很难维护尤其是当你想支持按键重定义时。一个好的实践是创建一个输入管理器将原始的pygame事件映射为游戏内抽象的“动作”。# input_manager.py class InputManager: def __init__(self): self.key_bindings { move_left: [pygame.K_a, pygame.K_LEFT], move_right: [pygame.K_d, pygame.K_RIGHT], jump: [pygame.K_SPACE, pygame.K_w, pygame.K_UP], confirm: [pygame.K_RETURN, pygame.K_SPACE], cancel: [pygame.K_ESCAPE, pygame.K_BACKSPACE], } self.current_actions {action: False for action in self.key_bindings} def process_event(self, event): 处理单个事件更新动作状态用于瞬间动作如跳跃 if event.type pygame.KEYDOWN: for action, keys in self.key_bindings.items(): if event.key in keys: self.current_actions[action] True break # 一个按键通常只映射一个主要动作 elif event.type pygame.KEYUP: for action, keys in self.key_bindings.items(): if event.key in keys: self.current_actions[action] False def get_action(self, action_name): 获取某个动作当前是否被触发按下 return self.current_actions.get(action_name, False) # 在Game类中初始化InputManager并传递给状态 # 在状态中不再直接检查pygame.K_XXX而是检查 if self.game.input.get_action(jump):对于需要持续按下的移动我们还需要在update中查询按键状态pygame.key.get_pressed()输入管理器也可以整合这个功能。这大大提高了代码的可读性和可配置性。4.3 场景图与精灵组管理大量游戏对象当你的游戏有几十个甚至上百个敌人、子弹、道具时逐一手动更新和渲染是不可行的。Pygame提供了pygame.sprite.Sprite和pygame.sprite.Group来高效管理这些对象。最佳实践是创建一个自定义的精灵类并在每个游戏状态中维护自己的精灵组# 在play_state.py中 class Player(pygame.sprite.Sprite): def __init__(self, pos): super().__init__() self.image pygame.Surface((50, 50)) self.image.fill((100, 200, 255)) self.rect self.image.get_rect(centerpos) self.speed 300 def update(self, dt, actions): # 根据输入管理器的actions更新位置 move pygame.Vector2(0, 0) if actions.get(move_left): move.x - 1 if actions.get(move_right): move.x 1 if actions.get(move_up): move.y - 1 if actions.get(move_down): move.y 1 if move.length() 0: move.scale_to_length(self.speed * dt) self.rect.x move.x self.rect.y move.y class PlayState(BaseState): def __init__(self, game): super().__init__(game) self.all_sprites pygame.sprite.Group() self.player Player((100, 100)) self.all_sprites.add(self.player) def update(self, dt): # 获取当前输入动作 actions {} # ... 从game.input_manager获取动作状态 ... # 更新所有精灵 self.all_sprites.update(dt, actions) def render(self, screen): screen.fill((40,40,40)) self.all_sprites.draw(screen) # 一次性绘制所有精灵使用精灵组你只需要调用group.update()和group.draw()pygame会帮你高效地处理所有成员。碰撞检测也有对应的pygame.sprite.spritecollide等函数非常方便。4.4 性能调优与调试技巧即使有了框架写出低效的代码依然会让游戏卡顿。这里有几个Python 3.11结合pygame的优化点表面转换Convert加载图片后立即调用.convert()或.convert_alpha()。这会将图片转换成与当前显示模式最匹配的格式大幅提升后续blit绘制的速度。# 慢 image pygame.image.load(player.png) # 快 image pygame.image.load(player.png).convert_alpha() # 带透明通道用这个 # 或 image pygame.image.load(player.png).convert() # 不带透明通道用这个脏矩形更新如果你的游戏每一帧只有小部分区域变化比如一个角色在静态背景上移动使用pygame.display.update(rect_list)只更新变化区域而不是pygame.display.flip()更新整个屏幕可以显著提升性能。避免在循环中创建对象比如pygame.Rect,pygame.Vector2, 字体渲染font.render()。这些操作比较耗时。应在初始化时创建好并复用。# 不好每帧都创建新的Rect和渲染文字 def render(self): text self.font.render(fScore: {self.score}, True, (255,255,255)) # ... # 好只在分数变化时重新渲染 def update_score(self, new_score): self.score new_score self.score_text self.font.render(fScore: {self.score}, True, (255,255,255)) def render(self): screen.blit(self.score_text, (10, 10))使用Python 3.11的性能特性Python 3.11的Faster CPython项目带来了平均25%的加速。确保你的代码风格能从中受益比如使用更具体的数据结构listvsdeque减少不必要的属性查找等。对于极度性能敏感的部分如粒子系统更新可以考虑使用numpy或pygame的surfarray模块或者用Cython编写扩展。5. 常见问题与排查技巧实录在实际使用这个框架开发时你肯定会遇到一些问题。下面是我踩过的一些坑和解决方案。5.1 状态切换时画面闪烁或残留问题描述从菜单切换到游戏场景时屏幕会短暂地闪一下黑色或上一帧的残影。原因分析这是因为在新状态的startup方法或第一帧的render之前屏幕没有被完全清空。主循环中虽然有一句self.screen.fill(...)但如果新状态的render方法绘制的内容没有覆盖全屏或者startup耗时较长就会看到背景色。解决方案确保每个状态的render方法的第一句都是清屏操作screen.fill(背景色)。如果状态切换时有复杂的初始化如加载大图可以考虑在状态内实现一个加载画面或者使用渐入渐出的过渡效果。在我们的框架中可以在Game.run()的主循环里在调用active_state.render()之前确保执行了清屏。5.2 游戏运行速度不稳定忽快忽慢问题描述物体的移动速度在不同电脑上或同一电脑的不同时段不一样。原因分析这是没有正确使用帧时间差dt的典型症状。如果你的移动代码是rect.x 5那么帧率越高物体移动得越快。解决方案务必在所有运动相关的update逻辑中使用dt参数。将速度定义为“像素/秒”然后计算每帧的位移位移 速度 * dt。我们的框架已经将计算好的dt传递给了每个状态的update方法你只需要在状态内部使用它即可。这是游戏编程中最重要的一条纪律。5.3 事件处理“失灵”或响应迟钝问题描述按键有时没反应或者需要长按才能触发。原因分析事件队列被淹没如果一帧内产生了大量事件比如鼠标移动pygame.event.get()会一次性取出所有事件。如果你的handle_events逻辑复杂处理完所有事件可能已经过去了几毫秒影响了帧率导致输入响应变慢。状态栈阻塞如果状态栈顶有一个不传递事件的状态比如一个全屏动画状态它下面的状态就接收不到事件。解决方案在handle_events中只做最轻量、最紧急的判断如退出、暂停。将持续性的输入检查如pygame.key.get_pressed()放到update方法中。检查状态栈的逻辑。确保只有“活动状态”栈顶状态处理事件是符合设计的。如果你希望背景状态也能接收某些事件比如全局快捷键需要在Game.run()的事件循环中做特殊分发。5.4 内存泄漏游戏运行越久越卡问题描述长时间运行游戏后内存占用持续增长帧率下降。原因分析在Python中常见原因是不断创建新的Surface图片、Font对象而没有释放或者在列表/字典中不断添加对象而不清理如死亡的敌人、消失的子弹。解决方案使用资源管理器如前所述确保图片、字体等资源只加载一次全局共享。及时清理精灵组对于已经不需要的游戏对象如飞出屏幕的子弹、被击败的敌人一定要将其从所有精灵组中移除sprite.kill()这样Python的垃圾回收器才能释放其内存。在状态的cleanup方法中释放专属资源如果一个状态加载了独有的、巨大的背景图在cleanup中应将其设为None帮助垃圾回收。5.5 调试利器内置的性能监控你可以轻松地在框架中加入一个简单的性能显示状态用于调试。# 在Game类的run循环末尾渲染之前添加 def run(self): while self.running: # ... 事件、更新逻辑 ... # 渲染游戏状态 if active_state: active_state.render(self.screen) # 调试信息覆盖渲染 (可选) if self.show_debug: # 可以设置一个开关比如按F3触发 fps self.clock.get_fps() debug_text fFPS: {fps:.1f} | DT: {self.dt:.4f}s | States: {len(self.state_stack)} debug_surf self.font.render(debug_text, True, (255, 0, 0)) self.screen.blit(debug_surf, (5, 5)) pygame.display.flip()实时查看FPS和dt能帮你快速定位性能瓶颈。这个框架是一个起点而不是终点。它的价值在于提供了一种清晰、可扩展的组织代码的方式。你可以基于它轻松地添加暂停界面、过场动画、关卡选择、存档系统等复杂功能而不用担心代码结构崩溃。记住好的框架不是限制你的创造力而是让你更自由、更专注地去实现游戏的想法本身。