The only thing harder than adding Tombstoning support is adding it later. “Tombstoning” on Windows Phone is what happens when a user navigates away from your game – you’ve got a few moments in which to store state and get out of dodge.  Later, if the user “backs” to your game, you leverage that stored state to restore the game to where it was before.

Today I added tombstoning support into the Dungeon source code; the pain of keeping that up to date as new features come online pales compared to the pain of adding it in a couple of months.  Now in the emulator you can hit the “home” key and then “back” to the game, and it will be in the same state as before.

In order to ensure I’m not designing in a way that would break on the PC (which doesn’t have tombstoning), I’ve also added support so that when you exit on the PC it saves the current state, and it reloads that state automatically the next time you start the game; this’ll eventually be replaced with Continue/New game functionality in the main menu (for the phone as well), but for now it allows me to continue testing in the PC version rather than the emulator.  Note that pressing the right-shift key ignores the save game state and starts over.

Code changes

The code changes themselves are fairly straightforward.  In the game’s constructor, I hook up events for Launching (which fires when an app is started from scratch), Activated (which fires when an app is starting after being tombstoned), and Deactivated (which fires when an app is tombstoning):

// Hook up events for Tombstoning (which only occurs on the phone)
 PhoneApplicationService.Current.Activated += new EventHandler<ActivatedEventArgs>(OnGameActivated);
 PhoneApplicationService.Current.Deactivated += new EventHandler<DeactivatedEventArgs>(OnGameDeactivated);
 PhoneApplicationService.Current.Launching += new EventHandler<LaunchingEventArgs>(OnGameLaunching);

The actual handlers (in DungeonGame.cs) are below.  I store a tag for which GameState the game was in when tombstoned and leverage that to get to the GameState-specific restoration code (you can find that in GameState_Loading.cs).  The handlers:


/// <summary>
 /// Called when the game is being launched
 /// </summary>
 /// <param name="sender">Ignored</param>
 /// <param name="e">Ignored</param>
 void OnGameLaunching(object sender, LaunchingEventArgs e)
 {
     // Load subsystems, and then go to the MainMenu state
     GameStateMgr.SetState(new GameState_Loading("MainMenu", false));
 }

 /// <summary>
 /// Occurs when the application is being made active after previously being tombstoned.
 /// </summary>
 /// <param name="sender">Ignored</param>
 /// <param name="e">Ignored</param>
 void OnGameActivated(object sender, ActivatedEventArgs e)
 {
     // User "backed" back to us.  restore tombstoned state (if any present)
     if (!PhoneApplicationService.Current.State.ContainsKey("Mode"))
     {
         // Tombstoning state is in unexpected state; just start in Main Menu
         GameStateMgr.SetState(new GameState_Loading("MainMenu", false));
     }
     else
         GameStateMgr.SetState(new GameState_Loading((string)PhoneApplicationService.Current.State["Mode"], true));
 }

 /// <summary>
 /// Called when the user exits the application (home button, search, etc)
 /// </summary>
 /// <param name="sender">Ignored</param>
 /// <param name="e">Ignored</param>
 void OnGameDeactivated(object sender, DeactivatedEventArgs e)
 {
     PhoneApplicationService.Current.State["Mode"] = GameStateMgr.CurrentState.Name;
     GameStateMgr.CurrentState.Persist();
 }

Many of the game classes now have persist/restore functionality as well.  For example, here’s what the GameObject class’ Persist and Restore functions look like:

 /// <summary>
 /// Restores the GameObject from the specified serialization stream, and places
 /// the GameObject at its location in the level.
 /// </summary>
 /// <param name="reader">Stream object that contains restoration data</param>
 /// <param name="level">Level into which this GameObject will be placed</param>
 public virtual void Restore(FileReader reader, Level level)
 {
     // Let our base class (RenderableObject) restore information first
     base.Restore(reader);

     // Get our location from the stream and inject ourselves into the Level at the appropriate location
     int x = reader.ReadInt();
     int y = reader.ReadInt();
     level.MoveObject(this, level.Cells[x, y]);
 }

 /// <summary>
 /// Persists the GameObject to the specified stream object
 /// </summary>
 /// <param name="writer">Stream object to which this GameObject will be persisted</param>
 public virtual void Persist(FileWriter writer)
 {
     // Let our base class (RenderableObject) persist information first
     base.Persist(writer);

     // Store our location
     writer.WriteInt(Location.X);
     writer.WriteInt(Location.Y);
 }

The final change was adding a pair of FileReader/FileWriter classes that abstract out some of the pain of reading/writing to streams.  the FileReader/FileWriter also support saving as encrypted files; useful later in providing a minimum level of security for savegame files.

Getting out in front of the question: Yes, I should probably utilize .NET’s built-in Serialization support.  Maybe in the next version…

Here’s the PC executable: http://wanderlinggames.com/files/dungeon/dungeonexe-3-13-11.zip

And here’s the updated source code: http://wanderlinggames.com/files/dungeon/dungeon-3-13-11.zip

Advertisements