2. Tombstone Support
Adding tombstone support to your XNA Framework game
is different from adding tombstone support in Silverlight. In
Silverlight, the Deactivated and Activated events only
fire when tombstoning and returning from tombstoning respectively. In
Silverlight, these events do not fire when launching (Launching event) nor when closing (Closing event).
In the XNA Framework, the OnDeactivation (tombstoning) and OnActiviation
(returning from tombstone) also fire when launching and closing the
game, making it difficult to distinguish tombstoning events from regular
launching/closing events.
To detect tombstoning events, you can use the PhoneApplicationService class available in the Microsoft.Phone.dll assembly. You must also add a reference to System.Windows.dll as well, or a compilation error will occur. Add a using Microsoft.Phone.Shell statement to the top of AlienShooterGame.cs (default name is game1.cs in a new project). In the AlienShooterGame() constructor (default name is Game1() in a new project), add this code at the end to hook the Activated and Deactivated events:
//Implement tombstoning support globally if managing
//multiple state objects across game gamescreens
PhoneApplicationService.Current.Activated += AlienGame_Activated;
PhoneApplicationService.Current.Deactivated += AlienGame_Deactivated;
You can then implement tombstoning for the application in the corresponding event handlers in AlienShooterGame.cs:
void AlienGame_Activated(object sender, ActivatedEventArgs e)
{
//Globally handle return from tombstoning here
}
void AlienGame_Deactivated(object sender, DeactivatedEventArgs e)
{
//Globally handle tombstoning here
}
As far as where to store game state, as with Silverlight, you can store transitional state in the PhoneApplicationService.Current.State Dictionary object, like this example:
PhoneApplicationService.Current.State[enemies] = enemies;
The other bit of information available to you is the PhoneApplicationService.Current.StartupMode property, which can have a value of StartupMode.Launch or StartupMode.Activate. This can be useful if you need to detect whether starting up a new application instance (StartupMode.Launch) or returning from tombstone (StartupMode.Activate) down within the game screens. Note that there isn't a corresponding way to check for deactivation. Only the PhoneApplicationService.Deactivated can help there.
For the AlienShooter game, we add code in the AlienGame_Deactivated to find the GameplayScreen and call the SaveAlienShooterState method. Here is the updated code:
void AlienGame_Deactivated(object sender, DeactivatedEventArgs e)
{
//Globally handle tombstoning here
GameScreen[] screens = screenManager.GetScreens();
foreach (GameScreen screen in screens)
if (screen is GameplayScreen)
{
(screen as GameplayScreen).SaveAlienShooterState();
}
}
We update the AlienGame_Activated method to load the MainMenuScreen upon restore from tombstoning, as shown here:
void AlienGame_Activated(object sender, ActivatedEventArgs e)
{
//Globally handle return from tombstoning here
if (!screenManager.DeserializeState())
{
// Activate the first screens.
// Resume at Main Menu so that user isn't caught off guard
screenManager.AddScreen(new BackgroundScreen(), null);
screenManager.AddScreen(new MainMenuScreen(), null);
}
}
There isn't anything else to show for game save/load
or tombstoning. Run the code and play around with the functionality,
including tombstoning the game. As you will see, the functionally works
pretty well and has the feel of a "real" Windows Phone 7 game.
One thing that is missing from the game play is
explosions when the missiles collide with the enemy alien space ships. A
particle system can assist with implementing "random" looking stuff
flying around like you would expect if something is blowing up or
smoking from a missile strike.
3. Particle System
A particle system randomly generates points or small
area sprites with a random velocity and rotation, as well as a lifetime,
such that the points fly out and then disappear much like you see in
Hollywood movie effects. Particle systems can be very simple or
extremely sophisticated, capable of generating water effects, fire,
smoke, and explosions. AppHub includes a simple particle system that
generates smoke and fire for explosions or to show damage. Here is a
link to the sample:
http://create.msdn.com/en-US/education/catalog/sample/particle
3.1. Modifying AlienShooter
In the AlienShooter project I add a folder named ParticleSystem and copy the following files from the AppHub Particle Sample into the new folder in the AlienShooter project, updating the namespace to AlienShooter.ParticleSystem:
ParticleSystem.cs
Particle.cs
ExplosionSmokeParticleSystem.cs
ExplosionParticleSystem.cs
SmokePlumeParticleSystem.cs
The Particle System classes sample have some ties to
the game project so the classes are refactored a bit to fit within the
AlienShooter project. The ParticleSystem classes are dependent on the SpriteBatch being part of the Gameclass instance. In our case, the Game.Components collection in the AlienShooterGame instance is accessed to grab the screenManager object, which then makes the ScreenManager.SpriteBatch accessible to the particle system classes:
((ScreenManager)game.Components[0]).SpriteBatch.Draw
Notice that the assumption is that the ScreenManager is the first Component added with the index of 0 in the Components collection, so it is something to be aware of.
Another modification required is to add the smoke.bmp and explosions.png content files to a new folder named ParticleSystem in the AlienShooter content project. The path values in the Particle System class files are updated by adding ParticleSystem/ path info in front of filenames so that the ContentManager can find them.
The random number-related helper functions were moved from the game class into the ParticleSystem
classes directly, helping to encapsulate the functionality better
within the classes themselves. Another option is to make the random
number helper members a part of the ScreenManager object, which could be useful in other situations when a GameScreen
instance may need a random number for game functionality. Either way,
once all of the house keeping updates to the code are completed, we can
now implement explosions and smoke for the AlienShooter game.
3.2. Adding Explosions and Smoke to AlienShooter
Once all of the modifications are completed to incorporate the AppHub Particle Sample code into the AlienShooter
project, it is brain dead simple to add support for explosions and
smoke plume in the game. In AlienShooterGame.cs, three additional fields
are added:
ExplosionSmokeParticleSystem explosionSmokeParticleSystem;
ExplosionParticleSystem explosionParticleSystem;
SmokePlumeParticleSystem smokePlumeParticleSystem;
The first two are for the explosion, one for the
smoke, the other for the fire. The third field added is for the smoke
plume. In the AlienShooterGame class constructor, the particle system classes are instantiated and then added to the Game.Components collection:
explosionSmokeParticleSystem = new ExplosionSmokeParticleSystem(this, 2);
Components.Add(explosionSmokeParticleSystem);
explosionParticleSystem = new ExplosionParticleSystem(this, 1);
Components.Add(explosionParticleSystem);
smokePlumeParticleSystem = new SmokePlumeParticleSystem(this, 8);
Components.Add(smokePlumeParticleSystem);
The numbers represent the number of particles.
Running on a device, you will see that if you set the number too high it
will directly affect framerate and game performance can suffer.
Now that we have our particle system DrawableComponents configured, it is time to add explosions and smoke plume support to the GameplayScreen.
We will add an explosion (both smoke and fire) each time an enemy alien
ship is shot down. We will add a smoke plume to the hero ship when a
life is lost due to a kamikaze enemy alien ship collision. Since this
involves collision detection, you are right to guess that the code to
draw the explosions and smoke is added there. Listing 3 has the updated CheckForCollisions method.
Example 3. GameplayScreen.LoadAlienShooterState Method
private void CheckForCollisions()
{
//Checking for two major collisions
//1 - Has an in flight missile intersected an alien spaceship - score 5 pts
for (int i = 0; i < heroShip.MaxNumberofMissiles; i++)
if (heroShip.Missiles[i].Alive)
for (int j = 0; j < maxEnemies; j++)
if ((enemies[j].Alive) &&
(enemies[j].BoundingRect.Intersects(heroShip.Missiles[i].BoundingRect)))
{
statusBoard.Score += 5;
//Display Explosion
((ExplosionSmokeParticleSystem)ScreenManager.Game.Components[1]).AddParticles(enemies[j].Position);
((ExplosionParticleSystem)ScreenManager.Game.Components[2]).AddParticles(enemies[j].Position);
enemies[j].ResetGameObject();
heroShip.Missiles[i].ResetGameObject();
}
//2 - Has an alien spaceship intersected the hero ship - deduct a life
for (int j = 0; j < maxEnemies; j++)
if ((enemies[j].Alive) && (enemies[j].Position.Y > 600) &&
(enemies[j].BoundingRect.Intersects(heroShip.BoundingRect)))
{
statusBoard.Lives -= 1;
((SmokePlumeParticleSystem)ScreenManager.Game.Components[3]).AddParticles(heroShip.Position);
for (int i = 0; i < maxEnemies; i++)
enemies[i].ResetGameObject();
for (int i = 0; i < heroShip.MaxNumberofMissiles; i++)
heroShip.Missiles[i].ResetGameObject();
}
}
|
Essentially the desired particle system DrawableComponent instance is grabbed from the AlienShooterGame.Components collection and the correct position Vector2 is passed into the AddParticles method. Here is the call to draw a smoke plume at the hero ship's Position property:
((SmokePlumeParticleSystem)ScreenManager.Game.Components[3]).AddParticles(heroShip.Position);
Note the use of the number index. If the value is incorrect you will get a invalid
cast exception at runtime. Figure 2 shows an explosion and smoke plume in action.
I won't go into the actual particle system sample
code; I'll leave that as an exercise for the reader, but you will see
that it isn't terribly complicated code, which is pretty amazing given
the robust effects that are provided. If anything, you may want to
simplify the code in a real game to minimize performance impact. Try
running the game on a device and you will see the frame rate drop
occasionally if too many explosions occur at the same time.
This completes the coverage of the AlienShooter
sample. What is great about this sample is that you can simply take it
in whole, find and replace namespaces if desired to match your game, and
essentially rewrite the GameplayScreen class and GameObject
descendants to reflect your game logic. Otherwise, the Game Screen
Manager, save and load, tombstoning, and particle system effects are
available for use in your game immediately, though you will have to
customize the actual code to save and load the correct state for your
game.
In the next section we completely shift
gears and provide an overview of the 3D game development capabilities
available within the XNA Framework. The section won't make you a
complete expert, but it will provide you with enough knowledge and
understanding to dive into 3D game development if you find you cannot
resist the allure of matrix mathematics and 3d model manipulation.