Posted by FIZIX Richard on Fri 17th August 2012 1:43pm

In this tutorial we will be looking at creating a game state manager in Unity 3D. For this tutorial we will be using the C# programming language, however the methods can be translated into JavaScript for Unity and Boo.



A game state manager is a persistent class, meaning that it is created and it's state will persist throughout the game session, even if you switch scenes. The state manager will hold game data that has to be maintained throughout the game session.



The kind of data that you store in a state manager is broad and very much game specific; it could be things such as your characters attributes such as health or stats in an RPG, the players accumulated points, current level progress or any other manner of things.



The state manager will also provide methods to interact with this data, usually this will at least include getter and setter methods such as increasing or decreasing health.



It may also contain other methods that are required globally throughout the game. Following standards however, these methods should be limited to those that manage game state.





In this tutorial we will be creating a state manager that maintains an RPG characters attributes, such as their name, HP, MP, strength, vitality, dexterity etc. We will also be creating a very simple UI to start the state manager (setting default attributes) and some buttons that will interface with the state manager. This tutorial can be used as a foundation to creating any kind of state manager, not just for RPG characters.



As this is purely a scripting tutorial we will not be concerning ourselves with anything relating to the UI/UX or artwork assets; just the code to create a state manager. We will be creating three scenes and three scripts; the only script that's actually important is the gamestate.cs script; the others two scripts and the scenes exist purely so that we can test the state manager and see it working.





This tutorial assumes a basic understanding of the Unity 3D interface and creating scripts.



While the end code isn't huge, this is a slightly lengthy tutorial as we explain what state managers are, how they are used and are reasonably thorough with our explanations of the code. For those who are more experienced, there are pastebin entries at the end of the post which lead to the full script sources.







Data Specification



Before we begin, it is important to know what data will be stored in our state manager and what methods will be included for managing state data.



Data

The data we will be storing is:



- Active Level (the current level)

- Name (the characters name)

- Max HP (the characters max health)

- Max MP (the characters max mana)

- Current HP (the characters current hp)

- Current MP (the characters current mp)

- Strength (the characters strength)

- Vitality (the characters vitality)

- Dexterity (the characters dexterity)

- Experience Points (the characters accumulated experience points)





Properties

From the data spec above, we will be creating and maintaining the following properties (variables):

activeLevel: string

name: string

maxHP: int

maxMP: int

hp: int

mp: int

str: int

vit: int

dex: int

exp: int





Methods

We will also need some methods (functions) so that we can manage the state information above.



Within the state manager itself, we will have methods to:



- Create the state manager instance

- To flush the instance on game exit

- To set the default data

- A method to set the active scene

- A method to return the active scene

- A method to return the characters name

- A method to return the characters HP

- A method to return the characters MP











Creating the Project



OK with that out of the way, we need to create our game project and lay out our scenes and scripts; when that's done we can get onto scripting.



1. Create a new project (you don't need to include any asset packs)

2. Create (and save so they appear in the project folder) 3 scenes: "gamestart", "level1" and "level2"

3. Create 3 C# scripts: "gamestart", "gamestate" and "levelgui"



With that done, load up each scene and create a new empty game object called "gui".





The 3 scenes have a simple purpose; the gamestart screen is obviously your start screen, which will lead to the level1 scene; the two level scenes are simply there to act as spaces in the game that we can switch between to test that the state data is being maintained between scene loads.



The gamestart script provides functionality for the start screen, the gamestate script will hold our state manager and the levelgui script will provide the GUI on the two level scenes.









Scripting the start screen



We will begin by scripting the start screen, so open the "gamestart" scene and drag the "gamestart" script from the project folder onto the "gui" object in the hierarchy so that the script is applied to the object. Then open up the gamestart.cs script in your script editor.



We don't need anything complicated here, just a "start game" button that will start up the state manager and lead us to the "level1" scene. In practice; depending on the game you are producing, you would do any number of things here, such as loading a saved game file and setting the state manager appropriately before transitioning to the next scene. For us, we will just be instantiating the state manager and setting the default attributes so that so that the state manager contains some data.



Your gamestart script will have a start() and an update() function, they aren't technically needed in this tutorial so you can chop them out if you want. What we will need however is a GUI, so we will create a OnGUI method; which is where our startscreen GUI code will exist.





Your script will look like this:



Code:

using UnityEngine; using System.Collections; public class gamestart : MonoBehaviour { // Our Startscreen GUI void OnGUI () { } }







We will then need to render a button on the screen which will call the method to start the game up when clicked. To do this, add the following block of code to the OnGUI method:





Code:

if(GUI.Button(new Rect (30, 30, 150, 30), "Start Game")) { startGame(); }







The if statement detects the button press, the GUI.BUTTON creates the button itself, the Rect() positions the button inside a holder (the first two parameters are the x,y position of the button, the second two are the width and height of the button, the final parameter is the buttons label).



Inside the if statement is a call to the "startGame" method, which will be triggered when the button is pressed, so now we need to create this method below the OnGUI method:





Code:

private void startGame() { print("Starting game"); DontDestroyOnLoad(gamestate.Instance); gamestate.Instance.startState(); }







The line below the print statement, which reads "DontDestroyOnLoad(gamestate.Instance);" is telling Unity not to destroy our gamestate instance when we load a new scene, so that it will persist between scene transitions.





The next line is calling a method in our state manager to start a new game state (of course the state manager doesn't exist yet so if you want to test your script you'll need to comment this and the previous lines out).



The code is basically saying "scriptname->Instance->method_to_call"; basically it's saying to call the startState() method in the active instance of gamestate the object.



At this point you can run and test your game (provided you comment out the DontDestroyOnLoad() and startState() function call). When you click the Start Game button, you'll see "Starting game" appear in the console; what we need to do now is to create the state manager itself.









Scripting the state manager



With the start screen created, it's time to create our state manager, so open up the "gamestate" script in your script editor and let's begin.





Creating the basic state manager

First cut out the start() and update() methods as they aren't needed, then adapt the script so it looks like the code snippet below:





Code:

using UnityEngine; using System.Collections; public class gamestate : MonoBehaviour { // Declare properties private static gamestate instance; // --------------------------------------------------------------------------------------------------- // gamestate() // --------------------------------------------------------------------------------------------------- // Creates an instance of gamestate as a gameobject if an instance does not exist // --------------------------------------------------------------------------------------------------- public static gamestate Instance { get { if(instance == null) { instance = new GameObject("gamestate").AddComponent (); } return instance; } } // Sets the instance to null when the application quits public void OnApplicationQuit() { instance = null; } // --------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------- // startState() // --------------------------------------------------------------------------------------------------- // Creates a new game state // --------------------------------------------------------------------------------------------------- public void startState() { print ("Creating a new game state"); } }







First we have a property declaration for our gamestate instance, then we have two methods; one to create the gamestate instance and on to destroy it and then we have the method "startState()" which is what we are calling from the gamestart screen.



While the code should explain itself, I shall explain it briefly:



1. We are creating a static variable for the game state instance

2. We then have a static function that will create a gamestate instance as a GameObject if one does not exist (this prevents you creating multiple instances which you don't want).

3. There is a method to flush the instance when the game quits

4. We then have the startState method, which will set the default state properties.





If you test your game now, when you click the start game button, you'll see the gamestate instance appear in the hierarchy and "Creating a new game state" appear in the console. This is the bare bones of a state manager, next we need to add some data store and maintain; it is this data that will form the heart of your state manager (as a state manager is fundamentally used to store persistent data); which is managed by a series of methods within the state manager.





Creating the properties of the state manager

As a general rule of thumb, the properties (variables) you create in the state manager should be private and interfaced only by dedicated methods (functions) within the state manager. Failing to do this can lead to serious issues with larger games; such as tracking down a bug. If you use dedicated methods, amongst other things, you can output data to the console whenever a property is changed along with information about that change.



We already know our properties from our data spec, so we will simply declare them below gamestate instance declaration, directly above the static method:





Your properties should look like this:



Code:

// Declare properties private static gamestate instance; private string activeLevel; // Active level private string name; // Characters name private int maxHP; // Max HP private int maxMP; // Map MP private int hp; // Current HP private int mp; // Current MP private int str; // Characters Strength private int vit; // Characters Vitality private int dex; // Characters Dexterity private int exp; // Characters Experience Points





All of the properties are private, meaning they can only be accessed by methods within the state manager (if they were public, they could be accessed from outside of the state manager like so: gamestate.Instance.propertyname).



We have used integers for the attributes as in most RPG's these attributes will be whole numbers.







Setting default attributes

Now we need to set the default values to the properties we have just declared, we do this by extending the startState() method like so:





Code:

public void startState() { print ("Creating a new game state"); // Set default properties: activeLevel = "Level 1"; name = "My Character"; maxHP = 250; maxMP = 60; hp = maxHP; mp = maxMP; str = 6; vit = 5; dex = 7; exp = 0; // Load level 1 Application.LoadLevel("level1"); }





The startState() method now sets the default properties (setting hp and mp to the values of maxHP and maxMP) and then loads the "level1" scene.



Remember to add the three scenes to the games build settings









Creating the game levels



Now we need to create our two level scenes; so open up the level1 scene and drag the "levelgui" C# script onto the gui object in the hierarchy and save the scene, then repeat for the level2 scene.



We will then create the levelgui script, where we will demonstrate how to access data and methods in the state manager and how to adjust data within the state manager.





Creating the levelgui script

Open up the levelgui.cs script in your script editor and adjust the code so it looks like the snippet below:





Code:

using UnityEngine; using System.Collections; public class levelgui : MonoBehaviour { // Initialize level void Start () { print ("Loaded: " + gamestate.Instance.getLevel()); } // --------------------------------------------------------------------------------------------------- // OnGUI() // --------------------------------------------------------------------------------------------------- // Provides a GUI on level scenes // --------------------------------------------------------------------------------------------------- void OnGUI() { // Create buttons to move between level 1 and level 2 if (GUI.Button (new Rect (30, 30, 150, 30), "Load Level 1")) { gamestate.Instance.setLevel("Level 1"); Application.LoadLevel("level1"); } if (GUI.Button (new Rect (300, 30, 150, 30), "Load Level 2")) { print ("Moving to level 2"); gamestate.Instance.setLevel("Level 2"); Application.LoadLevel("level2"); } } }







We now have two methods in the levelgui.cs script:



1. The start() method which outputs the current level to the console; the level itself is being returned from the game state manager via a new method which we will add in a moment called "getLevel()".



2. The OnGUI metod which provides a GUI on the level scenes; the GUI gives us two buttons to move between the two level scenes and in the process updates the "activeLevel" property in the state manager.



At the moment the script will fail as there is no getLevel() or setLevel() methods in the state manager, so switch over to the gamestate.cs script and add the following two methods:





Code:

// --------------------------------------------------------------------------------------------------- // getLevel() // --------------------------------------------------------------------------------------------------- // Returns the currently active level // --------------------------------------------------------------------------------------------------- public string getLevel() { return activeLevel; } // --------------------------------------------------------------------------------------------------- // setLevel() // --------------------------------------------------------------------------------------------------- // Sets the currently active level to a new value // --------------------------------------------------------------------------------------------------- public void setLevel(string newLevel) { // Set activeLevel to newLevel activeLevel = newLevel; }







The getLevel() method simply returns the activeLevel property; so when we call getLevel() from any other scripts, the active level is returned.



The setLevel() script received a new level in the variable "newLevel" and sets the activeLevel property to whatever is in newLevel.



If you look back over at the levelgui.cs script you can see how you can access methods within the state manager; quite simply like so:





Code:

gamestate.Instance.methodToCall();





So just like any other function call, except with gamestate.Instance suffixed to it.









Adding Feedback



Let's make this a bit more intuitive now, by adding an output of the characters stats. So switch back over to the levelgui script and add in the following code to the OnGUI method (below the two buttons):





Code:

// Output stats GUI.Label(new Rect(30, 100, 400, 30), "Name: " + gamestate.Instance.getName()); GUI.Label(new Rect(30, 130, 400, 30), "HP: " + gamestate.Instance.getHP().ToString()); GUI.Label(new Rect(30, 160, 400, 30), "MP: " + gamestate.Instance.getMP().ToString());





The code above displays a level that contains the stat name followed by the required stat, which is returned from the state manager using a getter method for each. Note that we have converted the HP and MP to from integers to strings so that they can be outputted in the label.



Again, you will need to add the getter methods to the state manager:





Code:

// --------------------------------------------------------------------------------------------------- // getName() // --------------------------------------------------------------------------------------------------- // Returns the characters name // --------------------------------------------------------------------------------------------------- public string getName() { return name; } // --------------------------------------------------------------------------------------------------- // getHP() // --------------------------------------------------------------------------------------------------- // Returns the characters hp // --------------------------------------------------------------------------------------------------- public int getHP() { return hp; } // --------------------------------------------------------------------------------------------------- // getMP() // --------------------------------------------------------------------------------------------------- // Returns the characters mp // --------------------------------------------------------------------------------------------------- public int getMP() { return mp; }











Closing Comments



That's all there is too it; we've created a state manager, which is a singleton class, learned how to make it persistent between scene loads, populated it with some private variables and added functions to interface with these variables. We've also learned how to access the methods within the class.



Anything more than this is going to be game specific, but here are a few of the things you can do with the state manager:



1. A good state manager will contain only the data needed within the state machine

2. You should validate data and maintain it's integrity

3. You have hold data between scene loads

4. You can use this approach to create bootstrap systems

5. You can tie the state manager into a save/load game system, where the data is set from a save file on load; and the data dumped to a save file on save.

6. You can use state managers to maintain world states and other data; you could for example have state managers for several different components of your game; if your game requires it.

7. You can output debug data for bug tracking, fixing and data analysis





Ultimately, state managers are behind complex games and form an important foundation.





As ever, for good measure, grab the full code from our PasteBin:

gamestate.cs: http://pastebin.com/YPJTGLwX

gamestart.cs: http://pastebin.com/fgL4kMtq

levelgui.cs: http://pastebin.com/XBgCLmwF