0

To create a game object dynamically, I take the ObjectTypeID, which is unsigned int, and let it compare within very long switch() statement. If the appropriate swith case is found, I create the object and store it. Because I already have 90 Game Objects, the Switch() is already very long and will be growing to about 300 objects.

To avoid the extra long switch() statement, and to improve speed, the perfect candidate would be taking advantage of indexed array to store all the object types (ObjectTypeID increases from 0 upward). Is there a way, how to store Object Type within an array?

I would like to use something like this:

  • aObjectTypesArray[ObjectTypeID] *pNewDynamicObject = new aObjectTypesArray[ObjectTypeID];

Can you advise me, please, how to take advantage of dynamic array indexing in my case, and how to avoid extra long switch() statement? Your advice might differ from my idea, the key is to use array indexing and to remove long switch() statement.

Bunkai.Satori
  • 4,698
  • 13
  • 49
  • 77

3 Answers3

8

In c++, classes are not first-class objects, so you cannot directly do what you want. But, if the game objects inherit from a common base class, you simply need to use a factory.

class GameObject {
};

class GameObjectFactory {
public:
  virtual GameObject * create() = 0;
};

class SomeGameObject : public GameObject {
};

class SomeGameObjectFactory : public GameObjectFactory {
  virtual GameObject * create() { return new SomeGameObject; }
};

Then store factory instances in your array.

Erik
  • 88,732
  • 13
  • 198
  • 189
  • The Factory pattern is almost certainly the way to go. Personally I would be less happy about using IDs as indices into an array but for speed I doubt you can beat it. – AAT Feb 23 '11 at 11:21
  • 1
    Typical Javaism. In C++ factory **functions** work fine, so you don't want to create a class with virtual method. You'll save some pointer dereferencing and loose nothing. – Jan Hudec Feb 23 '11 at 12:01
  • If the original poster can't use factory function pointers as IDs instead of the current integers (e.g. because he needs stable IDs for loading from a savegame), this is simply not going to help a tiny bit as he'll still need to convert the ID to the factory. – Jan Hudec Feb 23 '11 at 12:04
  • @Jan: As the question and answer states, lookup is done by using the ID as an array index. – Erik Feb 23 '11 at 12:12
  • @Erik: +1 for great answer. Using object factories is new to me and would perfectly work. If I still insist on using index arrays, would the following work?: `GameObject *pParentGameObject = new(aObejctSize[nObjectTypeID].nSize_t);` I reviewed the [new](http://www.cplusplus.com/reference/std/new/operator%20new/) operator and to me, it looks, that this could be a superfast solution. – Bunkai.Satori Feb 23 '11 at 12:56
  • @Jan, @Erik: yes, imagine that I load a scene map. All I've got is an ObjectTypeID and initial object state (position, rotation, another game parameters..) My question was directed mainly to how to efficiently convert the ObjectTypeID (unsigned integer) into object type, while avoiding Switch(). Logical idea was to use array indexing, but still there was a problem with what information to keep within the array, so it will be possible to instantiate objects based on that array records. – Bunkai.Satori Feb 23 '11 at 13:50
  • @Bunkai: You're more or less describing a perfect use case for the factory pattern, http://en.wikipedia.org/wiki/Factory_method_pattern - Whether you use factory classes or functions doesn't really matter as long as the chosen method is flexible enough to solve your specific problem – Erik Feb 23 '11 at 14:00
  • @Erik: your answer above looks like the *Accepted Answer* and apparently most people will agree, as it has gained the highest amount of votes. I hear a lot about **programming design patterns**. Would you be willing to share why are design patterns so popular? I have read about them, and Design Patterns look to me as a code fragments that are useful in many areas in programming, therefore can be often reused. In other words, design patterns look to me as a library, so what is the difference? – Bunkai.Satori Feb 23 '11 at 14:24
  • Patterns aren't code fragments, they're concepts/methodologies. They're useful because they clearly define scenarios every programmer has seen, and name/formalize ways of solving these scenarios. I'd strongly recommend reading "Design Patterns: Elements of Reusable Object-Oriented Software" while avoiding the trap of applying patterns to everything :) – Erik Feb 23 '11 at 14:24
  • @Erik: I see.. Design Patterns are recommended approaches and solutions to frequently encountered programming issues. In my spare time I will read that book. – Bunkai.Satori Feb 24 '11 at 14:41
2

An easier way to create factories is to use a template to create the factory method and then store it using a function pointer or perhaps boost::function or std::function if they're available.

For example, given the objects:

#include <iostream>

struct GameObject {
    virtual ~GameObject() {}
    virtual void foo() const = 0;
};

struct ExampleObject : GameObject {
    void foo() const { std::cout << "ExampleObject::foo\n"; }
};

We can use a template to define a generic object factory:

template <typename Object>
GameObject* object_factory() const
{
    return new Object();
};

Define a vector to store function pointers to factory methods:

#include <vector>

typedef GameObject*(*factory_ptr)();
std::vector<factory_ptr> factories;

Then add an object to the factories using something like:

int example_object_id = factories.size();
factories.push_back(&object_factory<ExampleObject>);

And then later create the object with:

GameObject* obj = factories[example_object_id]();

Here's a complete example:

#include <iostream>
#include <vector>

template <typename BaseObject>
class game_object_factory
{
    template <typename Object>
    static BaseObject* create_object()
    {
         return new Object();
    };

    typedef BaseObject*(*factory)();
    std::vector<factory> factories;

public:
    template <typename Object>
    int register_type()
    {
        int index = factories.size();
        factories.push_back(&create_object<Object>);
        return index;
    }

    BaseObject* create(int id) const {
        return factories[id]();
    }
};

struct GameObject {
    virtual ~GameObject() {}
    virtual void foo() const = 0;
};

struct Example1 : GameObject {
    void foo() const { std::cout << "Example1::foo\n"; }
};

struct Example2 : GameObject {
    void foo() const { std::cout << "Example2::foo\n"; }
};

int main() {
    game_object_factory<GameObject> factory;

    int obj1_id = factory.register_type<Example1>();
    int obj2_id = factory.register_type<Example2>();

    // Should use a smart pointer here to simplify memory management.
    GameObject* obj = factory.create(obj2_id);
    obj->foo();
    delete obj;
}
Daniel James
  • 3,899
  • 22
  • 30
  • thank you for your addition. I see, that the key role here is played by object templates. However, imagine, that I read a saved file or a scene map. All I have is ObjectID and object state information. I have to change the ObjectID integer to object type. My question was about a way how to efficiently convert Object ID into real datatype while avoiding Switch() operator. – Bunkai.Satori Feb 23 '11 at 13:47
  • Since the factory methods are stored in a vector, you can just call the index of the method as your ObjectID. So to create your object you just call 'factories[id]()'. – Daniel James Feb 23 '11 at 19:20
  • +1, and I really appreciate your work. Your full example helped. Apparently, creating game objects with Object Factory is the best way I can think of. As this answer is the best, I will mark this one as the *Accepted Answer*. Would you tell me, please, **what source have you learned this from**? – Bunkai.Satori Feb 23 '11 at 23:46
  • Modern C++ Design by Andrei Alexandrescu goes into this kind of thing in great detail, including a more advanced object factory. But it's quite an advanced text, you probably need to be more familiar with the ins and outs of C++ before attempting it. – Daniel James Feb 25 '11 at 16:14
  • thank you again. I have implemented object factory according to your asnswer, and I can say, **it works very well**. I do not need to have long switch() operand anymore. – Bunkai.Satori Feb 27 '11 at 13:55
1

I think Erik has given an excellent answer, i.e. use the Factory design pattern and fill in your array with Factory instances.

My only note of caution would be that using IDs as array indices imposes a little maintenance overhead -- you need to make sure the IDs and the array contents match. Easy when you've got 10 IDs, not so straightforward when you have 300, definitely not so simple when the code has been maintained for a few months or years.

If you can stand the performance hit (and I appreciate that may be a dominant consideration) then it is better to use some sort of map (I leave implementation selection up to you!) where each entry of the array contains an ID and its corresponding factory instance. That way you can group objects logically rather than having to sort them numerically, and the object IDs do not need to be contiguous. But either way Factory is a good way to go.

AAT
  • 3,286
  • 1
  • 22
  • 26
  • I'd just define all those IDs using an enum, adding something like `ID_COUNT` as the last item to determine the size of the array. Then you can add, remove or reorder IDs as much as you wish. – Sergei Tachenov Feb 23 '11 at 11:50
  • Factory **functions**, please. Than the array can be easily initialized statically. I bet the IDs need to be stable anyway, so adding new IDs will need some caution anyway and the array won't add too much of a maintenance burden. – Jan Hudec Feb 23 '11 at 12:09
  • +1 for nice addition to my question. To conclude, the key point is keeping instances of the objects within the array. For object identification I use enum() with IDs of all the objects, so that it is not an issue. What is an issue is what should be stored in the indexed array, so later I can instantiate objects from it. – Bunkai.Satori Feb 23 '11 at 14:04