游戏开发模式一:组件模式(Component)
软件设计模式告诉我们,程序中不同的领域要保持隔离,也就是解耦。所以,我们不希望AI,物理引擎,渲染引擎,声音引擎,还有其他的事情影响到彼此,不能把他们放到同一个类里。
下面是一个反例:
if (collidingWithFloor() && (getRenderState() != INVISIBLE)) { playSound(HIT_FLOOR); } |
如果有人要修改这段代码,那么他就需要查看物理,绘图,和声音的代码以保证不会出错。更糟糕的情况是,你可能需要修改其他部分的代码!
解决的办法:
我们可以吧不同的领域分割城不同的组件,谈后需要的时候持有这些组件的实例,例如 InputComponent。
再看一个难一点的例子:
class Bjorn { public : void update(World& world, Graphics& graphics) { // Apply user input to hero's velocity. switch (Controller::getJoystickDirection()) { case DIR_LEFT: velocity_ -= WALK_ACCELERATION; break ; case DIR_RIGHT: velocity_ += WALK_ACCELERATION; break ; } // Modify position by velocity. x_ += velocity_; world.resolveCollision(volume_, x_, y_, velocity_); // Draw the appropriate sprite. Sprite* sprite = &spriteStand_; if (velocity_ < 0) sprite = &spriteWalkLeft_; else if (velocity_ > 0) sprite = &spriteWalkRight_; graphics.draw(*sprite, x_, y_); } private : static const int WALK_ACCELERATION = 1; int velocity_; int x_, y_; Volume volume_; Sprite spriteStand_; Sprite spriteWalkLeft_; Sprite spriteWalkRight_; }; |
Bjorn
有一个 update()方法每一帧被调用一次。它获取determine来决定方向,谈后他用物理引擎来处理位置,最后,它把Bjørn绘制到屏幕上。可以看到这里其实只做了很少的事情,但是却显得很复杂。
分割不同的领域:
首先让我们把input分离:
class InputComponent { public: void update(Bjorn& bjorn) { switch (Controller::getJoystickDirection()) { case DIR_LEFT: bjorn.velocity -= WALK_ACCELERATION; break; case DIR_RIGHT: bjorn.velocity += WALK_ACCELERATION; break; } } private: static const int WALK_ACCELERATION = 1; };
Bjorn的变化:
class Bjorn { public : int velocity; int x, y; virtual void update(World& world, Graphics& graphics) { input_.update(* this ); // Modify position by velocity. x += velocity; world.resolveCollision(volume_, x, y, velocity); // Draw the appropriate sprite. Sprite* sprite = &spriteStand_; if (velocity < 0) sprite = &spriteWalkLeft_; else if (velocity > 0) sprite = &spriteWalkRight_; graphics.draw(*sprite, x, y); } private : InputComponent input_; Volume volume_; Sprite spriteStand_; Sprite spriteWalkLeft_; Sprite spriteWalkRight_; }; |
然后我们把其他的组件都分离:
class PhysicsComponent { public : void update(Bjorn& bjorn, World& world) { bjorn.x += bjorn.velocity; world.resolveCollision(volume_, bjorn.x, bjorn.y, bjorn.velocity); } private : Volume volume_; }; |
class GraphicsComponent { public : void update(Bjorn& bjorn, Graphics& graphics) { Sprite* sprite = &spriteStand_; if (bjorn.velocity < 0) sprite = &spriteWalkLeft_; else if (bjorn.velocity > 0) sprite = &spriteWalkRight_; graphics.draw(*sprite, bjorn.x, bjorn.y); } private : Sprite spriteStand_; Sprite spriteWalkLeft_; Sprite spriteWalkRight_; }; |
现在Bjorn变得很简洁:
class Bjorn { public : int velocity; int x, y; virtual void update(World& world, Graphics& graphics) { input_.update(* this ); physics_.update(* this , world); graphics_.update(* this , graphics); } private : InputComponent input_; PhysicsComponent physics_; GraphicsComponent graphics_; }; |
现在我们已经把不同的组件都分开了,但是Bjorn依然知道这些行为的具体实现。我们将把我们的组件隐藏在借口背后,这样就需要把InputComponent变成一个抽象类:
class InputComponent { public : virtual void update(Bjorn& bjorn) = 0; }; |
然后实现它:
class PlayerInputComponent : public InputComponent { public : virtual void update(Bjorn& bjorn) { switch (Controller::getJoystickDirection()) { case DIR_LEFT: bjorn.velocity -= WALK_ACCELERATION; break ; case DIR_RIGHT: bjorn.velocity += WALK_ACCELERATION; break ; } } private : static const int WALK_ACCELERATION = 1; }; |
我们将持有一个InputComponent的指针,
class Bjorn { public : int velocity; int x, y; Bjorn(InputComponent* input) : input_(input) {} virtual void update(World& world, Graphics& graphics) { input_->update(* this ); physics_.update(* this , world); graphics_.update(* this , graphics); } private : InputComponent* input_; PhysicsComponent physics_; GraphicsComponent graphics_; }; |
现在我们可以传入一个InputComponent来实例化Bjorn:
Bjorn* bjorn = new Bjorn( new PlayerInputComponent()); |
来看看InputComponent的另一个实现:
class DemoInputComponent : public InputComponent { public : virtual void update(Bjorn& bjorn) { // AI to automatically control Bjorn... } }; |
好了,最后让我们看看最简介的一般实现:
我们有两个component:
class PhysicsComponent { public : virtual void update(GameObject& obj, World& world) = 0; }; class GraphicsComponent { public : virtual void update(GameObject& obj, Graphics& graphics) = 0; }; |
一个GameObject:
class GameObject { public : int velocity; int x, y; GameObject(InputComponent* input, PhysicsComponent* physics, GraphicsComponent* graphics) : input_(input), physics_(physics), graphics_(graphics) {} virtual void update(World& world, Graphics& graphics) { input_->update(* this ); physics_->update(* this , world); graphics_->update(* this , graphics); } private : InputComponent* input_; PhysicsComponent* physics_; GraphicsComponent* graphics_; }; |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义