component

Table of Contents

Component

Intent

Allow a single entity to span multiple domains without coupling the domains to each other.

The Pattern

Imagine we have a class that spans multiple domains. We could have a single class manage all of the complexity, or we could split each domain into its own class, and have our class just manage the components.

When to Use It

Splitting out a Domain

class Bjorn {
public:
    Bjorn() : velocity_(0), x_(0), y_(0) {}

    void update(World& world, Graphics& graphics);
private:
    static const int WALK_ACCELERATION = 1;

    int velocity_;
    int x_;
    int y_;
    Volume volume_;
    Sprite spriteStand_;
    Sprite spriteWalkLeft_;
    Sprite spriteWalkRight_;
};
void Bjorn::update(World& world, Graphics& graphics) {
    switch (Controller::getJoystickDirection()) {
        case DIR_LEFT:
            velocity_ -= WALK_ACCELERATION;
            break;
        case DIR_RIGHT:
            velocity_ += WALK_ACCELERATION;
            break;
    }

    x_ += velocity_;
    world.resolveCollision(volume_, x_, y_, velocity_);
    Sprite* sprite = &spriteStand_;
    if (velocity < 0) sprite = &spriteWalkLeft_;
    else if (velocity_ > 0) sprite = &spriteWalkRight_;

    graphics.draw(*sprite, x_, y);
}

Splitting Out a Domain

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;
};
class Bjorn {
public:
    int velocity;
    int x;
    int y;

    void update(World& world, Graphics& graphics) {
        input_.update(*this);

        x += velocity;
        world.resolveCollision(volume_, x, y, velocity);

        Sprite* sprite = &spriteStand_;
        updateSprite(*sprite);
        graphics.draw(*sprite, bjorn.x, bjorn.y)
    }
private:
    void updateSprite(Sprite& sprite) {
        if (velocity < 0) sprite = &spriteWalkLeft_;
        else if (velocity > 0) sprite = &spriteWalkRight_;
    }
    InputComponent input_;
    Volume volume_;

    Sprite spriteStand_;
    Sprite SpriteWalkLeft_;
    Sprite spriteWalkRight_;
};

Let’s keep splitting:

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_;
};

Here’s our Bjorn Class.

class Bjorn
{
public:
  int velocity;
  int x, y;

  void update(World& world, Graphics& graphics)
  {
    input_.update(*this);
    physics_.update(*this, world);
    graphics_.update(*this, graphics);
  }

private:
  InputComponent input_;
  PhysicsComponent physics_;
  GraphicsComponent graphics_;
};

Robo Bjorn

One more step, we can make this better by abstracting out the behavior to interfaces.

class InputComponent {
public:
    virtual ~Inputcomponent() {}
    virtual void update(Bjorn& bjorn) = 0;
};

Next, we have our current user input handling code conform to that interface:

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;
};

Now Bjorn should hold a pointer to that input component instead of having an inline instance:

class Bjorn {
public:
  int velocity;
  int x, y;

  Bjorn(InputComponent* input)
  : input_(input)
  {}

  void update(World& world, Graphics& graphics) {
    input_->update(*this);
    physics_.update(*this, world);
    graphics_.update(*this, graphics);
  }

private:
  InputComponent* input_;
  PhysicsComponent physics_;
  GraphicsComponent graphics_;
};

Now, when we instantiate Bjorn, we pass in an input component for it.

Bjorn* bjorn = new Bjorn(new PlayerInputComponent());

We pay the cost of a virtual function call for our update() functions, but instead, we can swap out the InputComponent at will:

Let’s say we wanted an AI to control Bjorn, like on a Demo screen:

class DemoInputComponent: public InputComponent {
public:
    virtual void update(Bjorn& bjorn) {
        // Allow AI to control Bjorn
    }
};

Now we wire him up with our new component:

Bjorn* bjorn = new Bjorn(new DemoInputComponent());

No Bjorn at all?

Actually, Bjorn is just a component bag. Let’s go further with the physics and graphics and make them interfaces too.

class PhysicsComponent {
public:
  virtual ~PhysicsComponent() {}
  virtual void update(GameObject& obj, World& world) = 0;
};

class GraphicsComponent {
public:
  virtual ~GraphicsComponent() {}
  virtual void update(GameObject& obj, Graphics& graphics) = 0;
};

Rename Bjorn as GameObject:

class GameObject {
public:
  int velocity;
  int x, y;

  GameObject(InputComponent* input,
             PhysicsComponent* physics,
             GraphicsComponent* graphics)
  : input_(input),
    physics_(physics),
    graphics_(graphics)
  {}

  void update(World& world, Graphics& graphics) {
    input_->update(*this);
    physics_->update(*this, world);
    graphics_->update(*this, graphics);
  }

private:
  InputComponent* input_;
  PhysicsComponent* physics_;
  GraphicsComponent* graphics_;
};
class BjornPhysicsComponent : public PhysicsComponent {
public:
  virtual void update(GameObject& obj, World& world) {
    // Physics code...
  }
};

class BjornGraphicsComponent : public GraphicsComponent {
public:
  virtual void update(GameObject& obj, Graphics& graphics) {
    // Graphics code...
  }
};

And now we can build Bjorn again, without even having a dedicated class for him.

GameObject* createBjorn() {
  return new GameObject(new PlayerInputComponent(),
                        new BjornPhysicsComponent(),
                        new BjornGraphicsComponent());
}

How does the object get its components?

  • If the object creates its own components:
    • The object itself ensures it has the components it needs.
    • It’s harder to reconfigure the object.
  • If outside code provides the components:
    • The object becomes flexible
    • It can be decoupled from the concrete component types.

How do components communicate?

  • By modifying the container object’s state.
  • By referring directly to each other.
  • By sending messages There’s no one right answer

Prev: type-object Next: event-queue