1
\$\begingroup\$

I am trying to build an Entity Component System for an interactive application developed using C++ and OpenGL. My question is quite simple. In my GameObject class I have a collection of Components. I can add and retrieve components.

class GameObject: public Object
{
public:
    GameObject(std::string objectName);
    ~GameObject(void);
    Component * AddComponent(std::string name);
    Component * AddComponent(Component componentType);
    Component * GetComponent (std::string TypeName);
    Component * GetComponent (<Component Type Here>);
private:
    std::map<std::string,Component*> m_components;

};

I will have a collection of components that inherit from the base Components class. So if I have a meshRenderer component and would like to do the following

GameObject * warship  = new GameObject("myLovelyWarship");
MeshRenderer * meshRenderer =  warship->AddComponent(MeshRenderer);

or possibly

MeshRenderer * meshRenderer =  warship->AddComponent("MeshRenderer");

I could be make a Component Factory like this:

 class ComponentFactory
 {
 public:
     static Component * CreateComponent(const std::string &compTyp)
     {
         if(compTyp == "MeshRenderer")
             return new MeshRenderer;
         if(compTyp == "Collider")
             return new Collider;
         return NULL;
     }
 };

However, I feel like I should not have to keep updating the Component Factory every time I want to create a new custom Component but it is an option. Is there a more proper way to add and retrieve these components? Is standard templates another solution?

\$\endgroup\$
2
  • \$\begingroup\$ Adding component or new entity type (warship2 with different model)? \$\endgroup\$
    – wondra
    Commented Aug 21, 2014 at 20:28
  • \$\begingroup\$ I am asking about adding new component types to an entity/game object if thats what you mean. \$\endgroup\$
    – John
    Commented Aug 21, 2014 at 20:35

1 Answer 1

2
\$\begingroup\$

Templates could work:

template <typename ComponentType>
static Component * CreateComponent () {
  return new ComponentType();
}

However, that means all components must be created based on compile time data. Honestly, at that point you may as well just do

myGameObject->AddComponent(new MeshComponent()); 

instead of bothering with a factory, which would look almost the same:

myGameObject->AddComponent(ComponentFactory::CreateComponent<MeshComponent>());

If you need to be able to create component types based on runtime data, you'll have to use some kind of runtime key -- a string will suffice, as in your example (although other options may result in faster lookup time).

To avoid the problem of having to modify the factory every time in this case, provide a way to delegate the actual creation to something outside the factory. A function pointer is an extremely basic way to do this. For example:

typedef Component * (* CreateComponentFunction)();

std::map<std::string, CreateComponentFunction> m_delegates;

// ...

static Component * CreateComponent (const std::string & type) {
  return m_delegates[type]();
}

You could use std::function instead if you wanted, or an abstract interface... et cetera. The point is to move the actual instantiation into some delegate you can implement with the new kind of component you are authoring.

Then, you implement some kind of "registration" method in the factory:

void Register (const std::string & type, CreateComponentFunction delegate) {
  m_delegates[type] = delegate;
}

Then you can register the binding between strings keys and delegate functions at game startup, or somewhere -- somewhere higher-level than the component factory, so you no longer need to modify it when you add new components. For example, if a running instance of your game was represented by the MyGame class, you might register all your components during startup:

void MyGame::Start () {
  m_componentFactory = new ComponentFactory();
  m_componentFactory->Register("RigidBody", &RigidBodyComponent::Create);
  ...

  // ...here you could also call into your script-based hook for registering
  // component types as well, if you choose to build such a thing...
}

You can continue to expand upon this delegate technique in the same way and even move the registration out of code, into script files (as alluded to above), if you have a suitably robust scripting system integrated.

Unfortunately you'll never really get away from the task of having to make some kind of association between your runtime key data and the (compile-time) code that creates the appropriate, desired instances. It's simply a matter of choosing the ideal level of abstraction to do so. Moving it out of the "factory" is important, since it means that association is at least at the level of the component type you're implementing anyway, and also means the factory itself could be part of common library code you use across your projects.

\$\endgroup\$
6
  • \$\begingroup\$ Hi Josh. I would like to be able to derive custom components at runtime indeed. In that case, I was thinking about integrating Lua into my project but that is going to be a bit further down the line. Could you perhaps give me a more concrete example of how to use the method you proposed above? Say I want to add a RigidBody Component. How would you register it? I'm also thinking that a game engine should have some of these components hardcoded in a sense so the standard factory method and/or templates would apply. \$\endgroup\$
    – John
    Commented Aug 21, 2014 at 22:08
  • \$\begingroup\$ Where would you have the delegate functions? Can I have a function called Create in the base Component class that each derived Component would have to implement? and pass that function as the CreateComponentFunction? \$\endgroup\$
    – John
    Commented Aug 21, 2014 at 22:56
  • \$\begingroup\$ I put an example of what I mean by "doing it at startup" in the answer; as for where you put the delegate: anywhere you like. I'd chose to place it with the component class itself in many cases. Making it an abstract member function of the component class itself is perhaps an unusual choice, since it requires you to instantiate one of the components just to call that function, so I wouldn't recommend that. But a static method, like I used in the "MyGame::Start" example -- that would work. \$\endgroup\$
    – user1430
    Commented Aug 21, 2014 at 23:29
  • \$\begingroup\$ So you suggest having a static Component * RigidBodyComponent::Create() method which just returns a new RigidBodyComponent? How can you ensure that each derived component has this Create method? \$\endgroup\$
    – John
    Commented Aug 21, 2014 at 23:48
  • \$\begingroup\$ It's a simple way, yes. You can't ensure components have this method -- but that isn't strictly needed. All that's needed is for the component to provide you some way to create it which matches the interface that Register expects. If it doesn't provide that, you can't use it. \$\endgroup\$
    – user1430
    Commented Aug 22, 2014 at 0:28

You must log in to answer this question.

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