5
\$\begingroup\$

I am thinking about how to create component based game engine. I understand that all things should be very similar as in Data Oriented Design (each object is a collection of various structures as Position, 3D Model, Health, Damage etc.), but I don't understand how should I handle the very big project.

I made one game in XNA a while ago and when I tried to create such component based game engine - I ended up having many singletons. Many systems depend on each other and I had to use for example Terrain subsystem to handle AI - so I had to make singletons.

How should I organise the game engine so that I wouldn't have singletons? Is it even possible when I have many dependencies?

Also when I created many Entities I found the problem or repetition of code. If I want to for example make AI for few kinds of enemies - some code can be the same for various enemies (Initializing AI, beginning of AIUpdate function) - it appears that inheritance would be best for such case, but then it isn't component based architecture (or Data Oriented Design) anymore...

\$\endgroup\$
7
  • \$\begingroup\$ I'm not sure if data-oriented design will work that well for game design. Regarding singletons, what about passing around references to instances or using static members? \$\endgroup\$
    – Apples
    Commented Mar 28, 2013 at 21:06
  • \$\begingroup\$ The answer to the "to singleton or not to singleton"-question does not change in the context of a CS. It has been discussed a lot, just search the site and SE. Voting to close. \$\endgroup\$ Commented Mar 28, 2013 at 21:26
  • 3
    \$\begingroup\$ Nothing about good component-based ordata-oriented design says you can't use singletons or even inheritance, where appropriate. Spend your time making a game, not blindly following some design pattern advice from the Internet. \$\endgroup\$ Commented Mar 28, 2013 at 21:27
  • \$\begingroup\$ A not-so-good solution would be building an uber-manager which holds the managers and passes data between them. Having a god object is detrimental, though. \$\endgroup\$
    – Exilyth
    Commented Mar 28, 2013 at 23:19
  • 1
    \$\begingroup\$ @Zygmuntix: The fact that a project uses an anti-pattern does not mean that it isn't still an anti-pattern. Existing codebased do not necessarily use good coding practice. \$\endgroup\$ Commented Mar 29, 2013 at 0:31

2 Answers 2

4
\$\begingroup\$

Some month ago I asked myself the same question. Since my data orientated engine makes good progress, I can give you some advice.

Generally speaking, the key is to separate data and function. This way components can be connected only by the data they modify instead of being connected directly with each other.

Each component inherits a Init() and a Update() function from an abstract base class and that's it. The components neither know of each other nor do they call other's functions. Moreover component don't store data as class members but in global storages. This results in nearly stateless components, the state is given by the global data.

As I said, data is stored globally so that every component can fetch and modify the same data. Therefore I use a global data manager. It allows to create entities, which are nothing but an global number, and assign properties to them. There properties can be position data, buffers, health points, and so on. One important thing is here for the data manager to provide a function to fetch all properties of the same type. This allows a component to process all properties related to itself. For example the physics component fetches all position properties, loops over them, performs collision checks and updates the coordinates. The renderer component fetches the same data along with other properties like mesh buffers and materials to draw each form at the right place.

As a developer you are very flexible in such a architecture. Since there are no direct dependencies between components, you could for example just remove the physics component. The code would compile successfully and all other modules would still work as expected. Only the global position data won't be updated anymore and the scene stands still. As you might see this also allows you to change component, that means separate functionalities, with easy. The new component just have to modify the same data storages than the one before.

It might sound elaborate to developers who prefer object oriented design to get rid of all dependencies between components. But indeed it results in very encapsulated, easy to maintain and fast code. I told you that my own little game engine is based on this architecture. If you are interested, feel free to visit the Github repository take a look around.

As a side note, this architecture isn't guaranteed to be data oriented in the narrow sense of the word. It definitely uses that paradigm, that means data is stored in long lists by property and not by object. But it depends on the implementation whether the structure of your global data storages actually fit the CPU cache or not.

You might also want to read this question about data driven design in game engines.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Your code looks very clean, I think I will try to use some a little similar architecture in my future games. \$\endgroup\$
    – Zygmuntix
    Commented Apr 3, 2013 at 21:30
0
\$\begingroup\$

First of all there is no absolutely right way of creating a component based engine. It depends on a concrete game. I can show how I do it developing a usual platformer.

//BaseEntity for platformer contains typical components for world entity, i.e. coordinates in world and hp amount
public class BaseEntity{

    protected LiveComponent liveComponent;
    protected PositionComponent positionComponent;

    public BaseEntity(LiveComponent liveComponent, PositionComponent positionComponent){
        this.liveComponent = liveComponent;
        //LiveComponent is interface and it could be just a container for data about current HP, 
        //or implementation for immortal entity, or implementation of complex HP behaviour(increasing of HP depending on time for example).
        this.positionComponent = positionComponent;
        //PositionComponent is also interface and it could be implementation with Box2D body or just static rectangle witch constant position. 
        //Moreover, it could contain 2D position or 3D position in the world.
        addComponent(liveComponent);
        addComponent(positionComponent);
    }

    public BaseEntity(){
        //constructor for more flexible entity creation.
    }

    public void reset(ResetData resetData){
        //method for reusing your entity. For example when entity dies you put in some pool. 
        //When you need to create a new one you just take it from pool and call this method.
        //ResetData could contain new position or some new state.
    }

    public float getHP(){
        return liveComponent.getHP();
    }

    public Position getPosition(){
        positionComponent.getPosition();
    }

    public void setPosition(Position position){
        positionComponent.setPosition(position);
    }

    public void setDamage(float damage, DamageType type){
        liveComponent.setDamage(damage, type);//type is magic or melee, for example
    }

    public void update(float deltaTime){
        //iterate and update all components
    }

    public void destroy(){
        //destroy all components and clean references
    }

    protected void addComponent(Component component){
    //protected for inherited classes
    }

    protected BaseComponent getComponent(){
        //You can't avoid interaction of components. 
        //But you must understand that the more your components interact with each other the harder it will be to use them.
    }
}

//Base enemy class
public class Enemy extends BaseEntity{

    protected ViewComponent viewComponent;

    public Enemy(ViewComponent viewComponent, LiveComponent liveComponent, PositionComponent positionComponent){
        super(liveComponent, positionComponent);
        this.viewComponent = viewComponent;
        super.addComponent(viewComponent);
        //ViewComponent is something which displays your entity. 
        //It could be just frame switcher. Also it contains methods to control what it should show. 
    }
}

//Concrete enemy
public class SimpleZombie(){

    private const String ATLAS_ID = "NormalZombieAtlasId";//path or id of visual data of your entity. You can leave these data inside every new entity or somewhere else
    private const int MAX_HP = 10;//Max HP

    private MoveAndAttackAI moveAndAttackAI;

    public SimpleZombie(float x, float y){
        super(new ViewComponent(ATLAS_ID),
              new LiveComponent(MAX_HP),
              new PositionComponent(x, y)
        );

        moveAndAttackAI = new MoveAndAttackAI(super.postionComponent, super.viewComponent, new MeleeWeapon(3)));
        super.addComponent(moveAndAttackAI);
        //first interaction of components and it is ok. 
        //MoveAndAttackAI controls position of zombie(through setting concrete coordinates or application of forces if it is box2d body).
        //If zombie goes left it tells to viewComponent to show move animation directed to the left
        //MeleeWeapon is a weapon with max damage of 3 points
    }

    override public void reset(ResetData resetData){
        super.positionComponent.setPostion(resetData.position);
        super.liveComponent.reset(MAX_HP);
        moveAndAttackAI.reset();
    }

}

And some advice:

  • One task implies one component. Component should be simple and do one exact work.
  • It's ok if you have some interaction of components.
  • It's ok if a component contains state, but stateless components are more preferable.
  • Don't try to create super universal components.

PS Interaction of entities is a game dependent topic. For example, box2d has it's own system of colliding.

\$\endgroup\$
1
  • \$\begingroup\$ What if I would like to add various AI (for example patroling terrain behaviour, agressive behaviour, attack from various distances behaviours) for various enemies? Should I inherit all new classes from MoveAndAttackAI class or rather reorganize the AI engine in some other way (add independent AI classes)? \$\endgroup\$
    – Zygmuntix
    Commented Apr 3, 2013 at 21:30

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