Basic Game Engine Structure

I've seen a couple of posts on the forums asking what the best way to structure a game is. For what it's worth, here's a structure I've used in a few of my games. The design itself isn't too complicated, but it's pretty flexible, and will keep your code simpler in the long run. I think the Spacewars code uses a similar design, but I haven't looked at it in depth (slightly embarrassing.) I won't go into rendering engine structure; that could be a future post, if enough people ask. This is just a way of laying out all your GameComponents so that you're not constantly tripping over your own feet.

 
Edit:

Just a small disclaimer - a sample
has been released on the Creators Club website which uses this basic
design, but is more flexible. Plus, the source is available! As much as
it pains me to say it, it's probably a more useful resource than this
post.

 

The design isn't too technically complicated, but you should at least be familiar with what a state machine and a stack are, and how to use generics.

 

The Problem:

To understand why this structure is useful, I'll first explain the problem it's trying to solve. Imagine a game that has a menu and is pauseable. The game is going to have to keep track of a lot of different GameComponents, and is going to have be constantly fiddling with the Visible and Enabled properties on them, depending on what state the game is in. For example, when you go from the menu to the game, you're going to want to set Visible on all of the menu's UI elements to false. When you pause the game, you want to stop updating everything, but you may want to keep drawing it like normal. It doesn't take long for this kind of ad hoc structure to turn into a bug-prone code circus.

Say for example, you're writing the code that pauses your game.  There's about fifty different GameComponents in your game now, and you have to remember all of the ones that should be disabled when the pause key is pressed. What if you forget to disable the bad guys? Now you've got a bug where the player can pause the game, and everything pauses - everything except the bad guys, who happily carry on blasting your ship apart.

What we need is a better way to keep the GameComponents organized. How do we do it?

 

The Structure:

Basically, we'll turn the game into a sort of state machine. The whole design will center around two classes, GameScreen and GameScreenManager. A GameScreen is a grouping of GameComponents, and roughly corresponds to the current state the game is in: at the menu, waiting to start a game, playing the game, paused, etc. For this example, we'll have the MenuScreen, PlayScreen, and PauseScreen.

Each of the screens keeps the GameComponents it needs in a GameComponentCollection, just like Framework.Game does. GameScreen also has Update and Draw functions that call Update and Draw on their components.

The GameScreenManager is in charge of keeping track of all the different GameScreen. Internally, this is implemented as a stack of active screens.

Here's the structure we have so far (sorry about the shoddy diagram; VS2005's class designer is cool, but I'm not very good at it. There should be a "has-a" association between GameScreenManager and GameScreen - the ActiveGameScreens stack.)

Ok, so why a stack? At first glance, it seems like an odd way to organize your screens. It allows for some pretty cool behavior to be implemented simply, though. Let's say, for example, that when the user pauses the game, you just want to stop everything from updating, and put an image over the top of everything that says "Paused." With a stack, that's easy: you can just push a new PauseScreen onto the stack. When the user unpauses, pop the PauseScreen back off again. To implement the "everything still draws, but nothing updates" functionality, we'll add two new properties to GameScreen. BlocksDraw says that no screens under this one can draw, and BlocksUpdate does the same thing for update.

Now, we'll give GameScreenManager Update and a Draw functions. In his Update, he'll go through the stack of ActiveGameScreens from top to bottom, calling Update on everyone until he finds someone that BlocksUpdate.

Draw is a bit more complicated, since we want things on the top of the stack to draw after things on the bottom. Bear with me here, think of pause again: we want to draw the game screen, then the pause screen over the top. In order to get this behavior we have to draw from bottom to top. But, in order to know what to draw we have to go through the stack from top to bottom, looking for BlocksDraw. So, we'll go through twice: Draw could be implemented using a temporary list called screensToDraw. First go through the stack from top to bottom, adding to screensToDraw until you find one that BlocksDraw. Then go through screensToDraw backwards drawing all the screens. Iterating through the screens twice is not likely to cause any kind of noticeable performance hit: it's hard to imagine cases where you'd have more than around five screens on the stack.

 List<GameScreen> screensToDraw = new List<GameScreen>();
foreach (GameScreen screen in ActiveGameScreens)
{
    screensToDraw.Add( screen );
    if (screen.BlocksDraw)
        break;
}

for (int i = screensToDraw.Count - 1; i <= 0; i--)
{
    screensToDraw[i].Draw( gameTime );
}

 

Different Screens:

It works for more than just pausing, too. You could split your HUD and main game into two separate screens, making their code easier to understand. You could go even further, splitting your HUD into separate elements, and then give the user control over what UI elements they want to see without completely muddying up your code.

In a first person shooter, you might have a console that slides down and takes up most of the screen. This could be a screen with BlocksDraw and BlocksUpdate both set to false. A full screen in game menu (the kind you hit esc to get to) would be a screen with BlocksDraw and BlocksUpdate both set to true. BlocksDraw is set to true for this one because there's no point in rendering the whole game if the menu is just going to draw over it anyway.

 

Switching States:

If we give GameScreens a reference back to the GameScreenManager, they can access the ActiveGameScreens stack in order to move the game from one state into another. To replace one screen with another, like a MenuScreen might want to, just pop the current one off, then push the new one on. We saw above how pause would work: PlayScreen watches for the pause key, and pushes on a new PauseScreen. PauseScreen watches for pause button, and pops itself off. GameScreenManager's constructor takes a GameScreen for the startup screen.

 

Initialization - A Complication:

When a game is first created, the graphics device is created, and then the base Framework.Game.Initialize is called. This function then calls Initialize on all of the GameComponents in the Components collection. Internally, the DrawableGameComponent.Initialize function does some magic to make DrawableGameComponent.GraphicsDevice and LoadGraphicsContent work. Obviously, those are important, so we need Initialize to be called on all the DrawableGameComponents.

However, since our screen's components aren't in Game.Components, Game won't call Initialize for us: we're going to need to do it ourselves. We'll add an Initialize function to GameScreen, which will call Initialize on all of its components. Now, every time you create a GameScreen, remember to call Initialize on it - and make sure the graphics device is ready when you do so!

It all sounds complicated, but just remember to Initialize your GameScreens, and create your GameScreenManager in your game's Initialize, not the constructor. Even if you don't understand that part above, if you do those two things you'll be fine.

 

Final Design:

Again, sorry about the diagram; There should be a "has-a" association between GameScreenManager and GameScreen - the ActiveGameScreens stack.

 

Potential Improvements:

To reduce stress on the garbage collector, advanced users might want to keep a cache of screens somewhere, rather than instantiating fresh ones every time. Take it easy on the garbage collector, and your performance will thank you. smile_regular