In this section we add support for
functionality you would expect to see if this was a real Windows Phone 7
game. We first implement how to load and save game state. We modify the
main menu game screen to have both new game and resume game menu items
in the event that previous game state is present.
We next implement tombstoning support so that the
user is restored to the main menu screen and has a chance to click
resume to restart game play. The last section covers how to create a
basic particle system that simulates expositions when a hero ship
missile collides with an alien enemy spaceship.
1. Load and Save Game State
For our game, we automatically save game state when
the user backs out of the GameplayScreen with a game in progress. The
main menu screen is modified to include both New Game and Resume Game
menu options that will resume an existing game if saved state is
present. Automatic save keeps things simple and is a typical state
management approach for many game developers.
We set about modifying the MainMenuScreen class, renaming the playGameMenuEntry object to newGameMenuEntry and adding another MenuEntry named resumeGameMenuEntry. Here is the modified constructor MainMenuScreen():
public MainMenuScreen()
: base("Main Menu")
{
// Create our menu entries.
MenuEntry newGameMenuEntry = new MenuEntry("New Game");
MenuEntry optionsMenuEntry = new MenuEntry("Game Options");
// Hook up menu event handlers.
newGameMenuEntry.Selected += newGameMenuEntrySelected;
optionsMenuEntry.Selected += OptionsMenuEntrySelected;
// Add entries to the menu.
MenuEntries.Add(newGameMenuEntry);
MenuEntries.Add(optionsMenuEntry);
//robcamer - Only display resume game menu opion
//if saved state is present.
using (IsolatedStorageFile gameStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
if (gameStorage.FileExists(AlienShooterStateFileName))
{
MenuEntry resumeGameMenuEntry = new MenuEntry("Resume Game");
resumeGameMenuEntry.Selected += resumeGameMenuEntry_Selected;
MenuEntries.Add(resumeGameMenuEntry);
}
}
}
We want to display the "Resume Game" menu option only when there is game state present, so in the MainMenu there is a FileExists check to determine if game state is present. We add a using System.IO.IsolatedStorage statement to the top of MainMenuScreen.cs as well as a constant declaration of the state filename to wire things up:
const string AlienShooterStateFileName = "AlienShooter.dat";
We modify the new game selected event handler named newGameMenuEntrySelected to delete any saved game state since a new game is desired:
void newGameMenuEntrySelected(object sender, PlayerIndexEventArgs e)
{
//
using (IsolatedStorageFile gameStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
if (gameStorage.FileExists(AlienShooterStateFileName))
{
gameStorage.DeleteFile(AlienShooterStateFileName);
}
}
LoadingScreen.Load(ScreenManager, true, e.PlayerIndex,
new GameplayScreen());
}
This sets things up for the GameplayScreen to load saved state or start a new game depending on what option is chosen in the MainMenuScreen UI. In the GameplayScreen object we create two new public methods to load and save state. For load state, we do the following:
Check for saved state; if not present, initialize the variables as before.
If saved state is present, restore saved state and begin game play.
To save state, we use a System.IO.StreamWriter object. We don't use object serialization because we need to instantiate the GameObject classes normally, just like when LoadContent
is called. Instead, we simply save off the important state values to a
text file in Isolated Storage such as the Position and Velocity vector
values, the status board game score and remaining lives, and so on. The SaveAlienShooterState
method always deletes the file first before saving. Also, the method
does not save state if the game is over. Listing 1 shows the GameplayScreen. SaveAlienShooterState method.
Example 1. GameplayScreen. SaveAlienShooterState
public void SaveAlienShooterState()
{
//Only save game if not GameOver
using (IsolatedStorageFile gameStorage =
IsolatedStorageFile.GetUserStoreForApplication())
{
//Overwrite existing saved game state
if (gameStorage.FileExists(AlienShooterStateFileName))
{
gameStorage.DeleteFile(AlienShooterStateFileName);
}
if (!statusBoard.GameOver)
{
using (IsolatedStorageFileStream fs =
gameStorage.OpenFile(AlienShooterStateFileName,
System.IO.FileMode.Create))
{
using (StreamWriter streamWriter = new StreamWriter(fs))
{
//Only serialize interesting state
//Other state MUST be initialized each time
streamWriter.WriteLine(heroShip.Position.X);
streamWriter.WriteLine(heroShip.Position.Y);
streamWriter.WriteLine(heroShip.Velocity.X);
streamWriter.WriteLine(heroShip.Velocity.Y);
streamWriter.WriteLine(statusBoard.Score);
streamWriter.WriteLine(statusBoard.Lives);
for (int i = 0; i < maxEnemies; i++)
{
streamWriter.WriteLine(enemies[i].Alive);
streamWriter.WriteLine(enemies[i].Position.X);
streamWriter.WriteLine(enemies[i].Position.Y);
streamWriter.WriteLine(enemies[i].Velocity.X);
streamWriter.WriteLine(enemies[i].Velocity.Y);
}
streamWriter.Flush();
streamWriter.Close();
}
}
}
}
}
|
The GameplayScreen.LoadAlienShooterState method instantiates the GameObjects and classes, i.e., heroShip. statusBoardGameStatus object, and the enemies alien ship collection. This code was cut from the GameplayScreen.LoadContent method and pasted into the GameplayScreen.LoadAlienShooterState method shown in Listing 2.
Example 2. GameplayScreen.LoadAlienShooterState Method
public void LoadAlienShooterState()
{
using (IsolatedStorageFile gameStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
//Initialize all objects as before
enemies = new List<AlienGameObject>();
for (int i = 0; i < maxEnemies; i++)
{
enemies.Add(new AlienGameObject(alienShooterSpriteSheet, "spaceship", screenRect));
}
//Initialize Player Object
heroShip = new UserGameObject(alienShooterSpriteSheet, "heroship", screenRect, maxMissiles);
heroShip.Position = new Vector2(screenRect.Width / 2, 720);
heroShip.LoadContent(content);
//Initialize Status Board
statusBoard = new GameStatusBoard(gameFont);
statusBoard.LoadContent(content);
//Set saved state on objects
if (gameStorage.FileExists(AlienShooterStateFileName))
{
using (IsolatedStorageFileStream fs =
gameStorage.OpenFile(AlienShooterStateFileName, System.IO.FileMode.Open))
{
using (StreamReader streamReader = new StreamReader(fs))
{
heroShip.Position = new Vector2(
(float)Convert.ToDouble(streamReader.ReadLine()),
(float)Convert.ToDouble(streamReader.ReadLine()));
heroShip.Velocity = new Vector2(
(float)Convert.ToDouble(streamReader.ReadLine()),
(float)Convert.ToDouble(streamReader.ReadLine()));
statusBoard.Score = Convert.ToInt32(streamReader.ReadLine());
statusBoard.Lives = Convert.ToInt32(streamReader.ReadLine());
for (int i = 0; i < maxEnemies; i++)
{
enemies[i].Alive = Convert.ToBoolean(streamReader.ReadLine());
enemies[i].Position = new Vector2(
(float)Convert.ToDouble(streamReader.ReadLine()),
(float)Convert.ToDouble(streamReader.ReadLine()));
enemies[i].Velocity = new Vector2(
(float)Convert.ToDouble(streamReader.ReadLine()),
(float)Convert.ToDouble(streamReader.ReadLine()));
}
streamReader.Close();
}
}
}
}
}
|
The Save and Load code isn't the most elegant code
you will ever see but it is simple and works fine. With this code in
place, when you hit the Back hard button when playing the game, you get a
Resume menu item. Figure 1 shows the updated MainMenuScreen class UI.
If you play the game until all of your lives are
consumed and then hit the back button, the Resume Game menu option is
not present, because there isn't any saved state.
One issue that remains is, if you are
playing the game and then hit the Start button and then the Back button,
the game resets to a new game on the GameplayScreen UI. What
we want to happen is for state to be saved and the game resume back on
the main menu screen, so that the user has a chance to get ready to play
and then tap the Resume Game menu option. We cover how to implement
tombstone support in the next section.