[C++] Game Engine structure - Questions

Started by
2 comments, last by kkmzero 6 years, 5 months ago

Hello. I hope this is right part of forum to ask and I won't break any rules. I'm working on my own DirectX/C++ game engine for my purposes and after months I decided to completely rework its structure because I had some problems that started to bloat everything. I read books and information on web about game engines and it seems that there isn't much definitive answer on question how should be game engine structured exactly, I mean some standard rules describing it to details. It more seems like everybody is doing it more or less their own way, suiting their own needs which makes sense. However, I would like to ask anybody who has experience in making their own engines, maybe giving me a little tips about how I should structure it (simply: do/don't do this, you will regret it when you will start making game and bloat everything). Just a little note: I don't have editor like Unity/UE. All I want is some kind of skeleton of code I can build games on, so I don't need to code everything from scratch every time I decide to work on a game.


1. What exactly is purpose of object called game controller and how I should work with it? I was always used to have in game controller stored game related data (score, health, etc.). Currently my game controller is pretty much just telling main loop what game level to load and how to load, draw, update and release it. Do you think it is good idea, to move this stuff to another object that would be loading game levels and use game controller just for storing and passing particular game related data? What exactly means term game controller in relation to game engines and not to game development itself?

2. Currently in my main.cpp I have whole process of rendering window and main loop. Is it good idea to move main loop (initializations, render function, update function, release functions) to object that would be above main.cpp to keep everything more clean? I'm thinking about this because I would like to avoid overbloating my main.cpp file when I will start with game development. I would also like to avoid mixing my low-level engine structure with calls related to game, in other words, I don't want in my main loop next to D3Dinit something like "load level #1", that would be function of another object above main loop and only integrated in main loop, and I thought this object is game controller but then I realized, that I honestly have no idea what should game controller do.

Simply put, I would appreciate any advice and even better would be if you have some kind of good schema about how should be structure/object hierarchy of game engine done. I'm also expecting that nobody can help me with this because as I said, it seems that everybody is solving this stuff for theirs own needs.

Advertisement

The question is a bit tricky and there are reasons you did not find a good answer on this in the net because everybody/every team handles this on its own for there current needs so I can tell only for my needs and what I think works best for me.

As a note, I work multiplatform on GL/Vulkan graphics so this may be a little bit different from any DX platform related code.

I have certain kind of start point macro defined in my code that is


__app_entry(initialize, main, shutdown)

which has 3 states the program can have:

  • Initialize needs to call a function to well .. initialize anything that needs to be created before whatever can startup. This means in detail, calling memory layer to create at least a default allocator that will be used by any object inside the engine to get memory, load default config files into the engines settings layer from well defined locations (I'm a friend of the good old plain .ini files), create RTTI as same as Log and Profiler callbacks and setup the task and event systems. I use a thread pool to pass engine tasks to because my code runs in a multithreaded environment. I use a mixed global/local event system that works on different "channels" (which are specified by the event object) using single bindings and multicast delegates. Depending on settings, do some other stuff also like setting up network connections, load plugins/extensions and whatever needs to be also done before window creation is done
  • Main is my main loop (how creative ;) ) that does do reading the program args (as existing) to adjust the engine settings layer and then based on this start to create the rendering surface depending on the platform (a Window in Unix/Windows, App-Window in Android and a proxy class on PS4/Switch) and setups graphics. Graphics is bound internally to the related drivers/API calls depending on the platform too. This is normally the point where user code gets informed about the completed initialization step before main loop enters
  • Shutdown is going to release anything done in the initialize function in reverse order to cleanup the engine properly

My main loop is a small piece of code that does nothing else than requesting messages from the platform and otherwise gives its resources to the thread pool executing small tasks.

On the user code side I capture the initialized event and set a trigger into the asset manager to load my initial asset package that controls how the game starts, what scenes have to be loaded and depending on the scenes, what assets and objects needs to be created in addition. Anything else is then done by "the game".

Each system is an own sub project in my code so I have different projects for different systems which helps me keeping dependencies between those at a minimum to keep the code clean and reusable. Those are mostly triggered by client code behind the scenes when an asset package is requested or load of assets for example, when a sound needs to be played or objects are meant to be displayed. The only system that runs without any use code triggers are the network and graphics layer. Network layer is fully async based on the platforms APIs (poll/epoll for Unix/Android, IOCP on Windows and the related network APIs on PS4/Switch) that fire into the event system when something happend while the graphics system itself also puts and update and render event onto the global event stack that triggers any client code to update before it triggers itself to render again (using the event).

I write client code all the time because I see the engine in a client/server related architecture. The client code (user code or game code) knows the server (engine code or the engine systems) and is able to do calls into that with little context to the server while the server (so the engine backend code) dose know nothing about the client and needs to work on the given context. I think this keeps the two branches to be separate enougth to be some kind of generic but also close enougth to have maximum flexability on minimum management overhead while the client code could be created in a way that there does not need to be a "god object"

Thank you very much for your answer. Meanwhile I found really good source of information related to engine architecture that inspired me in other aspects but one important issue I had was that my main loop got easily too big and it seems after reading your insight is that I should keep main loop as much simple as possible. Also I found some interesting parts in your text that helped me to start thinking about other solutions for my project that would work better. Thank you very much for help, I appreciate it a lot.

This topic is closed to new replies.

Advertisement