Problems With Game Engine Architecture

Started by
15 comments, last by Shaarigan 7 years, 5 months ago

As the title says I'm having a lot of problems with the architecture of my game engine.

Every time I write a game engine all different components (Graphics, Audio, Input) get really messy.

Input needs to have access to Graphics in case for example the player shoots, and the Graphics might need access to Audio because clicking a button should play a audio file, and so on.

I can't find a good way to do this without everything looking like spaghetti.

I've considered these things, but I don't know if any of them are actually good ways to do it :

  • Making every single component of the engine global, and putting them in a globals.h.
  • Event/Messeging system (this one seems pretty good but I can't wrap my head around on how to implement it)
  • Declaring pointers to components in each class that needs it.
  • Passing the components to each function that uses it (I dislike this one since if a function needs several of the components it looks really messy)

Basically I want my engine parts (Graphics, Audio, Input, GUI, MapManagers, Debugging Classes and so on) to be able to access eachother without having to create a sphagetti mess everywhere.

Advertisement

Input needs to have access to Graphics in case for example the player shoots, and the Graphics might need access to Audio because clicking a button should play a audio file, and so on.


My suggestion to handle this is to extract the "game logic" into its own systems and have it communicate with the other systems. "Platform-level" components like input, graphics, and audio should never need to know about one another. Instead, try this:
- when the player shoots, input notifies the game system that the player has shot
- the game system takes care of telling the graphics system to spawn particles (the graphics system shouldn't know what "bullets" are - only things related to rendering)
- UI should not be in the graphics layer - UI should be in its own system that takes input and tells the graphics and audio layers what to do

As much as possible, you want dependencies to flow in one direction. Either platform components should not need to know about gameplay components at all, or gameplay components should not need to know about platform components at all. Platform components definitely shouldn't need to know about each other.

Passing the components to each function that uses it (I dislike this one since if a function needs several of the components it looks really messy)


This is my preferred option, most of the time. It may look messy, but it also lets you see exactly which function of each component depends on which other component. That's something that can come in handy when refactoring. If your dependencies are explicit, and they look messy, that's probably because they are messy. If your dependencies are implicit, they might look less messy, but that doesn't take the mess away.

Maybe a bit to obvious, but there's good book on this calles exactly "Game engine architecture". It's a good guideline for exactly what you're doing (currently I'm at chapter 4 :))

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

Input needs to have access to Graphics in case for example the player shoots, and the Graphics might need access to Audio because clicking a button should play a audio file, and so on.


My suggestion to handle this is to extract the "game logic" into its own systems and have it communicate with the other systems. "Platform-level" components like input, graphics, and audio should never need to know about one another. Instead, try this:
- when the player shoots, input notifies the game system that the player has shot
- the game system takes care of telling the graphics system to spawn particles (the graphics system shouldn't know what "bullets" are - only things related to rendering)
- UI should not be in the graphics layer - UI should be in its own system that takes input and tells the graphics and audio layers what to do

As much as possible, you want dependencies to flow in one direction. Either platform components should not need to know about gameplay components at all, or gameplay components should not need to know about platform components at all. Platform components definitely shouldn't need to know about each other.

Passing the components to each function that uses it (I dislike this one since if a function needs several of the components it looks really messy)


This is my preferred option, most of the time. It may look messy, but it also lets you see exactly which function of each component depends on which other component. That's something that can come in handy when refactoring. If your dependencies are explicit, and they look messy, that's probably because they are messy. If your dependencies are implicit, they might look less messy, but that doesn't take the mess away.

I checked out the source on CryEngine, they seem to use static objects for all their managers, considering they're using it I assume it's not that bad?

I really, really dislike the looks of sending them in to every function though. I'll probably only do that if I have no other options tbh.

Maybe a bit to obvious, but there's good book on this calles exactly "Game engine architecture". It's a good guideline for exactly what you're doing (currently I'm at chapter 4 :))

I'll check that out. :)

I checked out the source on CryEngine, they seem to use static objects for all their managers, considering they're using it I assume it's not that bad?


What other engines do is irrelevant to you. Just because a large engine does things one way, or that way is common, does not mean that it is the best way to do it, or even a not bad way to do things. You might be surprised at some of the atrociously ugly and unmaintainable code (especially legacy code!) in major engines. Just because an approach is common does not mean it's the best approach for your own circumstances.

You should do what is best for your code. I am suggesting that making your dependencies explicit and avoiding global state is what is best for your code.

I really, really dislike the looks of sending them in to every function though. I'll probably only do that if I have no other options tbh.


It doesn't matter whether you like the way it looks. What matters is that your code is maintainable and easy to reason about. You're shooting yourself in the foot otherwise.

You should write your code in such a way that bad code looks bad. If you're passing too many dependencies around, that's a sign that your code probably needs to be rethought. Implicitly passing those dependencies around via globals/singletons makes that fact a lot less obvious and can allow dependency spaghetti to grow.

Input should never have access to graphics. This doesn't make much sense to be honest.

This is where you come to abstraction layers. Abstraction layers will prevent things from spiraling down into insanity as long as you keep abstractions going in one direction.

For example

Input -> Logic -> World -> Representation.

Your input should only influence the logic. Where the logic influences the world. And the world influences the representation. To make things simple.

Your world consists of models and sounds. And your representation consists of how things needs to be rendered.

Between those, you'll have data that is goes back and forth between one another. Or goes in just one single direction.

A few things I do:

- I use globals for all my managers. There are a few, but I also cluster some of the sub managers into a manager for that area. EG - Special effects manager, has explosions, particles, etc...all the fluff. I do have a static and dynamic entity manager which should be managed together also. BUT

- General rules you make for yourself. Mine, don't call a manager from an entity in another manager. That's my rule, so it means that the entity has to have strong status controls in it. For example, if my bullet hits something, I set a status on what it hit (material). My manager orchestrates call from the top level. it means I dont have to follow into entities all the extra game logic.

- If you think something is complicated in your mind, then comment it in your code. You come back 2 weeks later and go WTF.

- Don't be afraid to start with creating too many managers (yes, its bad practice though), you will soon realise patterns in your code and refactor. Experience will play a part.

As mentioned before, major game engines have some pretty bad code in them. Read Unreal has some epic classes in its engine, making it hard to pick up and learn.

And reading books is good also, its old fashioned, but works

Indie game developer - Game WIP

Strafe (Working Title) - Currently in need of another developer and modeler/graphic artist (professional & amateur's artists welcome)

Insane Software Facebook

Input needs to have access to Graphics in case for example the player shoots, and the Graphics might need access to Audio because clicking a button should play a audio file, and so on.

What if I want to make an ASCII-based game that has no audio? What if I want to create a networked game where an input also needs to be sent across the internet?

Write the engine as engine, keeping the components completely separate from each other. Then write another layer, your game, on top of those that connect things together.

- I use globals for all my managers.

Heheh. I remember when I made that exact statement about my game project almost 5 years ago. I also remember 6 months ago when I suddenly realised why it was a bad idea and spent a few months refactoring everything to remove all globals.

Globals are seductive because they are easy to use and let you share information between different objects very easily. But you eventually end up in dependency hell, where to test a small system you need to recreate the entire game state, since object A references systems B and C, and they rely on D and E and F.. etc

The best thing about the mindset of passing dependencies instead of referencing them through globals, is that poor design stands out like a sore thumb.

[size="2"]Currently working on an open world survival RPG - For info check out my Development blog:[size="2"] ByteWrangler

- I use globals for all my managers.

Heheh. I remember when I made that exact statement about my game project almost 5 years ago. I also remember 6 months ago when I suddenly realised why it was a bad idea and spent a few months refactoring everything to remove all globals.

Globals are seductive because they are easy to use and let you share information between different objects very easily. But you eventually end up in dependency hell, where to test a small system you need to recreate the entire game state, since object A references systems B and C, and they rely on D and E and F.. etc

The best thing about the mindset of passing dependencies instead of referencing them through globals, is that poor design stands out like a sore thumb.

I do have globals, but I dont sprinkle them much throughout my code. The point is to use the conservatively. At the end of the day, my managers only accessed through say a core render functions. I enforce that game entities themselves don't jump in and out of Globals. Creates spaghetti code. Its just down to discipline.

Indie game developer - Game WIP

Strafe (Working Title) - Currently in need of another developer and modeler/graphic artist (professional & amateur's artists welcome)

Insane Software Facebook

This topic is closed to new replies.

Advertisement