軟件設計模式告訴我們,程序中不同的領域要保持隔離,也就是解耦。所以,我們不希望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_;
};
