Intermission 9–Back to the future (Phone 7)
Marty you gotta see this…. (whoops, back to the tutorial)
Hope you are not getting to dizzy now going back and forth from XNA GS 3.1 and GS 4.0 and the windows phone. But lets bring the phone project up to date with current efforts and also wrangle out some gotcha’s that have come to light.
Source updated for Final combined update project for GS 4.0 project here on Codeplex (Windows and WP7)
Game save settings
Granted we don’t have many settings to actually maintain on the phone at present, but we could have (like resolution, game speed, difficulty and such) and in the interest of at least attempting to make the GS 4.0 code ready for running everything we’ll add it in.
So first off copy the “FileManager.cs” and “KeyMappings.cs”, then update their namespace to match the rest of the project, “XNAStarTrooper2D_Phone7”.
Now it would be nice if that was it but first we have some alterations to handle, first up. The windows phone, unlike XBOX and windows doesn’t use storage containers, instead it uses something called Isolated Storage. Pretty much the same thing just a different name. Second it has a slightly different way of creating files, so we’ll deal with that as well. What it boils down to is that pretty much all of our code in the File Manager is useless. So why copy the File Manager class in at all, well that is to try and keep the project unified.
So first we need to add compiler pre-directives around our existing code first, so add:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: #<span style="color: rgb(0, 0, 255);">if</span> (!WINDOWS_PHONE) |
Just after the Class definition and add the following just after the SaveKeyMappings function:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: #<span style="color: rgb(0, 0, 255);">else</span> |
1 |
2: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">static</span> void LoadKeyMappings() |
1 |
3: { |
1 |
4: <span style="color: rgb(0, 0, 255);">using</span> (IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication()) |
1 |
5: { |
1 |
6: <span style="color: rgb(0, 0, 255);">using</span> (IsolatedStorageFileStream file = appStorage.OpenFile("<span style="color: rgb(139, 0, 0);">StarTrooperControls.sav</span>", FileMode.OpenOrCreate)) |
1 |
7: { |
1 |
8: <span style="color: rgb(0, 0, 255);">try</span> |
1 |
9: { |
1 |
10: XmlSerializer serializer = <span style="color: rgb(0, 0, 255);">new</span> XmlSerializer(<span style="color: rgb(0, 0, 255);">typeof</span>(InputMappings)); |
1 |
11: Input.SettingsSaved = <span style="color: rgb(0, 0, 255);">true</span>; |
1 |
12: serializer.Serialize(file, Input.InputMappings); |
1 |
13: } |
1 |
14: <span style="color: rgb(0, 0, 255);">catch</span>{} |
1 |
15: |
1 |
16: } |
1 |
17: } |
1 |
18: } |
1 |
19: |
1 |
20: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">static</span> void SaveKeyMappings() |
1 |
21: { |
1 |
22: <span style="color: rgb(0, 0, 255);">using</span> (IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication()) |
1 |
23: { |
1 |
24: <span style="color: rgb(0, 0, 255);">using</span> (IsolatedStorageFileStream file = appStorage.OpenFile("<span style="color: rgb(139, 0, 0);">StarTrooperControls.sav</span>", FileMode.OpenOrCreate)) |
1 |
25: { |
1 |
26: <span style="color: rgb(0, 0, 255);">try</span> |
1 |
27: { |
1 |
28: XmlSerializer serializer = <span style="color: rgb(0, 0, 255);">new</span> XmlSerializer(<span style="color: rgb(0, 0, 255);">typeof</span>(InputMappings)); |
1 |
29: |
1 |
30: Input.InputMappings = (InputMappings)serializer.Deserialize(file); |
1 |
31: } |
1 |
32: <span style="color: rgb(0, 0, 255);">catch</span> { } |
1 |
33: |
1 |
34: } |
1 |
35: } |
1 |
36: } |
1 |
37: } |
1 |
38: #<span style="color: rgb(0, 0, 255);">endif</span> |
If done right, you should see all the code greyed out, this pretty much leaves us with a blank class definition for windows phone while still retaining the functionality for other platforms.
But we still see several red wavy lines under the xml serialisation functions. This is because the protection around DLL’s have changes (you will see this a lot) and the serialisation implementation has been updated slightly. This is not a biggie, just right click “References” in the solution explorer and select add reference, then select “System.XML.Serialization” and click ok. Finally add a using reference in the top of the class:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: <span style="color: rgb(0, 0, 255);">using</span> System.Xml.Serialization; |
So we have added two new overloads which are Windows Phone specific thanks to the Pre-compiler statements, this means we don’t need to change the rest of the game code for saving and loading, although in hindsight, the name of the save and load functions could do with renaming now (something for later when the projects unify).
The two new functions use the phone specific way of saving and loading data, which is very similar to the XBOX version:
Instead of Storage Container we use Isolated Storage, the major advantage of this is that we don’t need to use the guide to get the user to choose a container, it’s all built in.
How we access files is slightly different, we still use streams but as with the isolated storage, it’s a lot simpler and easier to use.
No change in the serialisation, except that we now need to manually add a reference to the Serialisation DLL
To finish it off we need to add another pre processor directive to the using section of the class around the Storage statements and also add a reference for isolated storage, so replace:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: <span style="color: rgb(0, 0, 255);">using</span> Microsoft.Xna.Framework.Storage; |
With:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: #<span style="color: rgb(0, 0, 255);">if</span> (!WINDOWS_PHONE) |
1 |
2: <span style="color: rgb(0, 0, 255);">using</span> Microsoft.Xna.Framework.Storage; |
1 |
3: #<span style="color: rgb(0, 0, 255);">else</span> |
1 |
4: <span style="color: rgb(0, 0, 255);">using</span> System.IO.IsolatedStorage; |
1 |
5: #<span style="color: rgb(0, 0, 255);">endif</span> |
Last thing to update is in the KeyMappings class. Just remove the [Serializable] references (apparently they are only needed for binary serialisation). You can also remove all using statements except Microsoft.Xna.Framework.Input (this is a good general rule of thumb, to only declare what you actually intend to use in your code, same can be said of references). I’ll tidy up more later.
To update the main game to load and save settings, simply add the following in to the LoadResources function of StarTrooperGame.cs:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: //<span style="color: rgb(0, 0, 255);">Try</span> <span style="color: rgb(0, 0, 255);">and</span> load any saved key mappings |
1 |
2: FileManager.LoadKeyMappings(); |
1 |
3: |
1 |
4: //<span style="color: rgb(0, 0, 255);">If</span> no settings present <span style="color: rgb(0, 0, 255);">or</span> setting were unable <span style="color: rgb(0, 0, 255);">to</span> be loaded, use the defaults |
1 |
5: <span style="color: rgb(0, 0, 255);">if</span> (!Input.InputMappings.SettingsSaved) Input.Load_Defaults(); |
And so that our settings are saved when the game finishes, change the exit code at the start of the Update section to:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: // Allows the game <span style="color: rgb(0, 0, 255);">to</span> <span style="color: rgb(0, 0, 255);">exit</span> |
1 |
2: <span style="color: rgb(0, 0, 255);">if</span> (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) |
1 |
3: { |
1 |
4: FileManager.SaveKeyMappings(); |
1 |
5: this.<span style="color: rgb(0, 0, 255);">Exit</span>(); |
1 |
6: } |
This adds a call to Save Settings before we exit.
Particle Engine
The changes to the particle manager are also fairly cosmetic and mainly involve replacing:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: SpriteBlendMode.Additive |
To:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: BlendState.Additive |
Then:
Updating the namespace of all the classes
Updating the type SpriteBlendMode in the ParticleEmitter class from SpriteBlendMode to BlendState (standard XNA 4.0 change) and the settings in each emitter to the BlendState equivalent.
Updating the SpriteBatch.begin call in the particle manager draw call to “spriteBatch.Begin(SpriteSortMode.Texture, pe.SpriteBlendMode)
So copy in the “ParticleManager.cs”, “ParticleEmitter.cs” and “ParticleEmitters.cs” classes from the 3.1 project and make the above changes.
Then like before, we just need to add the particle engine references in the game itself like in the last post, these comprise of:
Particle Manager attribute in the start of the StarTrooperGame class:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">static</span> ParticleManager ParticleManager; |
Ant the particle manager initialisation in the StarTrooperGame constructor:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: ParticleManager = <span style="color: rgb(0, 0, 255);">new</span> ParticleManager(this); |
1 |
2: this.Components.Add(ParticleManager); |
Then we need to add the new Fireball effect in the StarTrooperSprites.cs file, just after the TrooperFire function in the Trooper class:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: void FireballLaunch(Vector2 position, Vector2 velocity, Vector2 accel) |
1 |
2: { |
1 |
3: |
1 |
4: FireballSmokeParticleEmitter smokeemitter = <span style="color: rgb(0, 0, 255);">new</span> FireballSmokeParticleEmitter(); |
1 |
5: smokeemitter.Initialize("<span style="color: rgb(139, 0, 0);">smoke</span>", 10); |
1 |
6: smokeemitter.EmitterPosition = position; |
1 |
7: smokeemitter.EmitterVelocity = velocity; |
1 |
8: smokeemitter.EmitterAcceleration = accel; |
1 |
9: smokeemitter.ParticleCycleTime = 0f; |
1 |
10: StarTrooperGame.ParticleManager.Add(smokeemitter); |
1 |
11: |
1 |
12: FireballParticleEmitter fireballemitter = <span style="color: rgb(0, 0, 255);">new</span> FireballParticleEmitter(); |
1 |
13: fireballemitter.Initialize("<span style="color: rgb(139, 0, 0);">explosion</span>", 10); |
1 |
14: fireballemitter.EmitterPosition = position; |
1 |
15: fireballemitter.EmitterVelocity = velocity; |
1 |
16: fireballemitter.EmitterAcceleration = accel; |
1 |
17: fireballemitter.ParticleCycleTime = 0f; |
1 |
18: StarTrooperGame.ParticleManager.Add(fireballemitter); |
1 |
19: |
1 |
20: } |
And add the call to the TrooperFire function:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: FireballLaunch(<span style="color: rgb(0, 0, 255);">new</span> Vector2(Position.X, Position.Y - 35), <span style="color: rgb(0, 0, 255);">new</span> Vector2(0, -40), <span style="color: rgb(0, 0, 255);">new</span> Vector2(0, -0.5f)); |
1 |
2: |
Then to finish it off we need to expose the helper features in the StarTrooperGame class, so update the Random attribute to:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: <span style="color: rgb(0, 0, 255);">static</span> Random m_Random = <span style="color: rgb(0, 0, 255);">new</span> Random(); |
The RandomBetween function towards the end of the class (after Load Resources):
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: #region Helper Functions |
1 |
2: |
1 |
3: // a handy little <span style="color: rgb(0, 0, 255);">function</span> that gives a random float between two |
1 |
4: // values. This will be used <span style="color: rgb(0, 0, 255);">in</span> several places <span style="color: rgb(0, 0, 255);">in</span> the sample, <span style="color: rgb(0, 0, 255);">in</span> particilar <span style="color: rgb(0, 0, 255);">in</span> |
1 |
5: // ParticleSystem.InitializeParticle. |
1 |
6: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">static</span> float RandomBetween(float min, float max) |
1 |
7: { |
1 |
8: <span style="color: rgb(0, 0, 255);">return</span> min + (float)m_Random.NextDouble() * (max - min); |
1 |
9: } |
1 |
10: |
1 |
11: #endregion |
And lastly the property to expose our random generator at the very end:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: // a random number generator that the whole sample can share. |
1 |
2: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">static</span> Random Random { <span style="color: rgb(0, 0, 255);">get</span> { <span style="color: rgb(0, 0, 255);">return</span> m_Random; } } |
Game Updates
In the last few posts I also made a few basic program updates to Sprites, so we’ll update those here as well.
First off was to move the origin property to the sprite class from the animation class, this fixes the centre point of the texture on the screen for drawing sprites, so add the attribute to the bottom of the Sprite class, like so:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: Vector2 m_Origin = Vector2.Zero; |
Then add the property to expose it, at the end of the properties section:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: <span style="color: rgb(0, 0, 255);">public</span> Vector2 Origin |
1 |
2: { |
1 |
3: <span style="color: rgb(0, 0, 255);">set</span> |
1 |
4: { |
1 |
5: m_Origin = value; |
1 |
6: } |
1 |
7: <span style="color: rgb(0, 0, 255);">get</span> |
1 |
8: { |
1 |
9: <span style="color: rgb(0, 0, 255);">return</span> m_Origin; |
1 |
10: } |
1 |
11: } |
Then to ensure the attribute is properly populated when the Sprite is constructed, update the constructors as follows:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: <span style="color: rgb(0, 0, 255);">public</span> Sprite() |
1 |
2: { |
1 |
3: m_Id = m_Counter++; |
1 |
4: } |
1 |
5: |
1 |
6: <span style="color: rgb(0, 0, 255);">public</span> Sprite(Texture2D Texture) |
1 |
7: : base() |
1 |
8: { |
1 |
9: AddAnimation(<span style="color: rgb(0, 0, 255);">new</span> Animation(Texture)); |
1 |
10: m_Origin = <span style="color: rgb(0, 0, 255);">new</span> Vector2((float)Texture.Width / 2, (float)Texture.Height / 2); |
1 |
11: } |
1 |
12: |
1 |
13: <span style="color: rgb(0, 0, 255);">public</span> Sprite(Texture2D Texture,int Frames, bool <span style="color: rgb(0, 0, 255);">Loop</span>) |
1 |
14: : base() |
1 |
15: { |
1 |
16: Animation animation = <span style="color: rgb(0, 0, 255);">new</span> Animation(Texture,Frames); |
1 |
17: m_Origin = <span style="color: rgb(0, 0, 255);">new</span> Vector2((float)(Texture.Width / Frames) / 2, (float)Texture.Height / 2); |
1 |
18: animation.<span style="color: rgb(0, 0, 255);">Loop</span> = <span style="color: rgb(0, 0, 255);">Loop</span>; |
1 |
19: animation.Play(); |
1 |
20: |
1 |
21: AddAnimation(animation); |
1 |
22: } |
Adding the Origin references as appropriate.
Next we update the background creation logic to override the constructor and set the background Origin to Zero, makes it easier to draw background textures absolute:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: Background bg = <span style="color: rgb(0, 0, 255);">new</span> Background(background); |
1 |
2: |
1 |
3: bg.Position = <span style="color: rgb(0, 0, 255);">new</span> Vector2(0, BackBufferHeight / 2); |
1 |
4: bg.ScaleX = BackBufferWidth / background.Width; |
1 |
5: bg.ScaleY = BackBufferHeight / background.Height; |
1 |
6: bg.ZOrder = 10; |
1 |
7: bg.Origin = Vector2.Zero; |
1 |
8: bg.Velocity = <span style="color: rgb(0, 0, 255);">new</span> Vector2(0,1); |
That should do it but I also made one other change here that will help us later, I’ve set the velocity of the main background so that the sprite update loop will move the background instead of just incrementing it by 1 all the time in the StarTrooperBackground class. This enables us to change the speed of the background later.
But by setting this property we also need to update the StarTrooperBackground classes update method, else it will always move twice as fast, so update this code to:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: <span style="color: rgb(0, 0, 255);">public</span> override void Update() |
1 |
2: { |
1 |
3: Vector2 NewPosition = Position; |
1 |
4: <span style="color: rgb(0, 0, 255);">if</span> (NewPosition.Y == StarTrooperGame.BackBufferHeight) |
1 |
5: { |
1 |
6: NewPosition.Y = -StarTrooperGame.BackBufferHeight; |
1 |
7: Position = NewPosition; |
1 |
8: } |
1 |
9: } |
So all this update function does now is to shift the background up to the top once it has passed beyond view at the bottom of the screen. However if the background was moving up this wouldn’t work.
Feel free to experiment with this if you want, to use a different velocity in a different direction.
To finish up you still need to remove all the references to m_Origin from the Animation.CS class m like the attribute at the end of the class, and the copy reference in the Clone constructor.
Basically search for all reference in the Animation class and remove them, the only exception is the Draw call which needs to be updated to:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: spritebatch.Draw(m_SpritesheetTexture, sprite.Position, CurrentFrame, m_Colour, sprite.Rotation, |
1 |
2: sprite.Origin,sprite.Scale, sprite.SpriteEffect, 0); |
Changing the Origin property to use the new Sprite Origin property.
Gotcha!!!
Now a good friend of mine sent me a little gotcha for Window Phone 7 projects, this refers to using Keyboard state. if you use it the performance of your game will suck big time. Turns out he was dead right.
After commenting out keyboard references, a certain amount of LAG (which I put down to the emulator on my laptop) disappeared, and the get felt a lot smoother.
Since input seems to be one of those things that is ever changing in the CTP, I’ve just removed all references in the Input.CS class for Keyboard, so removing things like:
Any Keyboard state attributes or updates
The private functions for testing keys
shortened the Player controls to remove the IsPressed function (just removed)
Removed the TrooperFired function alltogether since we are currently using the touchscreen to fire
Nice catch Charles!! (Randomchaos / Nemo Krad) Humphrey
Conclusion
That’s a lot of updates for one post, so I’m moving the rest of what I was going to cover (particle experiment and an accelerometer simulator) in the next intermission (last one in this section I promise )
Now put down the keyboard and step away please sir, nobody needs to get hurt.