The State of Things
Using state to create game menus, display game screens and make your game characters come alive!
An Introduction: Just what in the world is state?
Game state can be used to mean a variety of things and you'll find it used in many different ways in the game industry. In this case I'm using "game state" to provide a description of an object at a given point in time in a game. Clear as mud to you now? Don't worry, I'll try to describe it in a different way, one as a geek like me you might identify with a little better.
Comic books. When buying used comic books, they often say what "state" or condition the comic book is in. The current state of the comic book gives you some indication of how the comic book is going to look when you purchase it. Mint, Fine and Poor all give an image in your mind of just how battered or worn the comic book is.
The same is true for objects in your game. By defining the various valid states for your objects, your game will know just what to do when it is in that state. And just like the comic book industry has defined the various valid states for comic books, you as the governing board for your game must define valid states for your objects and just exactly how the game should respond when those objects are in any given state.
That's great...but what would I actually use "state" for in my game?
The real question is what wouldn't you use it for! Honestly, state is going to be used by just about every single object you can think of in your game. You'll use it to solve just about every single game problem. Take the following common Creator's forum questions for example.
- How do I make a menu?
- How do I create multiple screens in my game?
- How do I re-start a level?
- How do I pause a game?
- How do I make it so the bad guys die?
- How do I make is so my character flashes, runs, walks, sleeps?
All of these questions have the exact same answer. You use state!
Ok, so I know what it is and I think I know what I might use it for, so HOW do I use it?
That would be an important thing to learn right? Well, the truth is, there are many different ways you can use state in code and even more ways to implement it. State can be managed with a simple boolean, you might create large switch statements or maybe even take advantage of polymorphism! You can really make it as simple or complicated as you like. The important thing is to learn what it is and just how you might use it in your game.
Displaying Multiple Screens in a Game - let's see some code!
We're going to be looking at three different examples. Each sample solves the same problem, but implements a solution in a different way..and each sample solves the problem by using the concept of state. In the samples, we're going to be looking at the basics of how to display different screens. We will be displaying a "Controller Detect Screen" and a "Title Screen". These are two very common screens that need to be shown in many games. Understanding how to display different screens in your game is one of the first hurdles game coders encounter. It's also a problem that can be solved fairly easily with a basic understanding of the concept of state. So with that being said, let's start looking at some code.
1. Example 1 - Using boolean variables to indicate and track screen state
In this sample, we're going to show one of these easiest and simplest ways to implement state in your game to manage screens (this is often referred to as screen state). Using nothing more complicated than booleans, we'll be able to decided which screen should currently be displayed.
Creating the new game project:
First, let's start by opening XNA Game Studio Express and creating a new Windows Game project. If you are having trouble remembering how to create a new XNA Windows Game project just follow the steps in this tutorial here and come back to the this tutorial after you've refreshed your memory.
Adding the images:
Now, let's add the images that we are going to be using in this tutorial to the game project. You can download the images used in the project from here. If you are having trouble remembering how to add images to an XNA Game project, just follow the steps in this tutorial here and come back to this tutorial after you've refreshed your memory.
Setup the game to run at an ideal resolution for the 360:
The images created for this tutorial were designed at 1280x720. This is the ideal resolution for games on the XBox 360. We'll start by adding some code to the main game class (Game1.cs) in the constructor to change the height and width of our game to run at that resolution.
Add the following code to the Game constructor in the Game class in the Game1.cs file
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
//Initialize screen size to an ideal resolution for the XBox 360
this.graphics.PreferredBackBufferWidth = 1280;
this.graphics.PreferredBackBufferHeight = 720;
}
Setting the PreferredBackBufferWidth and the PreferredBackBufferHeight of the graphics device manager object will attempt to run your game at the indicated resolution. Using 1280x720 for your PC games is a good idea so that you can know how they might look when you finally move them over to the XBox 360.
Adding the variables to track state and the player one index:
Now let's setup our objects to store the background textures for each screen. We're also going to need some variables to track the current screen state, we're going to create a couple of boolean objects to track what screen is the current screen. And then finally we're going to store the PlayerIndex of the controller Player One is using.
Add the following class level objects to the Game class in the Game1.cs file.
//Background textures for the various screens in the game
Texture2D mControllerDetectScreenBackground;
Texture2D mTitleScreenBackground;
//Screen State variables to indicate what is the current screen
bool mIsControllerDetectScreenShown;
bool mIsTitleScreenShown;
//The index of the Player One controller
PlayerIndex mPlayerOne;
Now that we we have our backgrounds objects and state variable, we need to load and initialize them. We'll do that in the LoadContent method.
Load the Content and initialize the state variable:
Add the following code to the LoadContent method in the Game class in the Game1.cs file.
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//Load the screen backgrounds
mControllerDetectScreenBackground = Content.Load<Texture2D>("ControllerDetectScreen");
mTitleScreenBackground = Content.Load<Texture2D>("TitleScreen");
//Initialize the screen state variables
mIsTitleScreenShown = false;
mIsControllerDetectScreenShown = true;
}
We start by loading the background images for our two screens. Then we initialize our screen state variables. We indicate that the Title screen is not currently shown and that the Controller Detect screen will be our starting screen when the game runs (mIsControllerDetectScreenShown = true).
Coding the Update method:
Now that we have our state variables we can use them in the Update method to make sure we're updating the correct screen based on the current screen state for our game.
Add the following code to the Update method in the Game class in the Game1.cs file.
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//Based on the screen state variables, call the
//Update method associated with the current screen
if (mIsControllerDetectScreenShown)
{
UpdateControllerDetectScreen();
}
else if (mIsTitleScreenShown)
{
UpdateTitleScreen();
}
base.Update(gameTime);
}
You can see that we check our screen state variables to see what screen is the current screen and then we call the Update method associated with that screen (we haven't written those methods yet, that's coming next!). As you add more screens, you would just add more code to check to see which screen is currently shown (as well as more boolean state variables at the class level).
Let's go write the specific Update methods for each screen now.
Coding the UpdateControllerDetectScreen method:
The Controller Detect screen is that first screen you see in just about every XBox Live Arcade game (and XBox Live Community Game) with that "Press A to begin" text flashing. This screen is there to detect which controller the player is going to play the game with. A gamer might come into a room to play a game and just pick up any controller laying around to start a game, we as game developers can't assume it will always be controller one. In fact, often gamers leave their rockband guitar plugged in as controller one so they always play games using controller two. That's the purpose of the Controller Detect screen in games. It's there so we know what control the gamer is attempting to use as controller one.
The code in the UpdateControllerDetectScreen was written to poll all the gamepads and wait for the games to press the A button. It then records what playerindex they were using (what controller they had in their hands) and then switches the screen state variables to indicate the Controller Detect screen is finished and the title screen should be displayed.
Add the following method to the Game class in the Game1.cs file.
private void UpdateControllerDetectScreen()
{
//Poll all the gamepads (and the keyboard) to check to see
//which controller will be the player one controller
for (int aPlayer = 0; aPlayer < 4; aPlayer++)
{
if (GamePad.GetState((PlayerIndex)aPlayer).Buttons.A == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.A) == true)
{
mPlayerOne = (PlayerIndex)aPlayer;
mIsTitleScreenShown = true;
mIsControllerDetectScreenShown = false;
return;
}
}
}
You can see that the code cycles through all four controllers and checks to see if the A button has been pressed. Simple enough (but scarily enough it's one of the most commonly failed item for XBox Live Community Games during peer review!)
Now that we have the Controller Detect screens Update method coded, let's code something for the Title screen's update method.
Coding the UpdateTitleScreen method:
Typically the Title screen would have a menu on it and the user would scroll through menu items and pick their next action (play game, options, return to dashboard, etc.). However for this tutorial, the only update logic our Title screen is going to have is to detect if the user wishes to move back to the Controller Detect screen. The typical "back" movement in XBox Live Arcade games is the "B" button so that's what we'll check for. When the controlling controller (which we discovered in our Controller Detect screen!) presses the "B" button, we'll switch our screen state variables so that the Controller Detect screen will become the current screen.
Add the following method to the Game class in the Game1.cs file.
private void UpdateTitleScreen()
{
//Move back to the Controller detect screen if the player moves
//back (using B) from the Title screen (this is typical game behavior
//and is used to switch to a new player one controller)
if (GamePad.GetState(mPlayerOne).Buttons.B == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.B) == true)
{
mIsTitleScreenShown = false;
mIsControllerDetectScreenShown = true;
return;
}
}
You can see we use the "mPlayerOne" variable when checking the GamePad's current state. This holds the index of the player one controller we stored in our Controller Detect Screen. You can also see how we switch the state back to the Title screen when Player one presses "B".
Now that we have the logic coded for updating our various screens, it's time we write some code to draw the various screens based on the current screen state.
Coding the Draw method:
Similar to the Update method, the Draw method checks the screen state variables and then calls the appropriate draw method for each screen based on the screen state.
Add the following code to the Draw method in the Game class in the Game1.cs file.
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
//Based on the screen state variables, call the
//Draw method associated with the current screen
if (mIsControllerDetectScreenShown)
{
DrawControllerDetectScreen();
}
else if (mIsTitleScreenShown)
{
DrawTitleScreen();
}
spriteBatch.End();
base.Draw(gameTime);
}
Nothing too complicated there and it looks extremely similar to the Update method logic we wrote earlier. To add new screens to be drawn you would just add more "else if" blocks and call the appropriate new Draw methods you would create.
Now let's write the specific Draw methods for each screen.
Coding the DrawControllerDetectScreen method:
Our screens are very simple and currently just have a background image. If you have more items such as sprites or fonts to be drawn to the screen, you would just add more code to the method.
Add the following method to the Game class in the Game1.cs file.
private void DrawControllerDetectScreen()
{
//Draw all of the elements that are part of the Controller detect screen
spriteBatch.Draw(mControllerDetectScreenBackground, Vector2.Zero, Color.White);
}
Coding the DrawTitleScreen method:
Another very simple screen. We're just drawing a background image for the Title screen. But to expand on the tutorial if you were to add Menu items that needed to be drawn you would add them here. All the draw code for the Title screen would be handled in the specific Draw method for that screen.
private void DrawTitleScreen()
{ //Draw all of the elements that are part of the Title screen
spriteBatch.Draw(mTitleScreenBackground, Vector2.Zero, Color.White);
}
Run the Game
That's it! You've code all the logic you need to manage multiple screens in your game. Go ahead and click the run button to see the final result. When the game runs you should be greeted by the controller detect screen. If you're on a PC, hit the "A" key to move to the Title screen, with a gamepad, hit the "A" button. You should now see the Title screen. From here you can hit the "B" key on PC or the "B" button on the gamepad to move back to the Controller Detect screen.
So how was that? Do you get the concept of state? Do you see how you could expand upon this tutorial and add in more screens? Ready to see another approach to managing screen state? Well then what are you waiting for! Let's get started on the next example of screen state.
2. Example 2 - Using enumerations to indicate and track screen state
Now that we've shown a simple sample of tracking state using booleans, let's increase the difficulty and the sophistication of our solution a bit. We'll tackle the same problem, but instead of booleans, we'll create an enumeration of our various screen states and use a variable to indicate the current screen to be displayed. This technique can be used quite effectively for game and it's a technique I use often in most of my smaller games.
The first steps for this example are the same as example 1. Just look above if you forget.
Creating the new game project:
Adding the images:
Setup the game to run at an ideal resolution for the 360:
Now that you've got that done. Let's start in on some of the differences in how we're going to manage screen state.
Adding the enumeration for screen state, creating a variable to track the current state and the player one index:
We're still going to need our background images for our screens and we're still going to need to track the Player one PlayerIndex, but we're going to add some new object to track our screen state.
Add the following class level objects to the Game class in the Game1.cs file.
//Background textures for the various screens in the game
Texture2D mControllerDetectScreenBackground;
Texture2D mTitleScreenBackground;
//The enumeration of the various screen states available in the game
enum ScreenState
{
ControllerDetect,
Title
}
//The current screen state
ScreenState mCurrentScreen;
//The index of the Player One controller
PlayerIndex mPlayerOne;
You'll notice we added a enumeration for ScreenState. In it, we have our various screens that are currently available in our game. Then we create a new variable to track the current screen and make it of our enumeration type. We'll be able to query this variable in code to check and see which of our available screen states our game is currently in.
Now let's load our content an initialize our screen state. (just like we did in the first example, well almost the same...)
Loading the Content:
Add the following code to the LoadContent method in the Game class in the Game1.cs file.
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// TODO: use this.Content to load your game content here
//Load the screen backgrounds
mControllerDetectScreenBackground = Content.Load<Texture2D>("ControllerDetectScreen");
mTitleScreenBackground = Content.Load<Texture2D>("TitleScreen");
//Initialize the current screen state to the screen we want to display first
mCurrentScreen = ScreenState.ControllerDetect;
}
The background images are being loaded exactly the same, but instead of the booleans we were setting in the first tutorial example to initialize our screen state, now we just have a single line of code setting our Current screen to the ControllerDetect screen. We have now initialize our screen state! Let's write the Update logic now to work with that screen state.
Coding the Update method:
The logic is very similar to the first tutorial example. Only this time instead of a big "if...then...else if" block, we have a big "switch" block that uses our Current screen variable. Based on the value of mCurrentScreen, we call the appropriate Update method for each screen. To add more screens, you would simply add more values to the enumeration, check them in the Update method here and then add a new specific Update method for that screen. Simple huh?
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//Update method associated with the current screen
switch (mCurrentScreen)
{
case ScreenState.ControllerDetect:
{
UpdateControllerDetectScreen();
break;
}
case ScreenState.Title:
{
UpdateTitleScreen();
break;
}
}
base.Update(gameTime);
}
Let's write those specific Update screen methods now. They're very similar to the ones you wrote in the first tutorial sample, but with just one simple little change.
Coding the UpdateControllerDetectScreen method:
Add the following method to the Game class in the Game1.cs file.
private void UpdateControllerDetectScreen()
{
//Poll all the gamepads (and the keyboard) to check to see
//which controller will be the player one controller
for (int aPlayer = 0; aPlayer < 4; aPlayer++)
{
if (GamePad.GetState((PlayerIndex)aPlayer).Buttons.A == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.A) == true)
{
mPlayerOne = (PlayerIndex)aPlayer;
mCurrentScreen = ScreenState.Title;
return;
}
}
}
At first glance the method looks exactly the same, but you'll see that instead of the booleans we were using to track state, we now have a single line we were change our CurrentScreen state variable to the Title screen state value.
Coding the UpdateTitleScreen method:
Add the following method to the Game class in the Game1.cs file.
private void UpdateTitleScreen()
{
//Move back to the Controller detect screen if the player moves
//back (using B) from the Title screen (this is typical game behavior
//and is used to switch to a new player one controller)
if (GamePad.GetState(mPlayerOne).Buttons.B == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.B) == true)
{
mCurrentScreen = ScreenState.ControllerDetect;
return;
}
}
Again, very similar to the method you wrote in the first tutorial sample, but again we just have a single line of code where we change the CurrentScreen state variable.
Now that our update logic is written using our CurrentScreen state variable, let's go ahead and write our Draw methods.
Coding the Draw method:
Again, instead of a big "if...then...else if" block we use a "switch" block to check the CurrentScreen state and then call the appropriate Draw method for that screen.
Add the following code to the Draw method in the Game class in the Game1.cs file.
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
spriteBatch.Begin();
//Call the Draw method associated with the current screen
switch (mCurrentScreen)
{
case ScreenState.ControllerDetect:
{
DrawControllerDetectScreen();
break;
}
case ScreenState.Title:
{
DrawTitleScreen();
break;
}
}
spriteBatch.End();
base.Draw(gameTime);
}
You'll see that similar to the code we wrote in the Update method, we query the CurrentScreen variable and then based on it's value call the corresponding Draw method for that screen state.
Now let's write the specific Draw methods for each screen state we have. The code for them is exactly the same as in the tutorial above.
Coding the DrawControllerDetectScreen method:
Add the following method to the Game class in the Game1.cs file.
private void DrawControllerDetectScreen()
{
//Draw all of the elements that are part of the Controller detect screen
spriteBatch.Draw(mControllerDetectScreenBackground, Vector2.Zero, Color.White);
}
Coding the DrawTitleScreen method:
Add the following method to the Game class in the Game1.cs file.
private void DrawTitleScreen()
{ //Draw all of the elements that are part of the Title screen
spriteBatch.Draw(mTitleScreenBackground, Vector2.Zero, Color.White);
}
Run the Game
That's it! You've managed to solve the same problem you solved in the first tutorial sample, just in a slightly different way. When you run the game you should be once again greeted by the Controller Detect screen and with the simple press of the "A" button (or key) you should see the Title screen. Can you see how this method might a little simpler to manage than the first? Adding screen is just a little easier and not having to keep track of all those individual booleans to know what screen is the current screen makes your code just a little more readable. They both solve the problem, but one might be a better solution for your skill level or your game.
Ready to give it another go? This final tutorial sample is going to take a bit deeper down the rabbit hole. We've covered some of the more entry level methods of managing screen state. With this next sample you're going to be introduced to classes, objects, polymorphism and events! Scared? You shouldn't be, any words you get stuck on just do a quick search online and read about it. It's one of the best ways to learn.
So hang onto your hat and let's look at yet another method of managing screen state!
3. Example 3 - Using polymorphism to make managing screen state just a little easier
You've seen screen state managed two different ways already, now let's get all object oriented on the issue and introduce the concept of classes and take advantage of polymorphism. This is a much more advanced way of solving the same problem, but "advanced" doesn't necessarily mean it's a better solution. It's just always good to know and understand various ways of solving problems. The more tricks you have in your hat as a coder, the better off you and your games will be!
We'll start this example off the same way as the others. The first few steps are exactly the same as in the first sample. If you're not sure exactly how to do one of the steps below, just look at the sample above to jump start your memory.
Creating the new game project:
Adding the images:
Setup the game to run at an ideal resolution for the 360:
With that out of the way, lets start creating some of the base objects we're going to be using in our game. Since we're working with "screens" in our game, we're going to create a Screen class to give us a nice object to work with in code.
Create the Screen class
Add a new class to your game. (you do this by right-clicking on the project name in the Solution Explorer then selecting "Add" and then "Class"). Name the class "Screen". Once you've added the Screen class to your game, we need to start adding some code to the class.
Start by adding the following "using" statements to the top of the Screen class.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
Those lines will help you when coding with the XNA framework so that you don't always have to fully quality of the XNA objects like PlayerIndex and SpriteBatch. Without them you'd have to type something like Microsoft.Xna.Framework.Graphics.SpriteBatch every time you'd want to create a new SpriteBatch object. That's a lot of typing and less typing is a good thing sometimes!
With that added, let's start adding some functionality to the screen class. First, we're going to want a way to track the PlayerOne controller index. And we're going to want that tracking object to be available to all screens so they can use it when necessary. We'll do that by creating a "static" PlayerIndex object in our Screen class. The "static" keyword means that every instance of our screen classes will have the same value, so changing it in one changes it for all screens.
Add the following line of code to the Screen class.
//Stores the PlayerIndex for the controlling player, i.e. Player One
protected static PlayerIndex PlayerOne;
Next, we're going to want a way for our screen to notify the main game class that something has happened. We'll do this by having an event handler who's value will be filled in when the Screen class is created.
Add the following code to the Screen class.
//The event associated with the Screen. This event is used to raise events
//back in the main game class to notify the game that something has changed
//or needs to be changed
protected EventHandler ScreenEvent;
public Screen(EventHandler theScreenEvent)
{
ScreenEvent = theScreenEvent;
}
You can see that an EventHandler object is passed into the Screen constructor. We then store that value in our ScreenEvent object in our class. With the constructor finished, lets add our remaining two methods to the class.
All screens are going to have Update logic and Draw logic so we'll add "virtual" methods to the screen class that subclasses can override and add any specific Update and Draw code they have. By making these methods part of our base Screen class, we'll be able to take advantage of a concept called "polymorphism" (great subject to go read up on if you're unfamiliar with the term!).
Add the following virtual methods to the Screen class.
//Update any information specific to the screen
public virtual void Update(GameTime theTime)
{
}
//Draw any objects specific to the screen
public virtual void Draw(SpriteBatch theBatch)
{
}
And that's it for our screen class. Not much to see right now, but we're not quite done yet! Now that we have our base screen class let's start making some of our specific screen objects.
Create the Controller Detect Screen Class
We want to have a controller detect screen in our game, so let's go ahead and create a new class called ControllerDetectScreen. Once you have that new class added to your game project, add the following "using" statements to the top of the class.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
Next we're going to want to indicate that our ControllerDetectScreen is inheriting from our base Screen class so you'll need to modify your class line to look like the following.
class ControllerDetectScreen : Screen
Constructor
With our code setup to indicate that we are inheriting from the Screen class, let's start adding some code specific to our ControllerDetectScreen. We'll start by adding our background texture and then writing the code for our constructor to load the texture (and pass on the event handler to the base class).
Adding the following code to the ControllerDetectScreen class.
//Background texture for the screen
Texture2D mControllerDetectScreenBackground;public ControllerDetectScreen(ContentManager theContent, EventHandler theScreenEvent): base(theScreenEvent){//Load the background texture for the screen
mControllerDetectScreenBackground = theContent.Load<Texture2D>("ControllerDetectScreen");
}
Update
The Update logic for the ControllerDetectScreen class is going to be eerily familiar to the update methods we wrote in the previous two samples. The logic remains the same, the only real difference is that instead of changing to a new screen state we're invoke our ScreenEvent and notifying the main game class that something happened in the ControllerDetectScreen.
Add the following Update method to the ControllerDetectScreen class.
//Update all of the elements that need updating in the Controller Detect Screen
public override void Update(GameTime theTime){//Poll all the gamepads (and the keyboard) to check to see
//which controller will be the player one controller. When the controlling
//controller is detected, call the screen event associated with this screen
for (int aPlayer = 0; aPlayer < 4; aPlayer++){if (GamePad.GetState((PlayerIndex)aPlayer).Buttons.A == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.A) == true){PlayerOne = (PlayerIndex)aPlayer;ScreenEvent.Invoke(this, new EventArgs());return;
}}base.Update(theTime);
}
Draw
Next up is our Draw method. Again, this is going to look very similar to the draw methods you wrote for the controller detect screen in the previous two samples.
Add the following method to the ControllerDetectScreen class.
//Draw all of the elements that make up the Controller Detect Screen
public override void Draw(SpriteBatch theBatch)
{
theBatch.Draw(mControllerDetectScreenBackground, Vector2.Zero, Color.White);
base.Draw(theBatch);
}
And now we're finished with our ControllerDetectScreen class, on to the Title screen!
Create the Title Screen Class
Add a new class to your game and call it TitleScreen. Once you have that created, add the following "using" statements to the top of the class.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
We want our title screen to also have the behaviors and properties of a screen so we need it to inherit from the base Screen class as well. Modify your class line to look like the following.
class ControllerDetectScreen : Screen
Constructor
The constructor for the TitleScreen class is basically the same as the constructor you wrote for the ControllerDetectScreen. The only difference is that you're loading the TitleScreen background image.
Add the following code to the TitleScreen class.
//Background texture for the Title screen
Texture2D mTitleScreenBackground;public TitleScreen(ContentManager theContent, EventHandler theScreenEvent): base(theScreenEvent){//Load the background texture for the screen
mTitleScreenBackground = theContent.Load<Texture2D>("TitleScreen");
}
Update
Our TitleScreen also needs an Update (remember we want to move back to the ControllerDetectScreen when B is pressed!). So we will need to add in the Update method and it's logic.
Add the following method to the TitleScreen class.
//Update all of the elements that need updating in the Title Screen
public override void Update(GameTime theTime){//Check to see if the Player one controller has pressed the "B" button, if so, then
//call the screen event associated with this screen
if (GamePad.GetState(PlayerOne).Buttons.B == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.B) == true){ScreenEvent.Invoke(this, new EventArgs());}base.Update(theTime);
}
Notice that when the current gamepad state is queried we're using the PlayerOne object as the player index we pass into the method. This means that whatever controller the player pressed "A" with on the ControllerDetectScreen will be stored in that value. So if the player started the game with controller three, the controller three is what will be checked to see if the user pressed "B" to move back.
Draw
And now we add our Draw method to visually display all the elements that make up our title screen. In this sample this doesn't consist of much, just a simple background image, but you could fill this method with everything that needs to be drawn in your own title screen.
Add the following method to the Title screen class.
//Draw all of the elements that make up the Title Screen
public override void Draw(SpriteBatch theBatch)
{
theBatch.Draw(mTitleScreenBackground, Vector2.Zero, Color.White);
base.Draw(theBatch);
}
So now we've created our base screen class (Screen) and our two screens that inherit from it (ControllerDetectScreen and TitleScreen). But really, we still don't have anything to show for our effort. It's time to add some code to our main Game class to finally hook this all up.
Add the screens to the main Game class
We'll start by adding some variables to the top our our Game class. We'll create one object for each specific screen type (ControllerDetectScreen and TitleScreen) and then we'll create an object called mCurrentScreen that is just our base Screen type. You'll see how we'll be using that in just a minute (remember polymorphism!)
//The screens and the current screen
ControllerDetectScreen mControllerScreen;
TitleScreen mTitleScreen;
Screen mCurrentScreen;
Loading the Content
We created the screen objects, but now it's time to load them. We'll do that in the LoadContent method. We'll be creating new instances of our ControllerDetectScreen and TitleScreen objects and we'll be passing in some specific events for each screen type (we haven't created the events yet, but we'll pass in the names we're planning on using).
Add the following code to the LoadContent method in the Game class in the Game1.cs file.
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//Initialize the various screens in the game
mControllerScreen = new ControllerDetectScreen(this.Content, new EventHandler(ControllerDetectScreenEvent));
mTitleScreen = new TitleScreen(this.Content, new EventHandler(TitleScreenEvent));
//Set the current screen
mCurrentScreen = mControllerScreen;
}
You'll notice that we set the CurrentScreen equal to the ControllerScreen. This is possible even though the Controller screen is of the ControllerDetectScreen type because it inherits form Screen. The only restriction when using this to your advantage is that you can't access any specific properties or method that re only in the ControllerDetectScreen class, you only have the base Screen methods and properties available to you. Pretty neat trick huh? It gets even better in just a minute, but first we need to create those screen events!
Controller Detect Screen Event
Add the following method to the Game class in the Game1.cs file.
//This event fires when the Controller detect screen is returning control back to the main game class
public void ControllerDetectScreenEvent(object obj, EventArgs e)
{
//Switch to the title screen, the Controller detect screen is finished being displayed
mCurrentScreen = mTitleScreen;
}
You saw in the ControllerDetectScreen class we wrote that we were "invoking" a screen event method and then when we created our ControllerDetect screen object just a minute ago we passed in a new EventHandler with this method named. This means that when the ControllerDetectScreen invokes the ScreenEvent method, this method will be fired. You can then put any logic you want in the method. In our case, I'm switching the current screen variable to the TitleScreen indicating that our screen state has changed to a new screen.
Title Screen Event
Now we need to write a similar method for the Title screen event. Add the following method to the Game class in the Game1.cs file.
//This event is fired when the Title screen is returning control back to the main game class
public void TitleScreenEvent(object obj, EventArgs e)
{
//Switch to the controller detect screen, the Title screen is finished being displayed
mCurrentScreen = mControllerScreen;
}
With the methods for our ScreenEvents now coded, we can add our logic to the Update method.
Update
In our Update method, we basically just want to make sure that the Update method is being called for the current screen. And to do that, all you have to do is add a single line of code! Since the base screen class has an Update method, it's available to be called. And since the subclasses (our TitleScreen and ControllerDetectScreen) overrode that method, that special code in each class is what will be run all thanks to wonderful POLYMORPHISM! How great is that!
So add the following single line of code to the Update method in the Game class in the Game1.cs file.
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//By taking advantage of Polymorphism, we can call update on the current screen class,
//but the Update in the subclass is the one that will be executed.
mCurrentScreen.Update(gameTime);
base.Update(gameTime);
}
Draw
Can you guess what you need to do in the Draw method to make the current screen draw? Well, it's more than one line of code because we have to Begin and End our spriteBatch, but it IS just one line of code to draw the current screen. Thank your object oriented programming!
Add the following lines of code to the Draw method in the Game1 class in the Game1.cs file.
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
//Again, using Polymorphism, we can call draw on teh current screen class
//and the Draw in the subclass is the one that will be executed.
mCurrentScreen.Draw(spriteBatch);
spriteBatch.End();
base.Draw(gameTime);
}
And that's it, we're all done with the sample! Go ahead and run it and watch it work. Again, no new functionality but we solved our screen state issue with yet another solution. Think you could figure out how to add another screen class and make it work? Maybe make it so you can switch to it from the Title screen when you hit the "A" button?
4. The Professional approach - taking game state just a little further...
So you think you've got state management nailed and you're wondering just where to go from here? Well it sounds like you might be ready to tackle the Game State management sample found over at the Creator's site. The Game State Management sample was created by some very big brains at Microsoft and will demonstrate a very professional way of managing your game state. The Game State management sample has been the base code for many of the current games you can find on Community Games. It's very well done, but can be difficult to digest if you're new to development and the concepts of game state. Definitely worth looking at if you are having difficulty managing your screen state using any of the other three methods I introduced above.
In Conclusion
Hopefully after walking through all of those samples, you've gotten just a little taste of what "state" is and just how you might use it. In this tutorial, we only closely looked at screen state specifically, but the concept would be the same whether you were looking at menu state (an enumeration of all available menu items, a currently selected menu item variable and then switch logic in the update and draw methods) or character state (an enumeration of characters states such as alive, powered up, dead, well, you get the point now right?). Now armed with the powerful concept of state, you should be ready to take your game development to the next level...literally!