Advertisement

Game Engine Logic ECS but not really?

Started by June 11, 2018 02:29 PM
14 comments, last by Fulcrum.013 6 years, 3 months ago

Hello,
I have built games in the past in unity and XNA, android SDK etc. but it’s always been quite specific, when building a small Game Engine or more accurately a sub-set of code that is reusable across projects the tendency to structure the project falls towards composition of objects that are more "generic" than toward inheritance-based models.

For the Engine specific sub-systems, we have components such as a MeshInstance/Renderer that contains a Handle to a Mesh in a MeshLibary and a handle to a Material from the material library which in turn hold PSO data etc. We can also have Components such as SoundSorce, which are managed by an AudioSystem and contains the data to specify what sound, at what volume etc.

These systems can be Set up and shut down from within the Engine/Game class such as:


void Engine::Run()
{
	if (Initialize())
	{
		//Main loop call the systems
        //Dispatch Events
	}

	ShutDown();
}

void Engine::Initialize()
{
  //Init all sub-systems
    FileSystem.Init();
    RenderSystem.Init();
    PhysicsSystem.Init();
    AudioSystem.Init();
    WorldManager.Init(); //Creats Entities
}

void Engine::ShutDown()
{
    //shutdown all sub-systems
    WorldManager.ShutDown();
    AudioSystem.ShutDown();
    PhysicsSystem.ShutDown();
    RenderSystem.ShutDown();
    FileSystem.ShutDown();
}

But where does logic go? in smaller games like XNA or SFML you generally create some managers, let’s take the example of darkest dungeon.

They have various managers like:

  • Darkest Dungeon Manager
  • Campaign Selection Manager
  • Estate manager
  • Party formation manager
  • Town manager
  • etc..

All the logic and systems to run the game are based within the managers, and objects contain scripts of data that are fed thorough the systems.

In a small reusable code base how would you separate the logic from the engine, should like the smaller games all the managers just be shoved into the Engine Class even though that goes against a reusable data-driven framework? Should they just be stuck in a GameLogic system that's is initialized in the Engine Initialism function. And how do people tend to connect data scripts to the other various engine systems without causing too much game specific coupling. For example, you can use an Event System, but firing and event such as DAMAGED_BY_ZOMBIE and having the internal engine respond to that seems to break the separation from the low-high level of the engine system.

Would be great to here some opinions from the community on this subject as it is quite a vital and potentially problem prone aspect of engine/game development.

Thanks.

Everyone has a different way of approaching structuring a game, so there are no right or wrong answers here - just options, and different justifications for choosing those options. Personally I think 'manager' classes are often a bad idea - these tend to mean there is global state, which makes things harder to test and debug. But if you're careful with them, they are a reasonable approach to dividing your game up along broad lines that correspond to features.

Separating the logic from the engine is often just a case of thinking through every code decision as you make it. When you write a function, consider whether it could be more generic. When you create a class, think about which part of it is specific to this game and which part is more general, and whether they could be separated. When one class references another class, consider whether an engine class is referencing a game-specific class, and if so, try refactoring it to decouple them (e.g. via events, or a generic interface, etc).

I don't think any of this advice really cares whether you have a traditional inheritance model for GameObjects, or entities with components, or a fully-fledged entity/component/system division. The problems are broadly the same and the aim is not to know up-front exactly how everything should be structured, but to spot when things need changing and make the changes as you go.

(Specifically regarding your event problem - it's hard to see why engine code would want to react to a DAMAGED_BY_ZOMBIE event because it's meaningless to the engine, which knows nothing about zombies, only to the game, which might. You might change it to a more generic CHARACTER_DAMAGED event, which could actually be handled meaningfully by an engine, with some data value inside the event designating the source of the damage, if it's necessary.)

Advertisement

I guess its more about where game logic should live, in the XNA SFML style games, the main Game class becomes bloated with a bunch of systems for logic in most examples. Because the framework is custom though your initializing all the engine systems then the logic systems in the same place. It feels like the engine should be a self contained system, but both the game logic and the engine need to be hooked into a Main loop. the logic needs to know about the entities and so does the engine, it needs to sync up the engine specific components like MeshInstance. The World or Scene stores lists of Entities which have ID's and handles to the Engine specific components, but the world/scene shouldn't really have the logic? because technically the Scene/world is part of the engine in the scenes a small engine should provide the ability to create its entities and sub-system compoentns independent of what ever game it is.

For examples, a lot of games will Use Mesh's so the Scene should let you make an Entity with a transform and a mesh to render for you, what ever logic you want to apply shouldn't factor into the World/scene at all.

So this leads down to the option of having a separate GameLogic system that stores all the game specific systems within it, like menu system, enemy spawner etc. that all hook back into the world manager using the entity id's.

Other systems have a BehaviorComponent that they attach and update on each entity like unities mono behavior but logic in the component can sometimes be seen negatively.

3 hours ago, Kylotan said:

no right or wrong answers here - just options

There is industry wisdom though, that could pro/con the placement of logic.

Im going to try the GameLogic method and see how that goes.

Thanks

You mention 'industry wisdom', and my 10 years of industry wisdom told me that everyone does this differently. :)

 

16 hours ago, Jemme said:

in the XNA SFML style games, the main Game class becomes bloated with a bunch of systems for logic in most examples

If you make a single class and then choose to bloat it, that's your decision. There is always going to be some top-level object and you always have the option of subdividing it along the lines that make sense to you. Start simple and refactor as you go.

 

16 hours ago, Jemme said:

It feels like the engine should be a self contained system, but both the game logic and the engine need to be hooked into a Main loop

Nothing can be fully self-contained. The idea is that you have layers. The low level libraries like SFML wrap around the operating system, your own 'engine' code might wrap around SFML, and your game code will mostly wrap around whatever engine code you have. It's totally optional to have 'engine' code - you can just have your game reference SFML directly if you like. (In fact, I'd probably recommend that.)

 

15 hours ago, Jemme said:

So this leads down to the option of having a separate GameLogic system that stores all the game specific systems within it, like menu system, enemy spawner etc. that all hook back into the world manager using the entity id's. 

Yes, you probably want some sort of class that represents your game-specific behaviour. It might store game specific systems within it. They may well use entity IDs. This isn't particularly controversial so it's unclear why you seem worried about it.

 

16 hours ago, Jemme said:

Other systems have a BehaviorComponent that they attach and update on each entity like unities mono behavior but logic in the component can sometimes be seen negatively. 

Most games work this way. The Unity and Unreal engines are both designed to facilitate this approach as the main way of implementing game logic. Don't let some opinionated blog posts deter you from this if you think it would work for you.

There isn't really a good way to implement the game logic / components that interact with the underlying engine, these are the common approaches which i have used in different projects with the pro/cons.

  • OOP Inheritance:
    • Pro:
      • Makes sense to normal programmers, easy to explain.
      • Works for simple systems
    • Con
      • Deadly diamond
      • Monolithic class hierarchies
      • Multiple inheritance
      • Deep wide hierarchies
      • bubble up effect
  • Entity Component
    • pro
      • Easy to implement / explain
      • Works for most systems
      • Allows many Entities of different types
    • Con
      • Not friendly to cache
      • pointer chasing
      • Non-sequential component updates
      • virtrual function, vtable cache miss
      • spaghetti pointer chasing in the Update loop
  • Entity Component System
    • pro
      • Easy to implement / explain
      • Works for most systems
      • Allows entities of different types
      • Data Orientated Design (contiguous)
      • No vtables
      • ID's allow reallocation + avoid dangling pointer
    • con
      • Claimed cache benefits fall away when you have to chase a pointer to a GameObject to get a Transform for a meshrender or physics system to act upon it.
      • Using ID's causes dependency on systems. e.g (RenderSystem.GetComponent(Entity.ID)) so now the script / other system has to suddenly know about every other system it needs data from as opposed to just having a pointer to the data.
      • Still spaghetti pointer chasing in the update loop.

There the common way's you see being talked about, another method is to use a message system which makes sense. However, should you really have a message for every possible thing that can happen + every system having to know about every possible thing that can happen and just ignoring it. You could store a position with your MeshRenderer if a message is received to update position you can just update it that way it is cached with the MeshRender, the physics can cache its version, yes its duplication of data but ti stops pointer chasing.

So basically every system/method i have encountered improves something at the cost of making something else less efficient.

 

Advertisement

You don't need to pick one of these three routes - you can have all of the above, in the areas where it makes sense. It's not ideal to obsess over low level things like vtables or cache misses unless you know you have a system where that is going to matter.

It's also not right to consider a message system to be some sort of 4th approach - that is just a way of handling communication between things. I don't think anybody serious is trying to create an entire system where absolutely everything is handled by messaging. It is however useful for communication between completely unrelated systems - but so is the observer pattern, or callbacks, or events, or lambdas, or whatever your language provides to keep coupling low.

All programming is about trade-offs. Dwelling on it too much at the start is a good recipe for never getting anywhere. Start coding, and improve your code as you go along.

There's a significant difference between

On 6/11/2018 at 4:29 PM, Jemme said:

I have built games in the past in unity and XNA, android SDK etc.

 

On 6/11/2018 at 4:29 PM, Jemme said:

For the Engine specific sub-systems, we have components such as ...

 

On 6/11/2018 at 4:29 PM, Jemme said:

But where does logic go? in smaller games like XNA or SFML you generally create some managers

 

On 6/11/2018 at 4:29 PM, Jemme said:

In a small reusable code base how would you separate the logic from the engine, should like the smaller games all the managers just be shoved into the Engine Class even though that goes against a reusable data-driven framework?

 

On 6/11/2018 at 8:42 PM, Jemme said:

It feels like the engine should be a self contained system, but both the game logic and the engine need to be hooked into a Main loop.

Are you trying to extract an engine from your old games, to develop a new or improved engine for future use, or simply to find a good architecture for a new game? Sometimes you appear to have an established engine you want to improve, sometimes not.

The indecision between a main loop that belongs in the engine or in the game logic is a particularly troubling instance of a problem you shouldn't have: either you use adopt an existing game engine that "owns" the main loop and lets you write callbacks etc.:to customize it, or you adopt some less opinionated framework (like the mentioned XNA or SFML) and you just write (or adapt from examples) a suitable main loop for your game that will be eventually refactored and decomposed into different modules and layers as its complexity increases.

Omae Wa Mou Shindeiru

I'm building everything from scratch, all the low-level, dx11, gl etc.  In the past many years ago I used xna and sfml for specific projects like build an RPG etc. But now I'm doing everything  and I want it to be reuseable, I'm going the route of having a game logic system which initialises all the logic systems and using lua for behaviour scripts and JSON for components initialisation on entities.

On 6/11/2018 at 6:17 PM, Kylotan said:

- it's hard to see why engine code would want to react to a DAMAGED_BY_ZOMBIE event because it's meaningless to the engine,

Each bullet fired can be interpritated as damane message with dalayed delivery and unknown at send time recepient. And bullet manager/subsystem can be interpritated  as special case of messaging system,

#define if(a) if((a) && rand()%100)

This topic is closed to new replies.

Advertisement