0

I'm trying to make a game engine using Gameobject and Components, similar to Unity. I have a Component Class:

class Component
{
public:
    virtual void DoTheThing(){//Doing the thing};
}

A Texture Class derived from Component, with an overridden function:

class Texture : public Component
{
public:
    void DoTheThing(){//Doing a different thing};
}

A GameObject Class with a Component Map which stores derived components:

class GameObject
{
private:
    map<string, Component> components;
    Texture texture;
    components["id"] = texture;

public:    
    Component getComponent(string id){ return components[id];}
}

And finally:

int main(int argc, char* args[])
{
    GameObject gameObject;
    gameObject.getComponent("id").DoTheThing();
}

What I want is to be able to call the DoTheThing() that exists in the derived class, but it only calls the DoTheThing() in the base class.

Tanukibo
  • 13
  • 1

2 Answers2

0

The problem is in your GameObject class.
Let me reduce it to the relevant parts:

class GameObject {
private:
    map<string, Component> components;

    // ...

public:
    Component getComponent(string id){ /* do something and return a component */ }
};

Here you are actually slicing your objects.
That's why the method from the base class is invoked.


If you don't know what's slicing and how to avoid it, I wouldn't recommend you to write something similar to Unity.
I'd rather suggest you to read this Q/A for further details.

Community
  • 1
  • 1
skypjack
  • 49,335
  • 19
  • 95
  • 187
0

Your container is specified to contain Component objects, so when you do components["id"] = texture; you are actually putting a Component object in the map, with the copy constructor invoked on the object texture.

To ensure polymorphism your container must hold pointers, not a predefined class.

class Component
{  public:  virtual void DoTheThing() {cout << "Component\n";} };
class Texture : public Component
{  public:  void DoTheThing() override { cout << "Texture\n"; } };

class GameObject
{   map<string, Component*> components; Texture texture;
    public:
        GameObject() { components["id"] = &texture; }
        Component& getComponent(string id) { return *components[id]; }
        // Remember to return a Component& (or Component*) here, not a
        // Component (by value) because otherwise you would end up with a 
        // base-class Component object copied from the initial Texture
        // object saved in the map.
};

int main(void)
{   GameObject gameObject;
    gameObject.getComponent("id").DoTheThing();
    return 0;
}

OUTPUT: Texture

A.S.H
  • 29,101
  • 5
  • 23
  • 50
  • The original problem is known as [object slicing](http://stackoverflow.com/questions/274626/). Using pointers avoids slicing. And BTW, `Component& getComponent()` will crash if `id` is not already in the `map` since `components[id]` will return a NULL pointer which cannot be dereferenced. If you are going to return a `Component&` reference, you should use `map::find()` instead of `map::operator[]` and throw your own exception if `find()` does not find anything. Otherwise, `getComponent()` should return a `Component*` pointer instead (which the caller will have to validate before using). – Remy Lebeau Dec 04 '16 at 03:32
  • @RemyLebeau your valid observations can make the code better and safer. However the answer focuses on the main issue of the question and hopefully the idea was caught and the OP will acquire such good practice with experience. – A.S.H Dec 04 '16 at 03:42