One of the problems I often see is that people who are learning tend to spend much of their time learning graphics, and not enough time learning how to structure their game or game engine. In this tutorial series I am going to share what I’ve learned on this topic. This is by no means the only or ‘right’ way to do it, but there certainly are wrong ways, and I hope to help you avoid many of them.

There is a sample on the XNA Creators Club website that is a great starting point for game structure research. The design of the engine we’ll be designing is based partly on this tutorial. (http://creators.xna.com/en-us/samples/gamestatemanagement). So, let’s get started! The basic structure of the engine is as follows:

Game Engine Structure

  • Component class: This class is the base that all objects that will be managed by the engine should inherit from. It contains “Update()” and “Draw()” methods to update and draw itself. It also contains a “Visible” property to set whether or not to draw the component.
  • GameScreen class: This class contains a group of Components and handles their updating and drawing. It contains “BlocksDraw”, “BlocksUpdate”, and “BlocksInput” properties to block the drawing, updating, and input processing of the screens below. This allows for screens to easily implement functionality like pausing the game. Of course, the screens can override this so that screens that must always draw or update or something similar can still do so.
  • Engine class: This is a static class that contains a list of GameScreens. It manages their update and draw, and also contains a number of useful properties, such as a service container, the GraphicsDevice and SpriteBatch, and more.
Engine Structure

Engine Structure

Setting Up Visual Studio

Solution Explorer - Solution Setup

Solution Explorer - Solution Setup

Before we can start on our code, we need to get Visual Studio set up. We will have two projects in our solution, one for the engine itself and another for the demo game that utilizes it. So, open up Visual Studio, and select File-New Project, and choose type XNA Windows Game Library, and choose a name. I used “InnovationEngine”, but any name will do. When the project is all set up, open up the Solution Explorer and right click your solution (The top node). Select Add-New Project, and choose Type XNA Windows Game. Pick a name (I used “TestEnvironment”), and press OK. Finally, we need to reference our engine project from the game project. Expand the game’s node (It is labelled “TestEnvironment” in the example above”), right click “References”, and choose “Add Reference”. Click on the “Projects” tab and choose the name of your engine project. (In this case, “InnovationEngine”). Add a new folder to your engine project and call it “Components”. We are now all setup. To run the game, right click on it’s project and click “Set as Startup Project”. Now, if you press F5 your game should start up, and present you with a blank blue screen.

This helps us avoid common mistake #1 – Not seperating engine code and game code. This is almost always a good idea, unless the game is very simple. Not only does it help keep things straight in your solution, but it can greatly ease in the creation of an editor later on, and it helps to keep the code in a format that can be reused later on in different games.

Component Class

The first class we need to write is the component class. This is the basis for all objects managed by the engine. Add a new file to the engine project by right clicking on it and selecting Add-New Item-Code File, and call it “Component.cs”. Add the following code to that follow. Note: Please do not just copy and paste the code, actually type it out so you can learn and understand what you’re doing.

using Microsoft.Xna.Framework;

namespace Innovation
{
    public class Component
    {
	    // The GameScreen object that owns this component
        public GameScreen Parent;

	    // Whether or not this component has been initialized
        public bool Initialized = false;

    	// Whether or not the GameScreen that owns the component
	    // should draw it
        public bool Visible = true;

	    // This overloaded constructor allows us to specify the parent
        public Component(GameScreen Parent)
        {
            InitializeComponent(Parent);
        }

	    // This overload allows will set the parent to the default
	    // GameScreen
        public Component()
        {
            InitializeComponent(Engine.DefaultScreen);
        }

        // This is called by the constructor to initialize the
        // component. This allows us to only have to override this
        // method instead of both constructors
        protected virtual void InitializeComponent(GameScreen Parent)
        {
            // Check if the engine has been initialized before setting
            // up the component, or the Engine will crash when the
            // component tries to add itself to the list.
            if (!Engine.IsInitialized)
                throw new Exception("Engine must be initialized with "SetupEngine()""
                    + "before components can be initialized");

            Parent.Components.Add(this);
            Initialized = true;
        }

	    // Updates the component - This is called by the owner
        public virtual void Update()
        {
        }

	    // Draws the component - This is called by the owner
        public virtual void Draw()
        {
        }

    	// Unregisters the component with its parent
        public virtual void DisableComponent()
        {
            Parent.Components.Remove(this);
        }
    }
}

This should be pretty self explantory, the important thing to know is that a component is owned by a GameScreen, who calls the component’s update and draw methods every frame.

Having a clearly defined base class helps us avoid common mistake #2 - Not having a class that the engine knows how to work with. If your engine is made up of a bunch of differently formatted classes, then you have to worry about how each component will be updated, drawn etc. Having a base class means that all we have to do is inherit from it, change the update and draw a little bit, and the engine will draw, update, and manage the component for us, without any extra code. This is also useful because every component in the game can be accessed using the type of the base class, even if it is actually a terrain, sky, or anything else. This greatly simplifies our update, render, and component management logic.

Component Interfaces

The next thing we are going to do is define some interfaces for our components. An interface is a way of defining how an object interacts with other objects. It also lets us distinguish between types of objects. The interfaces we will be defining here are I2DComponent and I3DComponent. This allows to tell the engine if our component uses 2D or 3D for drawing and updating. It also allows the engine to get some basic information about an object’s position, rotation, etc. This simplifies things like picking (detecting what a mouse clicked on or what is under a target reticle), and things like adding modifiers in an editor to control position, rotation, etc.

We will also be defining an enum that will list various types of object groups: 2D, 3D, Both, and All. We can use this enum later on for things like rendering water. When drawing the reflections for water, we don’t want to have the HUD elements drawn with it in the reflection, so we can use this enum like this: Engine.Draw(ComponentType.Component3D); This will draw only 3D objects, so the HUD will not be drawn in the reflection. This lets us avoid common misake #3 - Having a base class, but not providing a way to tell the engine anything useful about it. By providing these interfaces, we can modify an object’s position, rotation, scale, etc. regardless of what kind of component it is. The code for the interfaces and object type enum are below. Add these into a new file called ObjectType.cs

using Microsoft.Xna.Framework;

namespace Innovation
{
    // Represents a 3D object. These objects will be drawn before
    // 2D objects, and will have modifiers automatically provided
    // in the editor.
    public interface I3DComponent
    {
        // Position in the Cartesian system (X, Y, Z)
        Vector3 Position { get; set; }

        // Rotation represented as a Vector3. This shouldn't
        // be used for calculations, it is left in so that
        // the rotation can be more easily modified by hand
        Vector3 EulerRotation { get; set; }
        // Rotation as a Matrix. This will give much smoother
        // and cleaner calculations that a Vector3
        Matrix Rotation { get; set; }

        // Scale for each axis (X, Y, Z)
        Vector3 Scale { get; set; }

        // BoundingBox to use for picking and pre-collision
        BoundingBox BoundingBox { get; }
    }

    // Represents a 2D object. These objects will be drawn after
    // 3D objects, and will have modifiers automatically provided
    // in the editor.
    public interface I2DComponent
    {
        Rectangle Rectangle { get; set; }
    }

    public enum ComponentType
    {
        // Represents all 2D components (I2DComponent)
        Component2D,
        // Represents all 3D components (I3DComponent)
        Component3D,
        // Represents all components that are either 2D or 3D
        // components
        Both,
        // Represents all components regardless of type
        All
    }
}

GameScreen

A GameScreen is a class that contains a group of components, and handles their updating, drawing, and input. It contains the properties BlocksDraw, BlocksInput, and BlocksUpdate. These can be used to control the screens that are “under” then if they are imagined as a stack of GameScreens, with the newest on top.

For example, if you add a new GameScreen with BlocksUpdate, BlocksInput, and BlocksDraw set to true, then you have effectively just created a pause menu. This can save huge amount of time because it is very easy to create screens like this that control those under them. There is no need to create a complex state system to keep track of what the game is doing. We can just add new screens and change their properties to suit our needs.

This brings us to common mistake #4 - Not segregating code into clearly defined sections and creating an overly complex state management system. While this may seem like overkill, and for smaller games it certainly is, this system is extemely flexible and can be expanded with very little effort.

While this kind of state management could be built into the final game itself and not managed by the engine, by including it right into engine itself, we get a flexible design provided right inside the engine, and we have a framework to design our game around.

So, without further ado, here’s the code for the GameScreen. Add it to a new file called GameScreen.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Innovation
{
    public class GameScreen
    {
        // Keep track of all the components we need to manage
        public ComponentCollection Components;

        // Whether or not to draw
        public bool Visible = true;

        // Whether or not this screen should block the update of
        // screens below (for pause menus), etc.
        public bool BlocksUpdate = false;

        // Whether or not this screen can override a blocked update
        // from an above screen (for a background screen), etc.
        public bool OverrideUpdateBlocked = false;

        // Same for drawing
        public bool BlocksDraw = false;

        // Same for drawing
        public bool OverrideDrawBlocked = false;

        // Same for input
        public bool BlocksInput = false;

        // Same for input
        public bool OverrideInputBlocked = false;

        // Whether or not we want to block our own input so we can
        // do things like loading screens that will want to accept
        // input at some point, but not at startup
        public bool InputDisabled = false;

        // This is set by the engine to tell us whether or not input
        // is allowed. We can still get input, but we shouldn't. This
        // is useful because a ProcessInput() type of function would
        // make it hard to manage input (because we can't utilize
        // events, etc.)
        public bool IsInputAllowed = true;

        // The name of our component, set in the constructor. This
        // is used by the Engine, because a GameScreen can be accessed
        // by name from Engine.GameScreens[Name].
        public string Name;

        // Fired when the component's Initialize() is finished. This can
        // be hooked for things like asynchronous loading screens
        public event EventHandler OnInitialized;

        // Whether or not the component is initialized. Handles firing of
        // OnInitialized.
        bool inititalized = false;
        public bool Initialized
        {
            get { return inititalized; }
            set
            {
                inititalized = value;
                if (OnInitialized != null)
                {
                    // Fire the OnInitalized event to let other's know we
                    // are done initializing
                    OnInitialized(this, new EventArgs());
                }
            }
        }

        // Constructor takes the name of the component
        public GameScreen(string Name)
        {
            // Setup our component collection
            Components = new ComponentCollection(this);

            // Register with the engine and set our name
            this.Name = Name;
            Engine.GameScreens.Add(this);

            // Initialize the component
            if (!Initialized)
                Initialize();
        }

        // Overridable function to initialize the GameScreen
        public virtual void Initialize()
        {
            this.Initialized = true;
        }

        // Update the screen and child Components
        public virtual void Update()
        {
            // Create a temporary list so we don't crash if
            // a component is added to the collection while
            // updating
            List<Component> updating = new List<Component>();

            // Populate the temporary list
            foreach (Component c in Components)
                updating.Add(c);

            // Update all components that have been initialized
            foreach (Component Component in updating)
                if (Component.Initialized)
                    Component.Update();
        }

        // Draw the screen and its components. Accepts a ComponentType
        // to tell us what kind of components to draw. Either 2D, 3D, or
        // both. (Useful for drawing a reflection into a render target
        // without 2D components getting in the way)
        public virtual void Draw(ComponentType RenderType)
        {
            // Temporary list
            List<Component> drawing = new List<Component>();

            foreach (Component component in Components)
            {
                if (RenderType == ComponentType.Both)
                {
                    // If the render type is both, we will draw all 2D or
                    // 3D components
                    if (component is I2DComponent || component is I3DComponent)
                        drawing.Add(component);
                }
                else if (RenderType == ComponentType.Component2D)
                {
                    // If the render type is 2D, we will only draw 2D
                    // components
                    if (component is I2DComponent)
                        drawing.Add(component);
                }
                else if (RenderType == ComponentType.Component3D)
                {
                    // If the render type is 2D, we will only draw 3D
                    // components
                    if (component is I3DComponent)
                        drawing.Add(component);
                }
                else
                {
                    // Otherwise, we will draw every component regardless of type
                    drawing.Add(component);
                }
            }

            // Keep a list of components that are 2D so we can draw them on top
            // of the 3D components
            List<Component> defer2D = new List<Component>();

            foreach (Component component in drawing)
                if (component.Visible && component.Initialized)
                {
                    // If the component is visible and is not loading itself..
                    if (component is I2DComponent)
                    {
                        // If it is 2D, wait to draw
                        defer2D.Add(component);
                    }
                    else
                    {
                        // otherwise, draw immediately
                        component.Draw();
                    }
                }

            // Draw 2D components
            foreach (Component component in defer2D)
                component.Draw();
        }

        // Disables the GameScreen
        public virtual void Disable()
        {
            // Clear out our components
            Components.Clear();

            // Unregister from the Engine's list
            Engine.GameScreens.Remove(this);

            // If the engine happens to have this screen set as the default
            // screen, set it to the background screen in the Engine class
            if (Engine.DefaultScreen == this)
                Engine.DefaultScreen = Engine.BackgroundScreen;
        }

        // Override ToString() to return our name
        public override string ToString()
        {
            return Name;
        }
    }
}

One thing to note here is input. We don’t want to limit our input by forcing it all into one function on the GameScreen, because we want to be able to take advantage of things like key press events. So, we have a boolean called IsInputAllowed. This will be calculated by the engine each frame, so when checking input we should also check to see if IsInputAllowed equals true before doing anything. Unless, of course, we want to ignore this to move a camera around in the background of the pause screen or something similar.

The GameScreen class has a member called Components, which is a ComponentCollection. This is a custom collection that handles the management of the Parent value for components when it is moved around various screens. The code for this collection is below. Add it to a file called ComponentCollection.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;

namespace Innovation
{
    // A custom collection for managing components in a GameScreen
    public class ComponentCollection : Collection<Component>
    {
        // The GameScreen to manage components for
        GameScreen owner;

        public ComponentCollection(GameScreen Owner)
        {
            owner = Owner;
        }

        // Override InsertItem so we can set the parent of the
        // component to the owner
        protected override void InsertItem(int index, Component item)
        {
            if (item.Parent != null && item.Parent != owner)
                item.Parent.Components.Remove(item);

            item.Parent = owner;

            base.InsertItem(index, item);
        }

        // Override RemoveItem so we can set the paren of
        // the component to null (no parent)
        protected override void RemoveItem(int index)
        {
            Items[index].Parent = null;

            base.RemoveItem(index);
        }
    }
}

In the next article, we’ll tackle the engine class itself. For now, read through and make sure you understand everything we have so far.

Download the Code for this Chapter

Back to Game Engine Tutorial Series

« »