Version 9, last updated by nolver at April 26, 2010 14:45 UTC
ManualBasics GameStates
Managing "Game States"
What is a game state?
In Tomahawk, "Game States" are the highest level states of a game. To ilustrate this concept, I'd like to provide an example of the typical game states one may found in a game:
- Intro
- Main Menu
- Playing Game Mode 1
- Playing Game Mode n
- Credits
The main goal of a Game State in Tomahawk is managing the assets (archetypes, models, textures, and just about any content) required during a certain period of your gameplay. The Game State defines a mechanism to determine how many and which assets are required, loading them, and releasing them when they are no longer needed.
You must customize the way this procedure is executed by creating your own Game State types. To implement your own game state you just have to inherits from the Tomahawk.Runtime.GameState class.
The game states are mutually exclusive, only one must and can be active at a given moment. Switching to a different game state, will cause the automatic destruction of the previously active game state, and consequently the unloading off all the contents used.
The GameState Class
Implementing your own game state is very easy. You just have to inherit from GameState and override this four methods:
- OnInitialize
- OnLoad
- OnUnload
- OnDestroy
This four methods get called automatically by the engine, so you just have to worry about implementing the right stuff inside each of them.
There is a single and very simple mechanism to load and start running a certain game state, the Load() method:
GameStates.WelcomeGameState gsWelcome = new ExampleGame.GameStates.WelcomeGameState();
gsWelcome.Load();
In this small example we are creating a new instance of a WelcomeGameState class, which a type that directly inherits from GameState. We just then call the Load() method on it. This will cause the currently loaded game state (if any) to be unloaded and destroyed, and then the new one to be created and loaded.
Imagine we had a game state of type "IntroGameState" loaded and currently active when we invoked that code. The exact chain of events happening will be the following:
- OnDestroy, will be called for IntroGameState.
- OnUnload, will be called for IntroGameState
- OnInitialize, will be called for WelcomeGameState
- OnLoad, will be called for WelcomeGameState
OnDestroy and OnUnload will be called for WelcomeGameState as soon as I manually invoke a Load() on a different game state.
Following we provide a detailed explanation of the purpose of each of the methods you have to override when creating your own game states:
OnInitialize
This is the first method that gets called on the game state that is being loaded. Notice that, as described before, this happen only after the previously active game state has been destroyed and unloaded. The goal of this method is to instantiate all the objects that will be required during the execution of the game state. Ok, maybe not all of them (you can still create new objects at any time, if you need to) but at least most of the objects used by your game must be created during this phase.
Typically you will be creating Tomahaw's "logic objects" or "subsystem objects" directly of indirectly from within OnInitialize method of a game state. In many many cases, those objects will depend on a certain amount of content assets to operate. These "files" will have to be loaded from disc so they can be used by the engine. Loading from disc is somewhat costly in terms of execution time, so normally you'll want to:
- Load everything you need ahead of time, before the game starts
- Load everything you need in a single step, a "loading screen"
- Present the user a "loading bar" or any visual proof that the game hasn't hung.
In order to achieve this three objectives, before leaving the OnInitialize method, a "required content assets" list must be created. You don't need to worry about the list itself, that's automatically handled by the engine's Resource Manager. What you need to worry about is that all of your game logic objects (or at least most of them) get created during before leaving OnInitialize on you game state.
The following is an example of the implementation of a game state's OnInitialize method:
/// <summary>
/// Initialization
/// </summary>
protected override void OnInitialize()
{
//create the main menu UI scene
this.mainMenuScene = (MainMenuScene)XWorld.Instance.CreateObject(typeof(MainMenuScene), "MainMenuScene",
@"ExampleGame.Content\Archetypes\UI\MainMenu.xml");
this.mainMenuScene.SceneNotification += new Tomahawk.Runtime.Logic.UI.ControlNotification(mainMenuScene_SceneNotification);
this.mainMenuScene.Visible = true;
this.mainMenuScene.Activate();
}
In this example, we are implementing a game state to handle the "Main Menu" state of a game. While the game is in this state, only a Main Menu screen is needed, so during OnInitialize we create an instance of type MainMenuScene, which indeed a UI screen, that derives from XUIScene. We are using an archetype (an .xml file) that includes the definition of the screen, a collection of UI controls with a variety of content asses requirements.
We are using a few advanced topics here which you may don't know about yet, if that's the case please ignore them for now, and focus on the fact that an engine's logic object is being created, and by doing so we are creating an undetermined number of other objects (in this case, those will be the UI controls defined within the screen). Before exiting OnInitialize methods, a lot of logic objects were created (the screen, and all its UI controls), and for each of them several content assets requirements may have been written down as "required" in the Resource Manager.
Once again, please note that all that was automatically handled by the Resource Manager of the engine. Once we leave OnInitiliaze, the Resource Manager has a complete list of all the content that needs to be loaded from disk, so we can now proceed to the next step.
OnLoad
This method gets called right after OnInitialize, when a new game state is being loaded. At this point of the procedure, we have a certain amount of logic and subsystem's objects created in memory, and all them are probably waiting for some content assets to be loaded from disk.
Loading all the contents required for the game is faisly easy at this point, we just have to invoke the LoadPendantResources() in the Resource Manager. That methods is going to take some time (depending on the amount and the size of the required content to be loaded), so you'll probably want to show up an animated loading screen before calling it.
Following, is an example of how to implement the OnLoad method:
/// <summary>
/// Content Loading
/// </summary>
protected override void OnLoad()
{
//This is where you'll show up you loading screen
//load all the required contents
Engine.Instance.ResourceManager.LoadPendantResources(this.ContentManager);
//This is where you'll hide your loading screen
}
Although implementing the actual loading screen is beyond the topic of this document, please note that if you want it to be animated you'll need to run it in a separate thread. Tomahawk provides some utilities to implement loading screen, those are described here Implementing Loading Screens.
OnDestroy
This method is called for a game state that was active, and is currently being replaced by a new one.
Since you are about to load a new game state, you'll need some room in your memory for their objects and contents. The previously loaded game state need to be destroyed, and all the memory it may be using should be released in order to leave space for the new one. The first stage of this clean up procedure, is the OnDestroy method. You'll have to destroy each one of the objects created during the OnInitialize method of the game state.
By destroying the logic objects, you are removing all the running objects that require the content assets in memory. You have to make sure all the objects that were created during the OnInitialize phase are destroyed before leaving the OnDestroy method of the game state, because it's only when no more objects exists that require the assets those can be freely removed from memory.
The implementation of the destruction stage is usually very simple:
/// <summary>
/// Destruction
/// </summary>
protected override void OnDestroy()
{
//destroy the main menu UI scene
this.mainMenuScene.Destroy();
}
Following with the example, we just had to destroy the Main Menu screen that was created during the OnInitialize. Actually, by calling the method Destroy() on a logic object you are causing the destruction of all its children objects, in this case the large collection of child controls (labels, images, buttons, etc) that lived within the main menu screen. Once the screen is destroyed, all the fonts and textures used to implement the screen, are no longer required by any object, and it's safe to remove them from memory.
OnUnload
Once the destruction phase has been completed for a "no-longer-current" game state, the final phase comes in: releasing all the memory occupied by content assets. This is automatically handled by the engine, you don't have to do anything during this phase.
You can override this method to get know exactly when the contents are being unloaded, for instance if your doing profiling of your game. Tyically, you'll just leave this method empty in all your gamestates:
/// <summary>
/// Unloading
/// </summary>
protected override void OnUnload()
{
}