游戲開發模式一:組件模式(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_;
};

  

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM