多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

游戏设计模式实战指南:GameDevMind 五模式配套代码精讲

游戏设计模式实战指南:GameDevMind 五模式配套代码精讲 文档知识库教程游戏开发【免费下载链接】GameDevMind最全面的游戏开发技术图谱(Game Development Map)。帮助游戏开发者们在已知问题上节省时间省出更多的精力投入到更有创造性的工作中去。项目地址https://gitcode.com/gonglei007/GameDevMind点击查看免费下载导读本文以 GameDevMind 仓库中「设计模式实战」配套代码code/artile-sample-code/01-foundation/02-design-patterns/为骨架逐一剖析单例、观察者、状态、对象池、命令五种在游戏开发中最常用的设计模式先给出可直接编译运行的代码再结合仓库源码逐层讲解实现原理、适用场景与常见坑点并延伸至仓库中更完整的工程级实现输入录制/撤销/重做、对象池性能基准测试。读完本文你将掌握五种模式的游戏化写法能够直接复制运行并懂得如何用它们解决内存抖动、输入缓冲、状态爆炸、系统解耦等真实问题。设计模式是程序开发的一项内功合理选用而非滥用能让系统更健壮、更易读、更易维护。在游戏开发中模式的应用场景尤为具体单例管理全局系统、观察者驱动事件系统、状态机控制角色行为、对象池优化高频对象、命令模式实现输入缓冲与回放。GameDevMind 仓库的 设计模式知识图谱 系统整理了这些模式的维度、应用场景与常见问题而配套示例代码则给出了可运行的最小实现两者互为印证。示例全景与运行方式仓库中 设计模式实战 — 配套代码 共提供 5 个示例覆盖 5 种核心模式C 与 Python 两种语言模式文件语言游戏场景单例模式MonoSingletonsingleton.cppC全局唯一管理器音频、配置观察者模式事件系统observer.pyPython事件总线 / 信号槽状态模式角色状态机state_machine.cppC角色 Idle / Run / Attack / Dead对象池模式object_pool.cppC子弹 / 粒子高频生成回收命令模式输入缓冲command.cppC格斗输入缓冲 / 录像回放运行方式README 原文均可在仓库目录下直接执行# C g -stdc17 singleton.cpp -o singleton ./singleton # Python python3 observer.py其余 C 示例同理编译g -stdc17 state_machine.cpp -o state_machine ./state_machine、g -stdc17 object_pool.cpp -o object_pool ./object_pool、g -stdc17 command.cpp -o command ./command。下面按模式逐一展开。单例模式全局唯一的管理器知识图谱对单例的定义是确保类只有一个实例提供全局访问点典型应用是各基础系统——GameSystem、SoundSystem、ResourceManager、NetworkManager 等见 1.2.1.设计模式.md。singleton.cpp 给出了三个递进版本。版本一C11 线程安全单例GameConfig用「函数局部静态变量」实现懒加载单例——static局部变量在 C11 及之后由标准保证初始化线程安全class GameConfig { public: static GameConfig getInstance() { static GameConfig instance; // C11 保证线程安全 return instance; } void setVolume(int v) { volume_ v; } int getVolume() const { return volume_; } private: GameConfig() : volume_(80) {} // 构造函数私有 ~GameConfig() default; GameConfig(const GameConfig) delete; // 禁止拷贝 GameConfig operator(const GameConfig) delete; // 禁止赋值 int volume_; };三个关键设计构造私有外部无法 new、拷贝/赋值 delete防止复制破坏唯一性、通过getInstance()静态访问。这就是知识图谱中「线程安全 → 静态初始化」的代码级答案。主函数验证了全局唯一性setVolume(50)后再次获取仍读回 50。版本二模板化 MonoSingleton模拟 Unity 风格Unity/Cocos 中最常见的模式是让每个管理器继承一个MonoSingletonT基类templatetypename T class MonoSingleton { public: static T getInstance() { static T instance; return instance; } protected: MonoSingleton() default; virtual ~MonoSingleton() default; private: MonoSingleton(const MonoSingleton) delete; MonoSingleton operator(const MonoSingleton) delete; }; class AudioManager : public MonoSingletonAudioManager { friend class MonoSingletonAudioManager; public: void playBGM(const std::string name) { /* ... */ } private: AudioManager() default; // 私有构造仅模板基类可实例化 };这是 CRTP奇异递归模板模式每个管理器只需继承MonoSingleton自己即可免费获得全局唯一实例。AudioManager的构造函数同样私有并借助friend允许模板基类构造它。assert(am AudioManager::getInstance())验证了两次获取同一实例。版本三Service Locator —— 单例的进阶替代知识图谱提醒单例的典型问题是「测试困难」与「全局状态污染」解决方向是「依赖注入替代、提供设置实例方法、接口抽象」。代码给出了一个务实答案——服务定位器 接口 Null Objectclass IAudioService { // 抽象接口 public: virtual void playSound(const std::string) 0; }; class RealAudioService : public IAudioService { /* 真实播放 */ }; class NullAudioService : public IAudioService { /* 静默 — Null Object */ }; class ServiceLocator { public: static IAudioService getAudio() { return *audioService_; } static void provide(IAudioService* service) { audioService_ service; } private: static inline IAudioService* audioService_ nullptr; static inline NullAudioService nullService_; // 未注入时返回空对象 };好处调用方依赖接口而非具体类provide()可在测试时注入 Mock未注入时默认返回空实现避免空指针崩溃。这是从「全局唯一」走向「可替换」的重要演进。观察者模式轻量事件总线知识图谱中观察者/发布订阅的定位是两系统无耦合交互一系统变化让另一系统知道并响应好处是系统更独立、性能可控游戏中的典型实现就是事件系统。observer.py 实现了一个极简 EventBus。EventBus 三件套on / off / emitclass EventBus: def __init__(self): self._handlers: Dict[str, List[Callable]] {} def on(self, event: str, callback: Callable): # 订阅 self._handlers.setdefault(event, []).append(callback) def off(self, event: str, callback: Callable): # 取消订阅 if event in self._handlers: self._handlers[event] [h for h in self._handlers[event] if h ! callback] def emit(self, event: str, **kwargs): # 触发 for handler in self._handlers.get(event, []): handler(kwargs)核心设计以字符串事件名为键、回调函数列表为值的字典。emit时通过关键字参数**kwargs把数据打包成字典传给所有订阅者订阅者解包使用互不依赖。游戏事件实战代码用Player 四个响应函数模拟完整游戏流程命中反馈、死亡掉落、分数变化、成就解锁bus.on(player:hit, on_player_hit) bus.on(player:death, on_player_death) bus.on(score:changed, on_score_changed) bus.on(achievement:unlocked, on_achievement) bus.emit(player:hit, attacker哥布林, target英雄, damage15) bus.emit(score:changed, delta100, total150) bus.emit(achievement:unlocked, name初出茅庐, desc首次击败 Boss) bus.emit(player:death, name英雄, drop_count3)演示末尾还验证了取消订阅bus.off(player:hit, on_player_hit)后监听者列表清空。事件名的命名空间:动作风格如player:hit、score:changed在大型项目里便于检索与归类。实战注意点来自知识图谱内存泄漏对象销毁时必须off取消订阅或用弱引用——否则被销毁的对象仍留在回调列表里通知顺序多个订阅者默认按注册顺序执行若需优先级需额外机制循环通知避免在回调里再次修改被观察者导致无限触发性能高频事件要考虑批量通知或限制订阅者数量。状态模式角色状态机知识图谱给出角色状态机的经典迁移图Idle ↔ Walk移动输入/停止、Idle/Walk → Attack攻击输入、Attack → Idle动作完成。state_machine.cpp 用「状态基类 具体状态类 Character 持有当前状态」实现替代层层 if-else。状态基类与迁移接口class CharacterState { public: virtual void enter(Character) {} // OnEnter进入状态 virtual void update(Character) {} // OnUpdate每帧更新 virtual void exit(Character) {} // OnExit离开状态 virtual std::string name() const 0; };Character::changeState()是状态迁移的统一入口遵循「先 exit 旧状态、再 enter 新状态」的 FSM 生命周期约定void changeState(std::unique_ptrCharacterState newState) { if (state_) state_-exit(*this); state_ std::move(newState); if (state_) state_-enter(*this); }四个具体状态状态类enter 行为update 行为对应知识图谱场景IdleState进入待机播放待机动画、随机小动作待机RunState开始奔跑—移动AttackState开始攻击并清零连击数累计连击第 3 次触发三连击攻击DeadState宣告阵亡—死亡主函数完整演示了状态流转Idle →移动→ Run →攻击→ Attack连击 4 帧触发三连击→HP ≤ 0→ Dead全程用stateName()打印当前状态。状态爆炸与优化方向知识图谱指出状态机的典型问题是状态数量爆炸与转换逻辑复杂解决方向包括分层状态机、行为树、状态转换表、状态池。当角色动作超过 10 个、转换规则彼此耦合时就应考虑这些升级方案而不是无限堆叠状态类。对象池模式子弹与粒子的内存救星对象池的动机非常明确见 object_pool.cpp 注释弹幕游戏每秒产生数百颗子弹频繁new/delete会导致内存碎片和 GC 抖动而对象池预先分配并循环复用。知识图谱同时把它列为工厂模式的典型应用场景之一——「对象池工厂创建和管理池中对象实例」。池的核心实现templatetypename T class ObjectPool { public: explicit ObjectPool(size_t initialSize 32) { for (size_t i 0; i initialSize; i) { // 预分配 auto obj std::make_uniqueT(); obj-reset(); available_.push(obj.get()); allObjects_.push_back(std::move(obj)); } } T* acquire() { // 获取 if (available_.empty()) { // 池耗尽动态扩展 for (int i 0; i 16; i) { /* 新增 16 个 */ } } T* obj available_.front(); available_.pop(); return obj; } void release(T* obj) { // 归还 obj-reset(); available_.push(obj); } private: std::queueT* available_; // 空闲队列 std::vectorstd::unique_ptrT allObjects_; // 所有权容器 };数据结构上采用「双容器」allObjects_unique_ptr持有所有权、永不释放available_空闲指针队列。这样既保证内存只分配一次又能 O(1) 获取/归还。Bullet的reset()负责把对象恢复为新子弹状态坐标清零、生命周期复位为 5.0f、activefalse。演示流程初始 4 颗 → 发射 6 颗池自动扩展 16并打印警告→ 回收 4 颗 → 再次获取 2 颗时复用回收的子弹输出复用 Bullet #x直观展示循环复用。进阶仓库中的性能基准测试仓库在工程目录下提供了更完整的版本 object_pool/main.cpp模拟300 帧、每帧 200 颗子弹总计 60000 颗用std::chrono::high_resolution_clock对比两种策略策略 A每帧new Bullet()过期delete策略 B预分配ObjectPoolBullet pool(bullets_per_frame * 2)Acquire()/Release()循环复用。基准使用固定随机种子std::mt19937 rng(42)保证两种策略完全同场景可复现输出new/delete 耗时、对象池耗时、加速比与节省时间并给出结论对象池避免频繁 malloc/free减少内存碎片帧率更稳定。该目录还带 CMakeLists.txt 可直接构建README.md 说明运行方式——建议自行运行对比验证对象池在真实负载下的收益。注意加速比是运行环境相关的实测数据不同编译器、平台结果不同本文不预设具体数字。命令模式输入缓冲与回放系统命令模式把「请求」封装为对象从而支持参数化、队列化、日志化与撤销/重做知识图谱定义。command.cpp 以格斗游戏输入缓冲为场景演示「录制 → 分帧执行 → 历史回放」全流程。命令抽象与接收者class Command { public: virtual void execute() 0; virtual void undo() {} // 预留撤销能力 virtual std::string describe() const 0; // 描述用于回放记录 };接收者Fighter提供 jump / punch / kick / special / block 五个动作每个具体命令JumpCommand、PunchCommand…持有接收者引用在execute()中调用对应动作。命令与接收者分离是命令模式的精髓——发出命令的代码不需要知道接收者是谁。InputHandler队列缓冲 历史回放class InputHandler { public: void enqueue(std::unique_ptrCommand cmd) { // 入队缓冲 buffer_.push(std::move(cmd)); } void processFrame() { // 每帧执行最早的命令 if (buffer_.empty()) return; auto cmd std::move(buffer_.front()); buffer_.pop(); history_.push_back(cmd-describe()); // 写入历史 cmd-execute(); } private: std::queuestd::unique_ptrCommand buffer_; std::vectorstd::string history_; };演示模拟格斗游戏的典型输入序列↓↘→P, K, P玩家可能在同一帧内快速按键系统将三条命令入队后分三帧Frame 1/2/3依次执行最终showHistory()打印Frame 1: Special、Frame 2: Kick、Frame 3: Punch的完整回放记录。这正是知识图谱中「游戏 → 撤销/重做功能、操作历史、宏命令」的场景实现。进阶仓库中的录制/撤销/重做/回放实现工程目录 command/ 给出了更完整的版本main.cpp 演示六步流程创建玩家「勇者」录制输入序列向右移动 → 向下移动 → 攻击 → 火球术SkillCommand支持执行 lambda 与撤销 lambda 双回调→ 向前跑批量执行全部命令撤销最近 2 步UndoLast重做 1 步RedoLast重做火球术冲刺完整回放Replay重置玩家后从录制序列重新执行。其中SkillCommand的构造展示了命令模式的灵活之处——执行与撤销逻辑可以直接用 lambda 注入无需为每个技能单独建类CommandQueuecommand_queue.hpp则管理录制序列、撤销栈与重做栈。目录内同样提供 CMakeLists.txt 便于构建验证。实战注意点命令对象过多高频率命令如移动指令可用对象池复用知识图谱明确建议「命令对象池、轻量级命令、合并相似命令」撤销深度限制撤销栈要限制深度如编辑器常限 50 步避免内存无限增长序列化若命令需跨端传输网络同步或持久化录像存档describe()这类序列化接口就是基础。模式之外知识图谱中的架构补充知识图谱在五种模式之外还整理了与游戏架构强相关的三个模式可作为延伸阅读1.2.1.设计模式.md工厂模式Factory封装对象创建过程适合「按条件创建不同类型」「创建过程复杂」的场景与对象池天然搭配池内实例的创建统一由工厂管理MVCModel数据/业务、View纯表现、Controller业务逻辑分离适用于好友、排行榜、投票等有表现与交互的功能Controller 膨胀时可抽取 Service 层View-Model 耦合用观察者解耦ECS数据Component与逻辑System分离、Entity 仅作 ID 组合适合大量对象的高性能处理物理、渲染但不适合偏业务流程的功能。知识图谱还为每个模式附带了AI Coding 提示词范例如「为这个 C# SoundSystem 实现线程安全的懒加载单例并提供一个 SetInstance 方法便于测试时注入 Mock」可作为用 AI 辅助编写模式化代码时的交互模板详见原文。总结五模式的选型速查模式一句话定位典型场景核心坑点单例全局唯一、全局访问配置、音频、资源管理线程安全、测试困难、全局状态观察者一对多解耦通知事件系统、UI 更新、网络状态通知内存泄漏未取消订阅、循环通知状态机状态行为封装进独立类角色行为、登录/网络/房间状态状态爆炸、转换逻辑复杂对象池预分配 循环复用子弹、粒子、高频临时对象复用对象必须 reset 干净命令请求封装为对象输入缓冲、撤销/重做、回放、宏命令对象过多、撤销深度建议的动手路径先按 README 的命令编译运行全部 5 个示例观察输出再打开工程版 command/ 与 object_pool/ 的基准/撤销重做实现对比示例版与工程版的差异最后回到 知识图谱用「会遇到哪些问题」清单逐条对照自己项目中的真实代码。设计模式的价值不在背定义而在把「已知问题的成熟解法」内化为肌肉记忆——这正是 GameDevMind 仓库「在已知问题上节省时间」的初衷。赞分享文档知识库教程游戏开发【免费下载链接】GameDevMind最全面的游戏开发技术图谱(Game Development Map)。帮助游戏开发者们在已知问题上节省时间省出更多的精力投入到更有创造性的工作中去。项目地址https://gitcode.com/gonglei007/GameDevMind点击查看免费下载相关推荐GameDevMind 游戏开发设计模式实战指南从单例到 ECS 的九大模式与 AI Coding 协同GameDevMind 游戏开发设计模式实战指南从单例到 ECS 的九大模式与 AI Coding 协同 设计模式是程序开发的一项内功合理选用而非滥用设文档教程知识库游戏开发GameDevMind 程序设计篇设计模式、数据结构、算法与代码重构实战指南GameDevMind 程序设计篇设计模式、数据结构、算法与代码重构实战指南 程序设计是游戏开发团队最重要的基本功它决定了整个团队的开发效率与软件维护成本。文档教程知识库游戏开发Swift 创建型设计模式Creational Design Patterns实战指南基于 Design-Patterns-In-Swift 源码逐模式精讲Swift 创建型设计模式Creational Design Patterns实战指南基于 Design Patterns In Swift 源码逐模式精示例工程上一篇alive-progress主题系统完全解析内置样式与自定义方案终极指南下一篇新手 10 分钟跑通 d2s-editor免费暗黑2存档编辑器使用指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表