Allow a single entity to span multiple domains without coupling the domains to each other.
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.
You have a class that touches multiple domains which you want to keep decoupled from each other.
A class is getting massive and hard to work with.
You want to be able to define a variety of objects that share different capabilities, but using inheritance doesn’t let you pick the parts you want to reuse precisely enough.
class Bjorn {
public:
() : velocity_(0), x_(0), y_(0) {}
Bjorn
void update(World& world, Graphics& graphics);
private:
static const int WALK_ACCELERATION = 1;
int velocity_;
int x_;
int y_;
volume_;
Volume spriteStand_;
Sprite spriteWalkLeft_;
Sprite spriteWalkRight_;
Sprite };
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_;
.resolveCollision(volume_, x_, y_, velocity_);
world* sprite = &spriteStand_;
Spriteif (velocity < 0) sprite = &spriteWalkLeft_;
else if (velocity_ > 0) sprite = &spriteWalkRight_;
.draw(*sprite, x_, y);
graphics}
class InputComponent {
public:
void update(Bjorn& bjorn) {
switch (Controller::getJoystickDirection()) {
case DIR_LEFT:
.velocity -= WALK_ACCELERATION;
bjornbreak;
case DIR_RIGHT:
.velocity += WALK_ACCELERATION;
bjornbreak;
}
}
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);
+= velocity;
x .resolveCollision(volume_, x, y, velocity);
world
* sprite = &spriteStand_;
Sprite(*sprite);
updateSprite.draw(*sprite, bjorn.x, bjorn.y)
graphics}
private:
void updateSprite(Sprite& sprite) {
if (velocity < 0) sprite = &spriteWalkLeft_;
else if (velocity > 0) sprite = &spriteWalkRight_;
}
input_;
InputComponent volume_;
Volume
spriteStand_;
Sprite ;
Sprite SpriteWalkLeft_spriteWalkRight_;
Sprite };
Let’s keep splitting:
class PhysicsComponent
{
public:
void update(Bjorn& bjorn, World& world)
{
.x += bjorn.velocity;
bjorn.resolveCollision(volume_,
world.x, bjorn.y, bjorn.velocity);
bjorn}
private:
volume_;
Volume };
class GraphicsComponent
{
public:
void update(Bjorn& bjorn, Graphics& graphics)
{
* sprite = &spriteStand_;
Spriteif (bjorn.velocity < 0)
{
= &spriteWalkLeft_;
sprite }
else if (bjorn.velocity > 0)
{
= &spriteWalkRight_;
sprite }
.draw(*sprite, bjorn.x, bjorn.y);
graphics}
private:
spriteStand_;
Sprite spriteWalkLeft_;
Sprite spriteWalkRight_;
Sprite };
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:
input_;
InputComponent physics_;
PhysicsComponent graphics_;
GraphicsComponent };
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:
.velocity -= WALK_ACCELERATION;
bjornbreak;
case DIR_RIGHT:
.velocity += WALK_ACCELERATION;
bjornbreak;
}
}
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;
(InputComponent* input)
Bjorn: input_(input)
{}
void update(World& world, Graphics& graphics) {
input_->update(*this);
physics_.update(*this, world);
graphics_.update(*this, graphics);
}
private:
* input_;
InputComponentphysics_;
PhysicsComponent graphics_;
GraphicsComponent };
Now, when we instantiate Bjorn, we pass in an input component for it.
* bjorn = new Bjorn(new PlayerInputComponent()); Bjorn
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 = new Bjorn(new DemoInputComponent()); Bjorn
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;
(InputComponent* input,
GameObject* physics,
PhysicsComponent* graphics)
GraphicsComponent: input_(input),
physics_(physics),
graphics_(graphics)
{}
void update(World& world, Graphics& graphics) {
input_->update(*this);
physics_->update(*this, world);
graphics_->update(*this, graphics);
}
private:
* input_;
InputComponent* physics_;
PhysicsComponent* graphics_;
GraphicsComponent};
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.
* createBjorn() {
GameObjectreturn new GameObject(new PlayerInputComponent(),
new BjornPhysicsComponent(),
new BjornGraphicsComponent());
}
Prev: type-object Next: event-queue