In this chapter, we’re going to be building the main class of our engine, rightfully called “Engine”. Start off by adding a new file to your “Components” folder called “Engine.cs”. Add the following code to create a static class called “Engine”. The difference between this and all the other classes we’ve created so far is that it doesn’t have to be instantiated (using “new ObjectType();”). Instead we can get its members using “Engine.MemberName”. Here is the starting code:

using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Innovation
{
    public static class Engine
    {
    }
}

We are going to add some member variables to the Engine class. Add the following to our class:

// The GraphicsDevice the engine is using
public static GraphicsDevice GraphicsDevice;

// The Engine's SpriteBatch
public static SpriteBatch SpriteBatch;

// The collection of GameScreens we are managinng
public static GameScreenCollection GameScreens = new GameScreenCollection();

// The current GameTime
public static GameTime GameTime;

// Whether the Engine has been initialized yet
public static bool IsInitialized = false;

Next we are going to create a method to initialize the engine. This method accepts an IGraphicsDeviceService object which helps us set up the Engine’s graphics. If initializing from the Game class, this is the GraphicsDeviceManager graphics that is created in the default Game template. The initialize method can be found below:

// Initializes the engine
public static void SetupEngine(IGraphicsDeviceService GraphicsDeviceService)
{
    // Setup the GraphicsDevice and SpriteBatch
    Engine.GraphicsDevice = GraphicsDeviceService.GraphicsDevice;
    Engine.SpriteBatch = new SpriteBatch(GraphicsDeviceService.GraphicsDevice);

    Engine.IsInitialized = true;
}

The next method we will need is an update method. This will update all the GameScreens who will then update their components. There is some complex logic in updating and drawing because screens are allowed to block drawing and updating of each other. The update accepts a GameTime to update the internal GameTime, as does the draw method.

// Update the engine, screens, and components
public static void Update(GameTime gameTime)
{
    // Update the game time
    Engine.GameTime = gameTime;

    // Create a temporary list
    List<GameScreen> updating = new List<GameScreen>();

    // Populate the temp list
    foreach (GameScreen screen in GameScreens)
        updating.Add(screen);

    // BlocksUpdate and OverrideUpdateBlocked login
    for (int i = GameScreens.Count - 1; i >= 0; i--)
        if (GameScreens[i].BlocksUpdate)
        {
            if (i > 0)
                for (int j = i - 1; j >= 0; j--)
                    if (!GameScreens[j].OverrideUpdateBlocked)
                        updating.Remove(GameScreens[j]);

            break;
        }

    // Update remaining components
    foreach (GameScreen screen in updating)
        if (screen.Initialized)
            screen.Update();

    // Clear list
    updating.Clear();

    // Repopulate list
    foreach (GameScreen screen in GameScreens)
        updating.Add(screen);

    // BlocksInput and OverrideInputBlocked login
    for (int i = GameScreens.Count - 1; i >= 0; i--)
        if (GameScreens[i].BlocksInput)
        {
            if (i > 0)
                for (int j = i - 1; j >= 0; j--)
                    if (!GameScreens[j].OverrideInputBlocked)
                        updating.Remove(GameScreens[j]);

            break;
        }

    // Set IsInputAllowed for all GameScreens
    foreach (GameScreen screen in GameScreens)
        if (!screen.InputDisabled)
            screen.IsInputAllowed = updating.Contains(screen);
        else
            screen.IsInputAllowed = false;
}

The draw method is very similar, we do some logic for BlocksDraw and for the type of components we are supposed to be drawing, and then we draw them.

// Draws the current collection of screens and components. Accepts a
// ComponentType to render
public static void Draw(GameTime gameTime, ComponentType RenderType)
{
    // Update the time, create a temp list
    Engine.GameTime = gameTime;
    List<GameScreen> drawing = new List<GameScreen>();

    // Clear the back buffer
    GraphicsDevice.Clear(Color.CornflowerBlue);

    // Populate the temp list if the screen is visible
    foreach (GameScreen screen in GameScreens)
        if (screen.Visible)
            drawing.Add(screen);

    // BlocksDraw and OverrideDrawBlocked logic
    for (int i = GameScreens.Count - 1; i >= 0; i--)
        if (GameScreens[i].BlocksDraw)
        {
            if (i > 0)
                for (int j = i - 1; j >= 0; j--)
                {
                    if (!GameScreens[j].OverrideDrawBlocked)
                        drawing.Remove(GameScreens[j]);
                }

            break;
        }

    // Draw the remaining screens
    foreach (GameScreen screen in drawing)
        if (screen.Initialized)
            screen.Draw(RenderType);
}

Next we are going to add an IServiceContainer object. This is an object that will keep track of objects called Service Providers. We can retrieve and store Service Providers by their type. For example, to get the GraphicsDeviceService, we would use “Engine.Services.GetService(typeof(IGraphicsDeviceService));”. Or, to store a ContentManager object called “content”, we would use “Engine.Services.AddService(typeof(ContentManager), content);”. We are going to create a class that inherits from IServiceContainer, which contains methods to add, remove, and get services. Here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Innovation
{
    public class IEServiceContainer : IServiceProvider
    {
        // Contains the service types and services
        Dictionary<Type, object> services = new Dictionary<Type, object>();

        // Add a new service
        public void AddService(Type Service, object Provider)
        {
            // If we already have this type of service provider, throw an
            // exception
            if (services.ContainsKey(Service))
                throw new Exception("The service container already has a "
            + "service provider of type " + Service.Name);

            // Otherwise, add it to the list
            this.services.Add(Service, Provider);
        }

        // Get a service from the service container
        public object GetService(Type Service)
        {
            // If we have this type of service, return it
            foreach (Type type in services.Keys)
                if (type == Service)
                    return services[type];

            // Otherwise, throw an exception
            throw new Exception("The service container does not contain "
            + "a service provider of type " + Service.Name);
        }

        // A shortcut way to get a service. The benefit here is that we
        // can specify the type in the brackets and also return the
        // service of that type. For example, instead of
        // "Camera cam = (Camera)Services.GetService(typeof(Camera));",
        // we can use "Camera cam = Services.GetService()"
        public T GetService<T>()
        {
            object result = GetService(typeof(T));

            if (result != null)
                return (T)result;

            return default(T);
        }

        // Removes a service provider from the container
        public void RemoveService(Type Service)
        {
            if (services.ContainsKey(Service))
                services.Remove(Service);
        }

        // Gets whether or not the container has a provider of this type
        public bool ContainsService(Type Service)
        {
            return services.ContainsKey(Service);
        }
    }
}

Now we need to create an instance of it in our Engine class. Add the following somewhere in the top of the Engine class:

// The engine's service container
public static IEServiceContainer Services;

And the following in the SetupEngine() method:

// Setup the service container and add the IGraphicsDeviceService to it
Engine.Services = new IEServiceContainer();
Engine.Services.AddService(typeof(IGraphicsDeviceService),
    GraphicsDeviceService);

Now we are going to create a custom ContentManager and add it to the Engine class. Our ContentManager will expand on XNA’s base ContentManager by allowing us to choose if we want to use the content cache (store loaded objects loaded so we don’t need to load the same asset twice), and allow us to unload specific pieces of content individually instead of unloading everything. Here is the code:

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Content;

namespace Innovation
{
    public class IEContentManager : ContentManager
    {
        // Do nothing in the constructor except inherit from ContentManager
        public IEContentManager(IServiceProvider serviceProvider)
            : base(serviceProvider) { }

        // Whether or not we should keep objects that have been loaded. This
        // way we can avoid loading assets multiple times. However, this may
        // lead to problems with multiple objects changing loaded data, such
        // as effects on a model
        public bool PreserveAssets = true;

        // Keep a list of disposable assets and loaded assets
        List<IDisposable> disposable = new List<IDisposable>();
        Dictionary<string, object> loaded = new Dictionary<string, object>();

        // Override loading of assets so we can use our own functionality
        public override T Load<T>(string assetName)
        {
            // Create a new instance of the requested asset
            T r = this.ReadAsset<T>(assetName, RecordIDisposable);

            // If we are holding on to loaded assets, add it to the list of
            // loaded assets
            if (PreserveAssets && !loaded.ContainsKey(assetName))
                loaded.Add(assetName, r);

            // Return the loaded asset
            return r;
        }

        // Internal method to record disposable assets
        void RecordIDisposable(IDisposable asset)
        {
            // If we are monitoring loaded assets, add it to the list of
            // disposable assets
            if (PreserveAssets)
                disposable.Add(asset);
        }

        // Unload all content
        public override void Unload()
        {
            // Dispose all disposable assets
            foreach (IDisposable disp in disposable)
                disp.Dispose();

            // Clear all loaded assets
            loaded.Clear();
            disposable.Clear();
        }

        // Unload a specific piece of content
        public void Unload(string assetName)
        {
            // If the asset has been loaded
            if (loaded.ContainsKey(assetName))
            {
                // If it is disposable, dispose it and take it off the
                // list of disposable content
                if (loaded[assetName] is IDisposable
                        && disposable.Contains((IDisposable)loaded[assetName]))
                {
                    IDisposable obj = disposable[
                        disposable.IndexOf((IDisposable)loaded[assetName])];

                    obj.Dispose();

                    disposable.Remove(obj);
                }

                // Take it off the list of loaded content
                loaded.Remove(assetName);
            }
        }
    }
}

Now we need to add an instance of our content manager to the Engine class. Add the following in the class near the other member declarations:

// The engine's content manager
public static IEContentManager Content;

Now initialize it by adding this at the end of SetupEngine():

// Setup the content manager using the service container
Engine.Content = new IEContentManager(Services);

Now we are going to do is add some GameScreens to the Engine class. The first GameScreen is called BackgroundScreen, and overrides BlocksDraw, BlocksUpdate, and BlocksInput. This way we can have components running in the background that may need to update or accept input, such as the input class. We will create another called DefaultScreen, and set it to the BackgroundScreen. This screen is the one that components will be created in unless they specify otherwise. We can change the DefaultScreen while running, so new components will be created on that screen instead. If the current DefaultScreen is disabled, it will be set back to the BackgroundScreen. So, add this to the class:

// GameScreen provided by the engine.
public static GameScreen BackgroundScreen;

// The GameScreen to set to new GameScreens when a screen is not specified
public static GameScreen DefaultScreen;

And add this in SetupEngine():

// Setup the background screen
BackgroundScreen = new GameScreen("Engine.BackgroundScreen");
BackgroundScreen.OverrideUpdateBlocked = true;
BackgroundScreen.OverrideDrawBlocked = true;
BackgroundScreen.OverrideInputBlocked = true;

// Set the default screen to the background screen so new screens will
// use it automatically unless told otherwise
DefaultScreen = BackgroundScreen;

The final thing we need to do is create the GameScreenCollection class. This is a class that keeps track of GameScreens. It inherits from KeyedCollection, so it can return a GameScreen by name, like so: “Engine.GameScreens["GameScreenName"]“. This is better than a list because with a list we would have to keep track of number IDs for the screens. It also has some logic to handle the resetting of Engine.DefaultScreen if it is removed. Here’s the code:

using System.Collections.ObjectModel;

namespace Innovation
{
    public class GameScreenCollection : KeyedCollection<string, GameScreen>
    {
        // Allow us to get a screen by name like so:
        // Engine.GameScreens["ScreenName"]
        protected override string GetKeyForItem(GameScreen item)
        {
            return item.Name;
        }

        protected override void RemoveItem(int index)
        {
            // Get the screen to be removed
            GameScreen screen = Items[index];

            // If this screen is the current default screen, set the
            // default to the background screen
            if (Engine.DefaultScreen == screen)
                Engine.DefaultScreen = Engine.BackgroundScreen;

            base.RemoveItem(index);
        }
    }
}

Well, thats all for this tutorial! Dont be dissappointed though, we are now done with our engine framework! In the next tutorial we will actually start working on some game components!

Download the Code for this Chapter

Back to Game Engine Tutorial Series

« »