Component Based Entity System Design Part 1

by Nox

on 04.24.09

This post is OLD. Learn what you will from it, but I don't necessarily condone it at the present time. It has been preserved due to high demand. Some links may be broken.

About a week ago, I decided that if I needed to dynamic_cast my entities everywhere they were used, I was doing something wrong. It’s not that an inheritance based entity system is inherently wrong per se, but in large systems it tends to become an impossible to manage web of sloppy code. Seeing this issue, and wanting to make Harmony the best it can be, I began to tackle the monstrous job of refactoring the entity code to a component based design.

I read the available literature (which is sparse, as it always is with entity systems):

and got to work.

In my opinion, the implementation in Game Programming Gems 6 is too rigid and overly complex, 2 things that I'm trying to escape in Harmony. So after struggling to wrap my head around how to get it to work, since, if it’s in a book, it MUST be the best…I gave up, recognized that ingenuity is the key to success and created an original component based entity system design.

This is part 1 of a 2 part series, in this post I will review the Component class itself aswell as the Entities that hold them.

Part 2 will cover the Component Manager.

In most games, entities are derived from a series of concrete base classes. These base classes provide the ability to render, move, posses AI, etc. This system of inheritance quickly becomes unwieldy in practice and devolves into a mass of dynamic_casts and spaghetti code. Also, this system does not lend itself well to data driven game design, as all possible entities and the interfaces that they implement must be specified at compile time. Optimally, in modern games, we want a system in which we can create entities dynamically on the fly at runtime. This not only makes the compile/debug cycle a non-issue for designers, but also opens up a realm of possibilities including: easy post-release patches that add new content through pure data, Spore-like creature creation, and simple user generated content.

An alternative to the standard architecture, which has come into the spotlight only recently, is the component based architecture. In this system, there is typically only 1 very generic Entity class that serves to hold the Components that implement its functionality. An Entity with hit-points would contain the HealthComponent, which would implement all of the methods relevant to dealing with hit points. This greatly increases the cleanliness of Entity’s direct interface and moves it all out into easy to manage and fully pluggable classes of functionality.

My implementation of this system is as follows:

//Component.h
class Component {

public:

    Component();
    ~Component();

    virtual void Update() = 0;

    std::string GetFamily();
    std::string GetName();

    void    SetOwner(Entity*);
    Entity* GetOwner();

    virtual void Set(std::map<std::string,boost::variant<int,std::string> >) = 0;

protected:
    std::string m_familyName;
    std::string m_name;
    Entity* m_owner;

};

To create components, one must simply derive a type from this base class which implements all the base facilities that a component may need. The Set() function is used in conjunction with XML to give instance specific values to the properties of a component. For example, here is a snippet from the implementation of HealthComponent.

HealthComponent::HealthComponent() {
    m_name = "BasicHealth";
    m_familyName = "Health";
    hp = 100;
    max = 100;
    min = 0 ;
}

void HealthComponent::Set(std::map<std::string,boost::variant<int,std::string> > props){
   //a variant is used because data can only be one of 2 things
    hp = boost::get<int>(props["hp"]);
    max = boost::get<int>(props["max"]);
    min = boost::get<int>(props["min"]);
 }

HealthComponent’s members are given sensible initial values in the contructor, if one wishes to use this component with different data, one can simply define a component template in XML (shown in part 2 of this article) which will override these default values. For instance, the following template would be suitable for a tank character:

<health>
  <tank>
    <name strval="TankHealth"/>
    <hp  intval="1000"/>
    <max intval="1000"/>
    <min intval="0"/>
  </tank>
</health>

 

//Entity.h
class Entity {

public:

    Entity();
    Entity(std::string name);
    ~Entity();

    unsigned int &GetID();
    std::string  &GetName();

    void Update();

    void AddComponent(Component*);
    void RemoveComponent(std::string);

    template<typename T>
    T * GetComponent(std::string familyName) {
        return dynamic_cast<t*>(m_components[familyName]);
    }

    void Do(std::string);

    SDL_Rect &GetLocation();

    unsigned int m_id;
    std::string m_name;
    static unsigned int nextID;
    SDL_Rect m_location;
    std::map<int,std::string> m_keymap;
    std::map<char,std::string> m_mousemap;
    std::map<std::string, Component*> m_components;

};

As you can see, an entity is little more than a container of components. In addition to components, it holds a name and position. Also, all entities are inherently able to accept input, so that is the source of m_keymap, m_mousemap, and Do(std::string). Components are held in m_components and are indexed by their family name. GetComponent(std::string) is a templated function that returns a properly pre-casted component of the family name.

Do note that the system in its current form is very bare bones, entirely lacks error checking, and is subject to sweeping changes. Stay tuned for part 2, and as always <3.