The final core class of the engine is the “Engine” class. This class maintains the GameScreen stack, keeps track of utility classes, and is the starting point for updating and drawing all the GameScreens and thus all the Components. Later on it will also be in charge of saving and loading the state of the engine for things like saving the game and creating levels and maps, and it will also interact with the scripting engine.

Once again, start by adding another class to the engine’s project, this time calling it “Engine”. We will need the following using statements:

using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace InnovationEngine
{
    public class Engine
    {
    }
}

Recall from earlier that the GameScreens are supposed to be in a stack. The stack model can be thought of like a stack of plates. You can either put a plate on the top, or take one off the top. You cannot, however, take one from the middle or add one to the middle. In computer science terms, we call adding an item to the “top” of the stack “pushing” it, and removing one from the top “popping” it.


Stack Model

Stack Model


This is exactly what we are trying to emulate with the GameScreens. You can put a GameScreen on the top of the stack, but you can’t take it off until all the screens above it have been removed. For example, in a simple game you may have the following game screens stacked on top of each other, assuming that the player has started the game and has just paused it, pushing a pause screen to the top of the stack.


Simple GameScreen Stack

Simple GameScreen Stack


When the player resumes the game, we simply pop the pause screen off of the stack, which returns us immediately to the HUD and gameplay screens. With the stack system, it is really easy to update and draw the screens in the right order. When drawing, we simply draw from the bottom to the top, and transparent parts of each screen will show through to the one underneath it.

The first thing we need is a list to represent our stack. C# actually provides a Stack class, but we won’t be using it here, as we need to be able to traverse the list from bottom to top without having to pop each screen off and put them all back on again.

List<GameScreen> gameScreens = new List<GameScreen>();

Next we need some functions to push or pop GameScreens from the engine’s stack. You will notice that while the Push function takes a GameScreen as an argument, the Pop function does not. This is because we want to maintain the stack’s order by not allowing GameScreens to be removed at random. If we allowed GameScreens to be added and removed randomly, then the stack’s integrity could be lost.

public void PushGameScreen(GameScreen GameScreen)
{
    if (!gameScreens.Contains(GameScreen))
        gameScreens.Add(GameScreen);
}
public GameScreen PopGameScreen()
{
    if (gameScreens.Count == 0)
        return null;

    // Return the top GameScreen from the stack
    return gameScreens[gameScreens.Count - 1];
}

Next we will work on the Update and Draw functions. You will notice that like we did for components in the GameScreen class, we are once again copying the list of GameScreens, in case that list is modified during the update. In the draw function, notice that we are starting with the 0th GameScreen and working our way to the top, which will draw the screens from bottom to top. These two functions both accept a GameTime as an argument. GameTime is a class provided by XNA, which provides some information on timing, such as the total elapsed time, the time since the last update, etc. We will first need a place to store that GameTime. We will add a local GameTime variable and a get-only property, so the value cannot be changed by external classes except through Update and Draw.

GameTime gameTime = null;

public GameTime GameTime { get { return gameTime; } }

Now we can add the Update and Draw functions. Note that they store the new GameTime into the local gameTime variable, where it is then available through the GameTime property to external classes.

public void Update(GameTime GameTime)
{
    this.gameTime = GameTime;

    List<GameScreen> copy = new List<GameScreen>();

    foreach (GameScreen screen in gameScreens)
        copy.Add(screen);

    foreach (GameScreen screen in copy)
        screen.Update();
}
public void Draw(GameTime GameTime)
{
    this.gameTime = GameTime;

    foreach (GameScreen screen in gameScreens)
        screen.Draw();
}

While we’re here, let’s add a few more properties that we will be using heavily in our Components later on.

ContentManager content = null;
GraphicsDevice graphicsDevice = null;
SpriteBatch spriteBatch = null;
IServiceContainer services = null;

public ContentManager Content { get { return content; } }
public GraphicsDevice GraphicsDevice { get { return graphicsDevice; } }
public SpriteBatch SpriteBatch { get { return spriteBatch; } }
public IServiceContainer Services { get { return services; } }

Later we’ll be adding a few more services to this list, but for now this is all we need. The ContentManager interacts with XNA’s content pipeline to load assets the game needs like models, textures, and fonts. The GraphicsDevice acts as our link to the graphics card. We need it for things like drawing models and mostly anything else 3D to the screen. The SpriteBatch is similar, except that it handles all the 2D drawing and does not actually link directly to the GraphicsCard. The ServiceContainer keeps track of objects used by many classes at once, like a camera or physics simulator. Using a service container makes it really easy to change the camera being rendered from simply by switching out the camera in the service container.

We need to intialize these properties in the constructor. Most of them will be created on the spot, but we’ll need to pass in the GraphicsDevice as it is created by XNA in the Game class.

public Engine(GraphicsDeviceManager Graphics)
{
    services = new ServiceContainer();
    services.AddService(typeof(IGraphicsDeviceService), Graphics);
    services.AddService(typeof(IGraphicsDeviceManager), Graphics);

    this.graphicsDevice = Graphics.GraphicsDevice;

    content = new ContentManager(Services);
    spriteBatch = new SpriteBatch(GraphicsDevice);
}

Our final task is to add a link to the Engine from the GameScreen class, so that the screen and its components have a way to access the services provided by the Engine class. We will do this through a public property on the GameScreen class. Notice that this time, unlike Component’s Parent property, this is a get-only property, meaning that a GameScreen’s Engine cannot be set directly to the GameScreen. This forces us to use the PushGameScreen function in the Engine class, which will make sure we are always maintaining the stack.

That said, we still need a way to tell the GameScreen which instance of the Engine class it belongs to. We do this by marking the private Engine variable as “internal”. This means that anywhere inside the “InnovationEngine” namespace, like the Engine class, we will have access to this variable, while outside the namespace, like in the game itself, we won’t have access to it and thus won’t be able to accidentally break the stack model.

// Set to internal so Engine can access it without allowing
// other classes to set engine. Engine must be set through
// the Engine's PushGameScreen() method so that the stack
// can be maintained
internal Engine engine = null;

public Engine Engine
{
    get { return engine; }
}

Now all we have to do is set this variable to “this” in the Engine class when the screen is pushed onto or popped off of the stack. Before adding it, however, we first check to make sure that the component does not already belong to another instance of the Engine class, as this would break that Engine’s stack.

public void PushGameScreen(GameScreen GameScreen)
{
    // Only allow GameScreens to exist in one Engine at a time
    if (GameScreen.Engine != null)
        throw new Exception("This GameScreen already exists on the stack " +
            " of another Engine instance");

    if (!gameScreens.Contains(GameScreen))
    {
        gameScreens.Add(GameScreen);
        GameScreen.engine = this;
    }
}
public GameScreen PopGameScreen()
{
    if (gameScreens.Count == 0)
        return null;

    GameScreen screen = gameScreens[gameScreens.Count - 1];

    // Stop linking the screen to this Engine instance before removing
    // the screen
    screen.Engine = null;

    // Remove the screen from the top of the stack
    gameScreens.Remove(screen);

    return screen;
}

Download the Code for this Chapter

« »