Simple game object / component system : part 1

In the engine I’m coding, I got to the part where I needed to write what is commonly called the game object system or game object model. Typically in games, there are a variety of object types; rocket launchers, giant orcs, starving pelicans, and of course, crates. The game object model provides an architecture by which all of these objects can be created, destroyed, updated, looked up, and so on.

Before I got started, I clicked through some old gdc slide decks, and thumbed through some of the books in my technical library. What I found was that there were quite a few ways to go when it comes to game object models, but there were two prevalent models: a game object was either started as an abstract base class that was sub-classed and extended to make a concrete object type, or was a simple object that wasn’t sub-classed at all, and concrete object types were built out of a variety of component classes that each represented different behavior.

After shipping a few games, I have been exposed to a couple different game object models, but nothing that departs from either of the paradigms mentioned above. Ultimately though, for my own engine I wanted to replicate some aspects of the way Unity3D works. The workflow in the editor is so streamlined and it really is easy to get things up and running.

The features I wanted in my game object model were:

  • Data declaration through script
    • Similar to Torque3D’s datablocks
    • A template or blueprint for an object’s component list, which gets instanced
    • Instances are placed in a map editor
    • The template gets sent across the network only once
  • Both scripted and native c++ components
  • Messages sent/received/handled in script or native code
  • Game object itself is very lightweight, maybe just a guid and a transform
    • Game objects can be extended in script with key/value pairs of properties

Once I started the work, I thought it would be a good opportunity for a series of posts.

Let’s start with the code that defines a game object:
gameobject.h

#pragma once

#include <ObjBase.h> // GUID
#include "eastl/list.h"

class c_component;
class c_transform_component;


// A game object is an object that can be placed in our game world
// each created game object has a unique identifier
// game objects can have components attached to themselves.
// by default, all game objects have a transform component attached
// the transform is the only component the gameobject 'knows' about.
class c_gameobject
{
public:
    c_gameobject(); 
   ~c_gameobject();	

    // game objects get updated every tick
    // inside our game object update we will give attached components
    // a chance to update.
    void update(double dt);

    // game objects expose a templated add_component method so that 
    // our game object doesn't need to know what kind of components 
    // exist.
    template <class t_component> 
    t_component* add_component();

    // our get_component method is also templated, again so that 
    // our game object doesn't need to know what kind of components
    // exist
    template <class t_component> 
    t_component* get_component();	
	
    // internally, our game object will keep a list of the components
    // that are attached.  You can use any flavor of list.
    // I recently switched from rolling my own generic containers to
    // using eastl.  
    typedef eastl::list<c_component*> 
    t_component_list;
	
    const t_component_list* get_components() const 
    {
        return &m_component_list;
    }
	
    // all game objects in our world need to be uniquely identifiable
    // for this purpose, I am using a guid.  You can use any mechanism
    // that guarantees the game object will be unique.
    inline GUID get_guid() const 
    {
        return m_guid;
    }	
	
    // here are a couple of operators used for comparing one game object
    // against another for equality.
    bool operator ==(const c_gameobject& rhs) const;
    bool operator !=(const c_gameobject& rhs) const;

protected:
	
private:
    // this could pobably be public, but using get_component
    // calls this anyways, so it would be redundant for users to
    // ask has_component and then call get_component
    // therefore get_component just returns NULL if the component
    // type in question is not attached.
    template <class t_component> 
    bool has_component();

    GUID m_guid;
    t_component_list m_component_list;

    // we need not necessarily store this, but it's more efficient
    // speed wise to just access it this way rather than calling
    // get_component internally, and even if we were doing it that way
    // we would be asking for a c_transform_component then, so it's
    // better to just store it imo.	
    c_transform_component* m_transform;
};

// our templated methods need to go inside the .h file.

// in c# you can do stuff like class MyClass<T> where T : SomeClass
// I would love to know how I can ensure that t_component is derived
// from c_component
// if anyone knows how to do that, shoot me an email: jspataro@gmail.com
template <class t_component>
t_component* c_gameobject::add_component()
{
    // interesting.
    // I didn't want all the overhead of a singleton for this
    // what this is, is a place where components register themselves
    // as a unique id (guid) and a creation function pointer
    // the creation function just needs to return a pointer to the
    // new component
    // so the component registry is just a hash table where guids
    // map to creator functions.
    extern t_component_registry g_component_registry;

    // for now, only one of any type of component can be attached
    // to a game object. Later on when we add scripted components
    // we'll allow more than one of those to be attached.
    // this is meant to prevent two transforms/renderers, etc.
    // from being attached because what would that mean aynways?
    // And, what would be the best way to resolve that abiguity?
    t_component* found_component= get_component<t_component>();

    if (found_component)
    {
        // so we already had a component of this type attached
        // just return it.
        return found_component;
    }

    // get the creator function from our hash table using the component's
    // guid as the key.
    // you can use whatever flavor of hashtable or map you prefer.
    const component_creator* creator= g_component_registry.get_value(t_component::type());

    // we don't know what kind of component we are creating until 
    // later, so our creator functions actually give back a pointer
    // which is cast to a c_component (the superclass of all components)
    c_component* new_component = NULL;

    // I'm not checking this against validity because internally, my hashtable
    // will barf if asked for a key that doesn't exist.
    new_component= (*creator)();

    // actually creating the component, however, could fail
    // if we were out of memory, for example...	
    if (new_component)
    {
        // components know which game object they are attached to
        new_component->gameobject= this;
		
        m_component_list.push_back(new_component);

        // finally, we want to give back the pointer cast to the
        // actual component type we are creating/attaching.
        return static_cast<t_component*>(new_component);
    }

    return NULL;
}

// walk the list of components
// check their type against the type we are looking for
// return a pointer to the component if found
// otherwise, return NULL
template <class t_component>
t_component* c_gameobject::get_component()
{
    t_component *result= NULL;	
	
    eastl::list<c_component*>::iterator component_iterator= m_component_list.begin();

    for ( ; component_iterator != m_component_list.end(); ++component_iterator)
    {
        // the eastl list is holding pointers to components, so the first check
        // where we dereferece the iterator is actually checking to see if the 
        // pointer to the component is not NULL
        if ((*component_iterator) && (*component_iterator)->get_instance_type() == t_component::type())
        {
            // we always cast to the pointer type we're looking for
            result= static_cast<t_component*>((*component_iterator));
            break;
        }		
    }

    return result;
}

// walk the component list
// check their type against the type we are looking for
// return true if found, otherwise false
template <class t_component>
bool c_gameobject::has_component()
{
    bool result= false;

    eastl::list<c_component*>::iterator component_iterator= m_component_list.begin();

    for ( ; component_iterator != m_component_list.end(); ++component_iterator)
    {
        if ((*component_iterator) && (*component_iterator)->get_instance_type() == t_component::type())
        {
            result= true;
            break;
        }
    }

    return result;
}

and gameobject.cpp

#include "gameobject.h"
#include "component.h"
#include "transform.h"

c_gameobject::c_gameobject() 
{
    // game objects are just a unique identifier
    // and a transform
    CoCreateGuid(&m_guid);	

    // all game objects have at least a transform.
    m_transform= add_component<c_transform_component>();
}

// iterate over each component and call its destruct method
c_gameobject::~c_gameobject()
{
    eastl::list<c_component*>::iterator component_iterator= m_component_list.begin();
	
    while (!m_component_list.empty())
    {
        if ((*component_iterator))
        {
            (*component_iterator)->destruct();
        }

        if (++component_iterator == m_component_list.end())
        {
            m_component_list.clear();
        }
    }
}

bool c_gameobject::operator ==(const c_gameobject& rhs) const 
{
    // which one of these is the right way?

    // this one uses memcmp, AND, if I want to return a bool, 
    // this gets converted from an int...which isn't super efficient
    //return (guid == rhs.get_guid());

    // I not certain that any object's data1 values would ever be 
    // identical if they were different objects...
    return (m_guid.Data1 == rhs.get_guid().Data1);
}

bool c_gameobject::operator !=(const c_gameobject& rhs) const
{
    return (m_guid.Data1 != rhs.get_guid().Data1);
}

void c_gameobject::update(double dt)
{
    eastl::list<c_component*>::iterator component_iterator= m_component_list.begin();

    for ( ; component_iterator != m_component_list.end(); ++component_iterator)
    {
        (*component_iterator)->update(dt);		
    }
}

In the next part, we’ll tackle components

Leave a Reply

Your email address will not be published.