How to structure your game with IGF?

Recently in the SunBurn engine forums, I answered Johnnyr on a few questions he had before buying a license for this awesome engine. He was looking for 2D support on it and I replied that it was definitively possible to make 2D games with SunBurn however, he’d have to code the sprite management system as SunBurn 2D features are, for now, limited to effective rendering with all the benefits on shaders you can already use in 3D (multiple light types, diffuse, specular, normal, etc…).

Unless he uses the 2D Sprite System I introduced in IGF v0.4 which actually implements such sprite management with support for collisions for instance or simply working in 2d world space (Position, Rotation and Scale are for instance managed using Vector2 instead of Vector3).

He then downloaded IGF and looked at it and since he was a beginner, he was a little bit confused with the way the samples were separated into multiple classes. It wasn’t obvious for me but such point may be relevant for many other developers so I decided to write down a simple blog post that explains how IGF structures your games so that you can easily design your game classes for better maintainability.

Overview

In strict XNA, you’ll generally use three important classes to make your games:

  • The Game class which is responsible of running your game Initialize, Update and Draw code as well as loading and unloading content from your Content project(s). It also holds a list of Components used for specialized and reusable code.
  • The GameComponent class gives you an easy way to implement a component that will get its Update method called making them ideal for input gathering, AI, networking, or any other system feature that doesn’t need to draw on screen.
  • The DrawableGameComponent class inherits from GameComponent and adds a Draw method that gets also called by the Game instance you’ve added it to. That’s where you will put all your rendering code.

While I really think such design helps newcomers understanding the roots of making a game (Update/Draw logic) and simple object oriented programming principles (inheritance, reusability, classes and instances…), I never felt very satisfied with it especially when you start working on a game that gets slightly more complicated.
The GameState sample provided by the Xna team did remove some of this frustration but I still felt limited on how I could structure my game code. Moreover, when you start coding your game entities (Player, Enemies, Pick ups, Vehicles, whatever…), you never really know what would be the best way to plug them together.

That’s why I decided to work on a Application structure framework which has a slightly different point of view. The main principle behind IGF application structure is that you have a set of classes that let you work from a global to detailed view and I’ll now go through them in this exact same order.

Application class

The Application class inherits from the XNA Game class and adds a few properties and methods that are commonly used all over the game. It also adds some static properties that let you access useful members from anywhere in your game such as the GraphicsDevice for instance or specific to SunBurn, the SunBurn SceneInterface instance to access its managers (ObjectManager, RenderManager, CollisionManager, etc…).

It also implements a Game State management system for you to load/unload (asynchronously or synchronously) levels for instance.

The Application class, as its parent class, is responsible to initialize, update and draw your specialized code. It’s the root of your game.

GameState class

A GameState is the first division of your game. In general, a game is divided into multiple parts:

  • an introduction with one or many movies or splash screens;
  • a main menu where players navigate to manage their options, or select to play a game solo or multi;
  • levels that have each different content but share the same game logic mechanics

And they can be a lot more: for instance, you can have multiple game states for levels depending on the mechanics: a Capture the Flag map would have different rules than a Death match map.

Therefore, IGF comes with the GameState concept which can be seen as a mini Application (or XNA Game) class. It has mostly the same responsibility but limited to the state they have to manage: Initialize, Load/Unload content, Update and Draw.

You can directly implement your own Update and Draw code in their respective methods that get called once the GameState instance if fully initialized and content loaded (managed by the Application class). To help you, you have access to the Application running instance directly within your inherited class.

It brings several advantages:

  • You can initialize only the features you really need: you may not need any special collision or networking feature in an introduction game state since you’ll only display promotional or information to the player;
  • You know exactly where to look at when something goes wrong in your game: if you have an issue with the way your menu works in your main menu game state, that’s probably the place to look for a bug;
  • You can create specialized GameState classes and reuse them for multiple games: I for instance use the exact same IntroductionGameState class for all the samples provided with IGF.

Layer class

The Layer class is a rendering specialized class. It’s purpose is to mimic how you may structure an image when working on Photoshop, GIMP or any other graphics program that lets you separate your drawing logic in multiple layers.

They are only needed in two situations: you want to use  SunBurn rendering in the current GameState instance or you have a really complex rendering structure where you mix background rendering, SunBurn rendering, HUD or GUI rendering and any other custom rendering code you may want to apply.

The Layer class gets its BeginDraw, Draw and EndDraw methods called each frame by the current active GameState they are added to.

You may use them several ways but I generally would suggest to associate the rendering AND content to be rendered directly inside them.

For instance, I’m right now working on the GUI feature for IGF v0.5. and I put my label, image and button instances in a specialized GUI layer that implements the IContentHost interface which automatically tells IGF that this Layer needs to load content when added to the GameState. I then use the fields present in the Layer if needed. This makes it very easy to know where to look at in your game while debugging.

public class GUILayer : Layer, IContentHost
{
    private Label myLabel;
    private Button myButton;

    public GUILayer(GameState gameState):base(gameState)
    {
    }

    public void LoadContent(IContentCatalogue catalogue, ContentManager manager)
    {
         myLabel = new Label("Fonts/MySpriteFont");
         myButton = new Button("Fonts/MySpriteFont");

         catalogue.Add(myLabel);
         catalogue.Add(myButton);

         var guiManager = Application.SunBurn.GetManager<GuiManager>(true);
         guiManager.Add(myLabel);
         guiManager.Add(myButton);
    }

    public void UnloadContent(IContentCatalogue catalogue)
    {
    }
}

There is a special case for SunBurn rendering: the SunBurnLayer. In order to ease your life and still allow you to create GameState classes that don’t need to use SunBurn rendering if not necessary, I implemented the SunBurnLayer which inherits from Layer and renders everything you submitted to the SunBurn ObjectManager instance. It also handles the code related to the required SunBurn SplashScreen presence so that you don’t have to care much about.

Note that the Layer rendering code is called in the same order your added them to the GameState. So, if for example, you add a layer responsible to render a background texture to the full screen after adding the SunBurnLayer instance, you won’t see any of the entities you would have submitted but the full texture.

Here is a quick code exerpt on how to add Layers to your game state:

public class MainMenuGameState : GameState
{
    private BackgroundLayer _backgroundLayer; // the layer responsible to render a nice texture behind everything
    private RootMenuLayer _rootMenuLayer; // the layer responsible to render the root of the main menu

    // UNRELEVANT CODE OMITTED

    public override void Initialize()
    {
        _backgroundLayer = new BackgroundLayer(this);
        _rootMenuLayer = new RootMenuLayer(this);

        AddLayer(_backgroundLayer);
        AddLayer(new SunBurnLayer(this)); // we imagine that we want to render a few meshes using a simple scene
        AddLayer(_rootMenuLayer);
    }
}

The great thing with Layer instances is that you can also directly manage if they should render or not using the IsVisible property. This could be helpful for instance if I would add a SettingsMenuLayer to the above example and implement the code so that when a player presses the Settings button in the RootMenuLayer, it sets the RootMenuLayer.IsVisible property to false and the SettingsMenuLayer.IsVisible property to true.

SunBurn SceneObject and SceneEntity

Then, you can work on your own game entities either using the built in SceneObject class or using your own custom SceneObject or SceneEntity class depending on the desired reusability.

If you have a game entity that will spawn multiple times and has its unique game mechanics, you may want to save you coding time by implementing the logic in a specialized SceneObject that you would therefore simply submit to the SunBurn SceneInterface.ObjectManager instance as much as you need.

Final words

There are still one level (the most detailed actually) that I didn’t went on which is the Agents level (PlayerAgent & NonPlayerAgent) with their Behavior classes and Commands. These are where you may want to place reusable game logic that you attach to any game entity (SceneObject or SceneEntity instances) for your game. The Pong sample makes an heavy usage on it and I already talked a bit about them in other posts.

If such logic reusability isn’t very clear, I’ll try to get back and publish another blog entry to explain how you can structure your player logic and AI using them ;)

One Comment

  1. Daniel says:

    Thanks for taking the time to provide some context on how these classes are intended to be used. I got most of this from the sample projects but the extra clarity provided here is definitely useful.

    Cheers
    Daniel

Leave a Reply