Simple yet effective game engine architecture patterns?

Started by
21 comments, last by Juliean 2 years, 4 months ago

Hello everybody!

During my hobby game development adventures over the years, every now and then, I stumbled upon a few game engine architecture patterns that turned out to be surprisingly simple yet extremely effective. They completely changed the way I write game code and became part of all of my projects after I had learned about them.

The two most notable ones are the entity-component-system (ECS) pattern to flexibly compose game entities from components and update them on a per-component basis, and the global event bus pattern for loose coupling of subsystems. In both cases, after learning about their basic concepts, I wrote very basic implementations of them, which only consist of a few dozens lines of code, but since then take absolute core roles in my applications and make my code a lot easier to change, extend and understand.

Now, my question is, are there more game engine architecture concepts with such great “power to effort ratio”? I'm specifically not looking for complex algorithms, or anything that is specific to a particular programming language or engine/library. I'm looking for simple and generic solutions that can be used in many different types of games, and especially things that increase flexibility and “cleanliness” of data structures and communication/interaction between different parts of a program, like the two examples mentioned above.

I'm looking forward to read about your experiences and recommendations! Thanks in advance ?.

Advertisement


Not really what you're asking for, but i have a good experience with a ‘configuration class’, and i wish i would have come up earlier with this.
It's more useful to build editor than game, and for research / experiments.
In the class i can add data elements, which can be anything from bool up to vec4, and each element has strings for name and description.
To access the data i use it's name, which has some advantage over using a hard coded member variable. I can add new data dynamically at runtime, and changing stuff does not force me to update all the code using it. Reminds me a bit on php where you also can add new variables to a class object at runtime.
Also, each such configuration can save / load itself to disk, and it can generate GUI so i can tweak the settings easily at runtime to try out things (using some flags to enable sliders / checkboxes and even drop down lists using the description string for the options).
I rarely spend time for GUI creation or files stuff after that, and there is now less Immediate Mode GUI clutter in my code.
It's not efficient, but for the applications this does not matter, and it's very convenient to use.
I guess many programmers use something like that, but it took me 30 years to get the idea : )

Here a picture showing GUI of one such config:

I can setup what the application should do at start up and per frame. Settings are saved automatically on exit.
Before i had used configurations, i had to edit code on many places so the app gives me quick feedback on what i'm currently working on. Pretty annoying and error prone. Now i only need to click some checkboxes, close and restart.

My personal two favourites:

a) Having some sort of “type system”, similar to what @joej probably uses in his example. But I'm not necessarily talking about a specific implementation, but more the ability to have an overarching system for handling types of all kinds, throughout the entire engine. Similar to how you can bind fields of “bool”, “int”, etc… in Unity via reflection, the same can also be done with a little more work in C++. Then instead of having to write all kinds of GUI, serialization code etc… you simply declare the fields of a certain class and have the engine figure out the rest. This is really huge; I used to have to write (=copy/paste) about 200 LoC for every new component I declared; and that was when I had about 1/5th of the features I have now.

b) More specific to scripting (with an embedded script-language): The ability to bind functions with one LoC each, and without having to supply duplicate information. I've seen many different script-implementations where you eigther have to write a wrapper-function (core LuA), specify the type of arguments yourself (AngelScript i belive) or have to implement a whole class/interface per call (I've heard Frostbyte does that). This is how it works for me, for my visual-scripting:

Entity& EntityManager::CreateEntity(std::wstring&& strName) { ... }

event::registerBindFunction(&EntityManager::CreateEntity, L"entity CreateEntity(name)");

And the rest is figured out by the aformentioned type-system, as well as a system for specifying additional script-only types.

Those are my personal best time-savers.

What I'm using heavily past 2 years is reactive programming pattern, which allows to stream data and have selector functions attached to it which define when and how data is processed. I'm using it in our build tool to chain different steps and processes depending on the data currently processed and their output but also try to implement that currently into our UI/Graphics framework to have fully data driven UI rendering and event handling.

The second one I really like is working with Task<T> in C#. I mention this because we use a similar but fully self implemented task system in our project (written in C++ and a little Assembly). It is super effective, once you get used to thinking in continous tasks, to split the entire game engine into smaller processes which may depend on other tasks. Even if there are totally async/await fanatics out in the C# wilderness, I use it whenever it is useful to do something in parallel and may be ascribed to a single line of execution after everything has been processed. Since the GDC presentation https://www.gdcvault.com/play/1022186/Parallelizing-the-Naughty-Dog-Engine​ I'm a fan of fiber based micro-threading in game engines

JoeJ said:
Not really what you're asking for

Actually, this is exactly the sort of think I'm asking for ?. Thank you very much!

If I understand you correctly, you're basically talking about something like the Windows Registry. The idea isn't entirely new to me, but I did not yet use it in my projects. There is a bit of conceptual overlap with the ECS pattern, and perhaps both can even be combined in a meaningful way. In any case, I'll think about how I can make use of it!

Shaarigan said:

What I'm using heavily past 2 years is reactive programming pattern, which allows to stream data and have selector functions attached to it which define when and how data is processed. I'm using it in our build tool to chain different steps and processes depending on the data currently processed and their output but also try to implement that currently into our UI/Graphics framework to have fully data driven UI rendering and event handling.

The second one I really like is working with Task<T> in C#. I mention this because we use a similar but fully self implemented task system in our project (written in C++ and a little Assembly). It is super effective, once you get used to thinking in continous tasks, to split the entire game engine into smaller processes which may depend on other tasks. Even if there are totally async/await fanatics out in the C# wilderness, I use it whenever it is useful to do something in parallel and may be ascribed to a single line of execution after everything has been processed. Since the GDC presentation https://www.gdcvault.com/play/1022186/Parallelizing-the-Naughty-Dog-Engine​ I'm a fan of fiber based micro-threading in game engines

Great answers too! I have already thought about reactive programming, too. I have read and attended a talk about it a coupe of years ago (ReactiveX/RxJS specifically), but, TBH, I couldn't really wrap my mind around it back then. Perhaps I should take another, closer look at it.

The difficult thing about fundamentally new patterns, especially if they are as complex as reactive programming and tasks, is that you do not only need to understand how they work in principle, but then also how to use them in effective ways. And usually, the second step takes a lot longer than the first. Especially because important details of the environment and use cases change all the time. I understood the ECS pattern after a few hours, but I'm still learning new things about how to model game worlds with it up to the present day. I assume it is very similar with reactive and task-based programming.

wurstbrot said:
There is a bit of conceptual overlap with the ECS pattern

Not really. ECS is about fundamental design paradigms, but my stuff is just about ‘Keep all my various settings manageable, and have automated GUI and storage of these’.
For a more advanced and efficient integration of such ideas, @juliean s post sounds much more relevant.

JoeJ said:

wurstbrot said:
There is a bit of conceptual overlap with the ECS pattern

Not really. ECS is about fundamental design paradigms, but my stuff is just about ‘Keep all my various settings manageable, and have automated GUI and storage of these’.
For a more advanced and efficient integration of such ideas, @juliean s post sounds much more relevant.

The overlap is not very big, but both provide a way to flexibly create data models at runtime.

You say “In the class i can add data elements, which can be anything from bool up to vec4”.

That's basically the same as what I do with adding a component to an entity.

I'm aware that there are many differences otherwise, but my remark was meant on a very abstract level.

wurstbrot said:
That's basically the same as what I do with adding a component to an entity.

It's the same, but you can do it efficiently, i can not. If i would use this at the level of thousands of game objects, the waste of memory and performance would be insane.
Thus my goal here is entirely convenience, which i can afford because i need just dozens or hundreds of such configurations, and i access their data only at the times of preparing / setting up a task, never while executing it or in inner loops.
This is surely the reason why i failed to come up with such convenient tools earlier, simply because i'm used to care about efficiency first.

Another important difference is dependency. If you implement something like an automatic reflection system, that's more powerful for sure. But it adds dependencies and conventions between the reflection system and your way of designing data structures. Say you have a game engine built around ECS, and a nice semi-automatic way of serialization and UI creation to complement this. (I guess that's what Juliean talks about.) This is good and often a must have, but once you decide to ditch ECS for another project and use something like OOP instead, chances are your reflection system may be hard to port. Contrary to this, my configuration is just one header without dependencies aside ImGui. I can include this to use it in any project. But on the other hand i need to create every configurations data members manually. It does not parse my classes code like a reflection system might do.

So i don't see my proposal related to any kind of engine architecture. It's more like an useful generic building block, which we often initially come up with from debugging needs. Similar to ImGui itself, for example.

JoeJ said:
(I guess that's what Juliean talks about.) This is good and often a must have, but once you decide to ditch ECS for another project and use something like OOP instead, chances are your reflection system may be hard to port.

Thats actually not the case for my system, if thats what you are refering to for the second parth ? As I said, the reflection is completely independant of ECS or what else, I even use it in the OOP-based gameplay-framework that sits on top of the ECS. So in that sense, every system does use the reflection (which is its own module), but there is no backwards-coupling (its really just a way to express what C# can do with its baseline reflection + a few preimplemented UI-elements to support user-editing). If I had a system like you described for your configuration, the reflection would drive the actual properties to be added and the UI.
Thats not to say something like you are doing specifically has no merit on its own. I have my own UI for editing configuration-properties in my editor. And I'm also looking into implementing an editor for editing savegame-related properties all in one window, to assist with testing, which would look similar to what you are doing. But out of interest, how are you declaring/handling property-types in your system? Since it sounds like you don't have any type-system attached to it, do you just use immediate-mode gui calls for the different types?

This topic is closed to new replies.

Advertisement