Previous posts in this series:
In a typical game, when a user double-clicks the EXE file, the game does not start right away. At the very least, an introduction screen is presented with some menu offering to start the game, maybe change some options, view high scores, etc. At the very very least, the player needs to “press any key” or something similar to get into the action.
Furthermore, when a game is over, there is typically some ending screen with the total score, perhaps some ranking system, a high score list or whatever.
To do all that, we need to manage game states. A simple game might need three states: Introduction, Playing and Game over. Managing states is not difficult with XNA when working with game components, as we have. Each game component can be “turned on” or “turned off”, so only a subset of game components are active at any one time.
Let’s add an introduction component. Add a new GameComponent by selecting Add New Item from the project right click menu. Name it IntroComponent. As usual, change the base class from GameComponent to DrawableGameComponent, as we need to draw some introduction stuff.
Let’s add to that class a SpriteBatch, and two fonts, one big and one small (we’ll use the Stats font we added in a previous post for the small version). We’ll load the fonts as usual, and create the SpriteBatch as usual as well. Here’s the Draw method we create:
public override void Draw(GameTime gameTime) {
_batch.Begin();
_batch.DrawString(_font, "ALIEN RAID", new Vector2(150, 120), Color.Yellow);
_batch.DrawString(_smallFont, "Press ENTER to play", new Vector2(280, 250), Color.Cyan);
_batch.End();
base.Draw(gameTime);
}
Next, we’ll add a readonly field to the game class (as we did with other components) and add the component to the Components collection in the constructor:
Components.Add(IntroComponent = new IntroComponent(this));
Running the game now has a strange effect: the title is displayed but the game is playing…
What we want is to disable and hide the IntroComponent while the game is actually playing.
First, let’s define some game states. Add the following code before the declaration of the AlienRaidGame class:
public enum GameState {
Intro, InPlay, GameOver
}
We have the three basic states: introduction, playing and game over. To switch between states, we’ll add a property indicating the current state and create a helper method in the game class that switches on or off the appropriate components:
public GameState State { get; private set; }
public void GotoState(GameState state) {
switch(state) {
case GameState.Intro:
IntroComponent.Enabled = IntroComponent.Visible = true;
AliensComponent.Enabled = AliensComponent.Visible = false;
PlayerComponent.Enabled = PlayerComponent.Visible = false;
GameOverComponent.Enabled = GameOverComponent.Visible = false;
break;
case GameState.InPlay:
IntroComponent.Enabled = IntroComponent.Visible = false;
AliensComponent.Enabled = AliensComponent.Visible = true;
PlayerComponent.Enabled = PlayerComponent.Visible = true;
GameOverComponent.Enabled = GameOverComponent.Visible = false;
break;
case GameState.GameOver:
IntroComponent.Enabled = IntroComponent.Visible = false;
AliensComponent.Enabled = AliensComponent.Visible = false;
PlayerComponent.Enabled = PlayerComponent.Visible = false;
GameOverComponent.Enabled = GameOverComponent.Visible = true;
break;
}
State = state;
}
Pretty straightforward. The more compute-science inclined might say that we need to build a full fledged state machine with states and transitions and …
Forget it. It’s not worth the trouble, and a simple switch will do even if we need a few more states along the way.
The Enabled property of a GameComponent specifies whether Update should be called. The Visible property specifies whether DrawableGameComponent.Draw should be called. Usually, both will be set to the same value. Note that the starfield component is always visible and enabled (which is the default). I decided I want to see the starfield even in the introduction screen.
Now that we have this in place, we can start with the “Intro” state by calling GotoState in the game’s constructor:
GotoState(GameState.Intro);
Running the game now makes the intro visible (along with the starfield) but the game does not go any further. We need to make good on our promise and play the game when ENTER is pressed.
For this we’ll modify IntroComponent.Update to check for the appropriate key and transition to the new state when needed:
public override void Update(GameTime gameTime) {
var ks = Keyboard.GetState();
if(_previousKS.IsKeyDown(Keys.Enter) && ks.IsKeyUp(Keys.Enter))
AlienRaidGame.Current.GotoState(GameState.InPlay);
_previousKS = ks;
base.Update(gameTime);
}
The _previousKS is a KeyboardState field that keeps track of the previous state. This helps to wait when the ENTER key is released after being pressed. Then we transition to the InPlay state.
Similarly, we can add a GameOverComponent and switch to that state when the player is killed (when Lives reach zero). From there, we can override Update again, and wait for some key and return to the Intro state or even straight to the InPlay state. The attached code has the GameOver state implemented (the player has currently one “life”).
That the idea of states and state management. It’s pretty convenient given the Components model.
In the next (and final) part I’ll talk about some more features we can add, and discuss some philosophical issues related to game development and XNA.