Best way to organize entities in a game?

后端 未结 5 2033
暗喜
暗喜 2021-01-30 15:24

Let\'s say I\'m creating an OpenGL game in C++ that will have many objects created (enemies, player characters, items, etc.). I\'m wondering the best way to organize these since

5条回答
  •  独厮守ぢ
    2021-01-30 15:32

    For my soon-to-be personal game project, I use a component-based entity system.

    You can read more about it by searching "component based game development". A famous article is Evolve Your Hierarchy from the Cowboy programming blog.

    In my system, entities are just ids - unsigned long, a bit like in a relational database. All the data and the logic associated to my entities are written into Components. I have Systems that link entity ids with their respective components. Something like that:

    typedef unsigned long EntityId;
    
    class Component {
        Component(EntityId id) : owner(id) {}
        EntityId owner;
    };
    
    template  class System {
        std::map components;
    };
    

    Then for each kind of functionality, I write a special component. All entities don't have the same components. For example you could have a static rock object that has the WorldPositionComponent and the ShapeComponent, and a moving enemy that has the same components plus the VelocityComponent. Here's an example:

    class WorldPositionComponent : public Component {
        float x, y, z;
        WorldPositionComponent(EntityId id) : Component(id) {}
    };
    
    class RenderComponent : public Component {
        WorldPositionComponent * position;
        3DModel * model;
        RenderComponent(EntityId id, System & wpSys)
            : Component(id), position(wpSys.components[owner]) {}
        void render() {
            model->draw(position);
        }
    };
    
    class Game {
        System wpSys;
        System rSys;
        void init() {
            EntityId visibleObject = 1;
            // Watch out for memory leaks.
            wpSys.components[visibleObject] = new WorldPositionComponent(visibleObject);
            rSys.components[visibleObject] = new RenderComponent(visibleObject, wpSys);
            EntityId invisibleObject = 2;
            wpSys.components[invisibleObject] = new WorldPositionComponent(invisibleObject);
            // No RenderComponent for invisibleObject.
        }
        void gameLoop() {
            std::map::iterator it;
            for (it = rSys.components.iterator(); it != rSys.components.end(); ++it) {
                (*it).second->render();
            }
        }
    };
    

    Here you have 2 components, WorldPosition and Render. The Game class holds the 2 systems. The Render component has an access to the position of the object. If the entity doesn't have a WorldPosition component, you can choose default values, or ignore the entity. The Game::gameLoop() method will only render visibleObject. There is no waste of processing for non-renderable components.

    You can also split my Game class into two or three, to separate display and input systems from the logic. Something like Model, View and Controller.

    I find it neat to define my game logic in term of components, and to have entities that only have the functionality that they need - no more empty render() or useless collision detection checks.

提交回复
热议问题