2
\$\begingroup\$

I have a scene, in the scene there are multiple systems. Transformsystem, collidableSystem etc ... Currently, these do nothing except returning a bool if the parameter component matches the system:

bool DrawableSystem::match(Component* component)
{
    return dynamic_cast<Drawable*>(component) != 0;
}

The parent class handles everything else:

class System
{
protected:
    std::vector<Component*> _components;
    std::map<int, int> _idToOffset;
public:
    System();
    ~System();

    void initiate();
    void update(long);
    void remove(unsigned int);
    Component& getComponent(unsigned int);
    bool contains(unsigned int);
    void add(Component*, unsigned int);

    virtual bool match(Component* component) = 0;
};

The match method is used in scene when i for example instantiate an entity (I dont really have an entity class, its just a vector of components). It returns the id which is used when interacting with the components representing that entity.

unsigned int Scene::instantiate(std::vector<Component*> components)
{
    _currentId++;

    for (Component* component : components) {
        for (System* system : _systems) {
            if (system->match(component)) {
                system->add(component, _currentId);
                break;
            }
        }
    }

    return _currentId;
}

This will make it so that the vector _components in each system consists of only components associated with that system.

The current design is very maintainable and extendable, i don't have to modify any code in scene when adding a new type of component or system as you can see:

Scene::Scene() {
    _currentId = -1;
    _systems.push_back(new TransformSystem());
    _systems.push_back(new DrawableSystem());
    _systems.push_back(new CollidableSystem());
    _systems.push_back(new ScriptableSystem(*this));
}

Scene::~Scene()
{
    for (System* system : _systems) {
        delete system;
    }
}

void Scene::initiate()
{
    for (System* system : _systems) {
        system->initiate();
    }
}

void Scene::update(long dt)
{
    for (System* system : _systems) {
        system->update(dt);
    }
}

unsigned int Scene::instantiate(std::vector<Component*> components)
{
    _currentId++;

    for (Component* component : components) {
        for (System* system : _systems) {
            if (system->match(component)) {
                system->add(component, _currentId);
                break;
            }
        }
    }

    return _currentId;
}

void Scene::addComponent(Component* component, unsigned int id)
{
    for (System* system : _systems) {
        if (system->match(component)) {
            system->add(component, id);
        }
    }
}

bool Scene::exists(unsigned id)
{
    for (System* system : _systems) {
        if (system->contains(id)) {
            return true;
        }
    }
    return false;
}

void Scene::destroy(unsigned int id)
{
    for (System* system : _systems) {
        if (system->contains(id)) {
            system->remove(id);
        }
    }
}

But from what i've heard oop should not be used extensively like i do when designing a game engine.

I tried a purely data oriented design in the beginning but it started to get difficult to modify when i got up to around 4 different systems / component types.

Worth mentioning is that this is my first time making a game engine or anything remotely that complex so i can't really plan everything out in detail and just implement it by looking at an UML diagram. I constantly come up with better architectural choices for how the systems / scene communicate, additional method that could be useful etc ...

Do you gain so much in performance using a data oriented approach that reducing maintainability / modifiability would be worth it? It does not feel like it at all in my case, but i'd really like to learn how they do it in the industry and start learning "the right way" from the start. Are there other potantial gains with dod that i haven't thought about?

\$\endgroup\$
2
\$\begingroup\$

But from what i've heard oop should not be used extensively like i do when designing a game engine.

I'm not sure where you heard this, but it sounds like junk advice. OO design and patterns are perfectly fine to use in a game engine, and are used often. There are certainly ways to abuse OO design and produce horrible monstrosities, and you should avoid those. But you shouldn't get on the dogma bandwagon of "no OO anywhere" unless you absolutely know what you are doing and why.

Object- and data- oriented designs are not mutually exclusive, either. Good OO is useful and enabling. Good DO is useful and enabling. Great game engines use both. Bad implementations of either are harmful, and should be avoided.

Worth mentioning is that this is my first time making a game engine or anything remotely that complex so i can't really plan everything out in detail and just implement it by looking at an UML diagram.

You're going to get this wrong and fail. Many times. Many, many times. Don't stress out about "doing it the right way" or emulating the industry in any fashion (more on that later). Focus instead on making something that actually is a game engine: that is, something you can make a game with, as demonstrated by the fact that you have a game to show off with it. It can be a simple game: Asteroids, Breakout, even Pong. But by forcing yourself to use the thing in the context it's intended for, rather than just theorizing about it, you will gain a better understanding of reality and what the pros and cons of any given architectural decision are. I encourage you, for example, to go back and examine why your initial "pure data-oriented" approach fell flat. Beyond just "it got difficult." Really understand why your design choices made things difficult.

Do you gain so much in performance using a data oriented approach that reducing maintainability / modifiability would be worth it?

Maybe. "Data oriented" approaches are simply approaches that are focused on understanding the shape of your data and the transforms you need to make on it to make them work. It's not just "stuff everything into a vector, and it will be fast" (although that's true often, it's not always the way to go for a good data-focused approach). That's why they're not incompatible with OO approaches; you can do both, as long as you understand the data and what is (and isn't) important about it.

At this point, what will limit the performance of your solutions right now is you, the programmer. Not the broad-strokes architecture you use.

i'd really like to learn how they do it in the industry and start learning "the right way" from the start.

"The industry" is not a uniform body, and "they" do things wildly differently from person to person and (and here's the important part), not all of them are good. Being in the industry just means somebody is paying you to write games, which implies some particular minimum ability bar, but it hardly means everybody in the industry is brilliant at every aspect of building a game. Don't fall into that sort-of hero-worship trap. You can trust, but verify: reach conclusions on your own.

Similarly there is no one "right way," beyond the way that involves careful study of what you are doing, why, and whether or not it meets your goals. There are several wrong ways, though. Don't stress about getting things right the first time because that is impossible. Instead, allow yourself to be detached from the emotional investment of your solutions and understand when you come across better ideas or new implementations that you can simply refactor your existing code (which helps you in turn build a whole different set of useful skills).

\$\endgroup\$
  • \$\begingroup\$ Thank you, i learned alot from your answer. It really gave me perspective. \$\endgroup\$ – ojoj kolol Aug 3 '17 at 12:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.