16
\$\begingroup\$

I'm writing a shooter (like 1942, classic 2D graphics) and I'd like to use a component based approch. So far I thought about the following design:

  1. Each game element (airship, projectile, powerup, enemy) is an Entity

  2. Each Entity is a set of components which can added or removed at run-time. Examples are Position, Sprite, Health, IA, Damage, BoundingBox etc.

The idea is that Airship, Projectile, Enemy, Powerup are NOT game classes. An entity is only defined by the components it owns (and that can change during time). So the player Airship starts with Sprite, Position, Health and Input components. A powerup has the Sprite, Position, BoundingBox. And so on.

The main loop manages the game "physics", i.e. how the components interact each other:

foreach(entity (let it be entity1) with a Damage component)
    foreach(entity (let it be entity2) with a Health component)
    if(the entity1.BoundingBox collides with entity2.BoundingBox)
    {
        entity2.Health.decrease(entity1.Damage.amount());
    }

foreach(entity with a IA component)
    entity.IA.update(); 

foreach(entity with a Sprite component)
    draw(entity.Sprite.surface()); 

...

Components are hardcoded in the main C++ application. Entities can be defined in an XML file (the IA part in a lua or python file).

The main loop doesn't care a lot about entities: it only manages components. The software design should allow to:

  1. Given a component, get the entity it belongs to

  2. Given an entity, get the component of type "type"

  3. For all entities, do something

  4. For all entity's component, do something (e.g: serialize)

I was thinking about the following:

class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component {...};
class Position : public Component {...};
class IA : public Component {... virtual void update() = 0; };

// I don't remember exactly the boost::fusion map syntax right now, sorry.
class Entity
{
   int id; // entity id
   boost::fusion::map< pair<Sprite, Sprite*>, pair<Position, Position*> > components;
   template <class C> bool has_component() { return components.at<C>() != 0; }
   template <class C> C* get_component() { return components.at<C>(); }
   template <class C> void add_component(C* c) { components.at<C>() = c; }
   template <class C> void remove_component(C* c) { components.at<C>() = 0; }
   void serialize(filestream, op) { /* Serialize all componets*/ }
...
};

std::list<Entity*> entity_list;

With this design I can get #1, #2, #3 (thanks to boost::fusion::map algorithms) and #4. Also everything is O(1) (ok, not exactly, but it's still very fast).

There is also a more "common" approch:

class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component { static const int type_id = 0; };
class Position : public Component { static const int type_id = 1; };

class Entity
{
   int id; // entity id
   std::vector<Component*> components;
   bool has_component() { return components[i] != 0; }
   template <class C> C* get_component() { return dynamic_cast<C> components[C::id](); } // It's actually quite safe
...
};

Another approch is to get rid of the Entity class: each Component type lives in its own list. So there is a Sprite list, a Health list, a Damage list etc. I know they belong to the same logic entity because of the entity id. This is simpler, but slower: the IA components needs access basically to all other entity's components and that would require searching each other component's list at each step.

Which approch do you think is better? is boost::fusion map suited to be used in that way?

\$\endgroup\$
1
  • 2
    \$\begingroup\$ why a downvote? What's wrong with this question? \$\endgroup\$
    – Emiliano
    May 13, 2011 at 17:12

2 Answers 2

6
\$\begingroup\$

I've found that component-based design and data-oriented design go hand in hand. You say that having homogeneous lists of components and eliminating the first-class entity object (instead opting for an entity ID on components themselves) will be "slower", but that's neither here nor there since you haven't actually profiled any real code that implements both approaches to arrive at that conclusion. As a matter of fact, I can almost guarantee you that homogenizing your components and avoiding the traditional heavy virtualization will be faster due to the various advantages of data-oriented design -- easier parallelization, cache utilization, modularity, etc.

I'm not saying this approach is ideal for everything, but component systems which are basically collections of data that need the same transformations performed upon them every frame, simply scream to be data-oriented. There will be times when components need to communicate with other components of different types, but this is going to be a necessary evil either way. It shouldn't drive the design, however, since there are ways to solve this issue even in the extreme case that all components are processed in parallel such as message queues and futures.

Definitely Google around for data-oriented design as it relates to component-based systems, because this topic comes up a lot and there is quite a bit of discussion and anecdotal data out there.

\$\endgroup\$
4
  • \$\begingroup\$ what do you mean by "data-oriented"? \$\endgroup\$
    – Emiliano
    May 13, 2011 at 21:14
  • \$\begingroup\$ There is a lot of information on Google, but here is a decent article that popped up which should provide a high-level overview, followed by a discussion as it relates to component systems: gamesfromwithin.com/data-oriented-design, gamedev.net/topic/… \$\endgroup\$ May 13, 2011 at 22:10
  • \$\begingroup\$ i can't agree with all the thing sied about DOD, since i think it can't be complete itself, i mean only DOD can suggest a very good aprroch for storing data but for calling functions and procedures you need to use either procedural or OOP approch, I mean the problem is how to combine these two method to take the most benefit both for performance and coding easiness, eg. in the structure i suggest there will be performance problem when all the entities doesn't share some components but it can easily be solved using DOD, you only have to make diffrent arrays for diffrent types of entieties. \$\endgroup\$
    – Ali1S232
    May 14, 2011 at 4:23
  • \$\begingroup\$ This doesn't answer my question directly but it's very informative. I remembered something about Dataflows back in my University days. It's the best answer so far, and it "wins". \$\endgroup\$
    – Emiliano
    May 18, 2011 at 6:34
-1
\$\begingroup\$

if i were to write such a code i would rather ot use this approch (and i'm not using any boost if it's important for you), since it can do all the thing you want but the problem is when there are too many enteties which do not share some componnet, finding those which have it will consume some time. other than that there is no other problem i can thing of :

// declare components here------------------------------
class component
{
};

class health:public component
{
public:
    int value;
};

class boundingbox:public component
{
public :
    int left,right,top,bottom;
    bool collision(boundingbox& other)
    {
        if (left < other.right || right > other.left)
            if (top < other.bottom || bottom > other.top)
                return true;
        return false;
    }
};

class damage : public component
{
public:
    int value;
};

// declare enteties here------------------------------

class entity
{
    virtual int id() = 0;
    virtual int size() = 0;
};

class aircraft :public entity, public health,public boundingbox
{
    virtual int id(){return 1;}
    virtual int size() {return sizeof(*this);};
};

class bullet :public entity, public damage, public boundingbox
{
    virtual int id(){return 2;}
    virtual int size() {return sizeof(*this);};
};

int main()
{
    entity* gameobjects[3];
    gameobjects[0] = new aircraft;
    gameobjects[1] = new bullet;
    gameobjects[2] = new bullet;
    for (int i=0;i<3;i++)
        for(int j=0;j<3;j++)
            if (dynamic_cast<boundingbox*>(gameobjects[i]) && dynamic_cast<boundingbox*>(gameobjects[j]) &&
                dynamic_cast<boundingbox*>(gameobjects[i])->collision(*dynamic_cast<boundingbox*>(gameobjects[j])))
                if (dynamic_cast<health*>(gameobjects[i]) && dynamic_cast<damage*>(gameobjects[j]))
                    dynamic_cast<health*>(gameobjects[i])->value -= dynamic_cast<damage*>(gameobjects[j])->value;
}

in this approch every component is a base for an entity so given the component it's pointer is also an entity! the second thing you ask for is to have a direct access to some entity's components eg. when i need to access damage in one of my entities i use dynamic_cast<damage*>(entity)->value, so if entity has damage component it will return the value. if you are not sure whether entity has component damage or not you can easily check if (dynamic_cast<damage*> (entity)) return value of dynamic_cast is always NULL if the cast is not valid and a same pointer but with the requested type if it's valid. so to do something with all the entities which has some component you can do it like below

for (int i=0;i<enteties.size();i++)
    if (dynamic_cast<component*>(enteties[i]))
        //do somthing here

if there any other questions i'll be glad to answer.

\$\endgroup\$
5
  • \$\begingroup\$ why did i get the down vote? what was wrong with my solution? \$\endgroup\$
    – Ali1S232
    May 15, 2011 at 15:26
  • 3
    \$\begingroup\$ Your solution is not really a component based solution as components are not separated from your game classes. Your instances all relies on the IS A relation (inheritance) instead of a HAS A relation (composition). Doing it the composition way (entities addresses several components) gives you a lot of advantages over an inheritance model (which is usually why you use components). Your solution gives none of the benefits of a component based solution and introduces some quirks (multiple inheritance etc). No data locality, no separate component update. No runtime modification of components. \$\endgroup\$
    – void
    May 15, 2011 at 15:37
  • \$\begingroup\$ first of all the question asks for structure that every component instance is only related to one entity, and you can activate and deactivate components by only adding a bool isActive to base commponent class. there is still need for introduction of usable components when you are defining enteties but i don't consider that as a problem, and still you have seprate componnent updates(remember somthing like dynamic_cast<componnet*>(entity)->update(). \$\endgroup\$
    – Ali1S232
    May 17, 2011 at 19:03
  • \$\begingroup\$ and i agree there will still be a problem when he wants to have component which can share data but considering what he asked for i guess there won't be a problem for that, and again there are some tricks for that problem too that if you want i can explain. \$\endgroup\$
    – Ali1S232
    May 17, 2011 at 19:07
  • \$\begingroup\$ While I agree that it's possible to implement it this way, I don't think it's a good idea. You designers cannot compose objects themselves, unless you have one über class which inherits all possible components. And while you can call update on just one component, it will not have good in-memory layout, in a composed model all component instances of the same type can be kept close in memory and iterated over without any cache misses. You are also relying on RTTI, which is something that is usually turned off in games due to performance reasons. A good sorted object layout fixes that mostly. \$\endgroup\$
    – void
    May 17, 2011 at 19:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .