Digital Transfusion

Putting the internet in your veins!

Zero Gravity: Moving to WP7

7 comments

In a previous post I released the source code to Zero Gravity. In this post I am going to dive in and explain the changes that had to be made to make the game ready for Windows Phone 7. I was actually surprised at how little actually had to change considering the code was originally written back when WPF/e was still walking around.

There can be only one!

The original game was written with a lot of MediaElements. The sound effects were loaded using something like this:

MediaElement toAdd = new MediaElement();
toAdd.SetSource(GetType().Assembly.GetManifestResourceStream(soundFileToPlay));

There are a couple of problems with this in Wp7. The first problem is that the MediaElement.SetSource method no longer accepts a type stream(even though the intellisense says it does).  Instead it requires an IsolatedStorageFileStream. Whether this was a bug, or an intentional change,  A quick fix would seem apparent:

IsolatedStorageFile v = IsolatedStorageFile.GetUserStoreForApplication();
if (!v.FileExists(musicFileToPlay))
{
    using (IsolatedStorageFileStream isfs =
                new IsolatedStorageFileStream(musicFileToPlay,
                FileMode.CreateNew, FileAccess.Write, v))
    {
        using (Stream s =
                this.GetType().Assembly.
                     GetManifestResourceStream(musicFileToPlay))
        {
            byte[] buffer = new byte[s.Length];
            s.Read(buffer, 0, buffer.Length);
            isfs.Write(buffer, 0, buffer.Length);
            s.Close();
        }
        isfs.Close();
    }
}
musicPlayer.SetSource(new
        IsolatedStorageFileStream(musicFileToPlay,
        FileMode.Open,
        FileAccess.Read, v));

Of course, you would notice that this effectively doubles the storage space needed for the audio files (once in the .xap, once in isolated storage). That isn’t a good thing, but with a little management you can make sure the application cleans up after itself…but there are more problems.

If you were to run this in the game, you would find that the background music would work. However nothing else would work. The movie transition between levels would fail and there would be no sound effects as you move around the boards.  Needless to say, it is a very bad user experience.

So what is going on?

One thing that a lot of developers forget about with regards to Silverlight on Windows Phone 7 is that you can only have 1 active media element. In fact I forgot about this little tidbit of information, and had to be reminded of this fact. (Thanks Bill!) For anyone else that needs a reminder, I recommend watching Davit Cutler’s presentation from MIX 2010.

So what is the solution to this? Well, on Windows Phone 7, there this is a little bleed over beteween XNA and Silverlight. The solution is to use the XNA framework to handle all the audio. For the ease of transition, I kept the movie inside the MediaElement (since I am allowed to have one).

To make this work, I added a reference to “Microsoft.Xna.Framework.” This gives us access to the  Microsoft.Xna.Framework.Audio.SoundEffect Class, which is used in XNA to play sound effects. So now the code to load the audio files went to something like this:

SoundEffect se = SoundEffect.FromStream(TitleContainer.OpenStream(soundFileToPlay));

Note: I could be wrong here, I am not an expert in XNA, but all audio files had to be converted to .wav files. The desktop MediaElements accepted the .mp3 file format, so there was an increase in size in the WP7 version.

Night of the living dead

The next change from the desktop version stems from the fact that the phone can’t multitask (at least not yet). So when the game is interrupted by something (maybe a phone call, or someone needs to check their email) it is a best if the game doesn’t dump them out to the main game menu when they return (leaving them to start completely over). This is accomplished by a process that has been dubbed “Tombstoning.”

In the app.xaml.cs file there are four methods to worry about if you want to implement Tombstoning:

  • Application_Launching
    • This is ran when the game is started for the first time. Otherwise known as a cold boot. It does not run when the game is resuming.
  • Application_Activated
    • This is ran when the game resumes from being put into the background or unloaded. This process has been referred to as “reanimation” (the process of coming back to life after being deactivated/unloaded).
  • Application_Deactivated
    • This is fired when the application is “interrupted”, but not shut down. So if you answer a phone call, swap to another application or just press the windows key.
  • Application_Closing
    • This is fired when the game is being closed down, but not when the game is being deactivated.

So what does this mean for the game? Well if there is an active game and the user decides to press the windows button I want to store off the current level and score, and restart the user from the beginning of that level. This is accomplished by putting the following code into the Application_Deactivated:

IsolatedStorageSettings store = IsolatedStorageSettings.ApplicationSettings;
MainPage page = ((MainPage)RootFrame.Content);
DisplayStack view = page.View;
if (view.IsPlaying)
{
    Player p = view.GameModel.GetPlayer();
    store["wasPlaying"] = true;
    store["password"] = view.BoardHolder.Password;
    store["score"] = page.GetCumulativeScore();
}
store.Save();

When the game is resumed, it is reloaded using the following code:

IsolatedStorageSettings store = IsolatedStorageSettings.ApplicationSettings;
if (store.Contains("wasPlaying"))
{
    if ((bool)store["wasPlaying"])
    {
        if (store.Contains("password") &&
            store.Contains("score") &&
            store.Contains("position"))
        {
            ((MainPage)RootFrame.Content).ReanimateGame((string)store["password"],
                                                       (int)store["score"],
                                                       (Point)store["position"],
                                                        0,
                                                        0,
                                                        TimeSpan.Zero);

            store.Remove("wasPlaying");
            store.Remove("password");
            store.Remove("score");
            store.Remove("position");

            store.Save();
        }
    }
}

How do I get there again?

One of the biggest differences between WP7 and most desktop environments is the user interface. that is to say that on the computer you have a keyboard. On a WP7 device you use gestures to get around, and it only makes sense to use swipe gestures to control the direction Lt. Bennett goes.

There is a really nice set of gesture helpers inside the wp7 Silverlight Toolkit. They make wiring the whole thing up a lot easier.

Inside the constuctor, store off a reference to the listener:

_gestureListener = GestureService.GetGestureListener(LayoutRoot);

There are two methods that enable or disable user input:

//disable input method
if (_gestureListener != null)
{
    _gestureListener.DragCompleted -= GestureListenerDragCompleted;
}
//enable input method
if (_gestureListener != null)
{
    _gestureListener.DragCompleted += GestureListenerDragCompleted;
}

And last but not least, the DragCompleted method that actually determines the direction to send Lt. Bennett on:

private void GestureListenerDragCompleted(object sender, DragCompletedGestureEventArgs e)
{
    if (e.Direction == System.Windows.Controls.Orientation.Vertical)
    {
        if (e.VerticalChange > 0)
        {
            _playerMover.MoveByMouse("Down");
        }
        else
        {
            _playerMover.MoveByMouse("Up");
        }
    }
    else
    {
        Debug.WriteLine("Horizontal:" + e.HorizontalChange);
        if (e.HorizontalChange > 0)
        {
            _playerMover.MoveByMouse("Right");
        }
        else
        {
            _playerMover.MoveByMouse("Left");
        }
    }
}

Always check performance with a device

I can still hear all the presenters at MIX reverberating the same message in every single session, “always check the performance of your app with a real device before deployment.” This cannot be understated. The emulator works fine, but generally performance on an actual device is even slower (though they have brought the difference between device and emulator speed differences down considerably).

The game runs at 60 FPS on a desktop without any issues, but if you did a straight conversion the game stutters a lot. Animations flicker, animations skip frames, and in general it just runs slow (15 FPS at certain points).

What this means is that you have to go through your code and basically copy/paste CacheMode=”BitmapCache” into many, many areas to help ensure that the game is sufficiently cached. This keeps the compositing on the GPU and not on the CPU, allowing there to be more clock cycles to process things like game logic. This equated to a HUGE difference in gameplay and animation speed.

While this is a really simple thing to copy/paste ClientMode, it is error prone. I can’t for the life of me figure out why the Silverlight team decided to leave performance enhancements like this to the developer and not to the underlying framework. If we leave it to the developers, there are going to be a lot of applications that don’t use the settings at all, or miss setting a few key objects here and there. But since it is left to the developer to do it, don’t forget to go through and test your application. “App.Current.Host.Settings.EnableCacheVisualization = true;” is your friend in this matter, so use it.

And that is it!

The rest of the application is exactly the same as the desktop version. While I know the code base is old, and by no means follows any of the modern patterns (lets face it, we were writing against an unknown architecture at the time), it is still a testament to how good a job the team is doing at backwards compatibility.

I for one was very shocked that the transition went as smoothly as it did.

Tags: , ,

7 Responses to “Zero Gravity: Moving to WP7”

  1. [...] pre-loader (back before there was such a thing as a “splashScreenSource”) runs without a hitch. In my next post, I will go into the changes that were necessary to get the game to work in WP7 but until [...]

  2. MIchael Flynn says:

    Wow you are a good developer!

  3. [...] This post was mentioned on Twitter by Kelly, ryanplemons. ryanplemons said: I was amazed at how easy it was to convert a #silverlight game to #wp7dev. Zero gravity was originally written in WPF/e http://bit.ly/bkaJsz [...]

  4. [...] Porting Zero Gravity to Windows Phone 7 If you haven’t yet got the tools yet for Windows Phone development, you can get the release versions now at the Windows Phone developer portal. Hope this helps! tags: silverlight, windows-phone, windows phone 7, terralever, xaml, ria, zerog, zero gravity, gaming, casual games This work is licensed under a Creative Commons Attribution By license. [...]

  5. [...] pre-loader (back before there was such a thing as a “splashScreenSource”) runs without a hitch. In my next post, I will go into the changes that were necessary to get the game to work in WP7 but until [...]

  6. [...] Porting Zero Gravity to Windows Phone 7 If you haven’t yet got the tools yet for Windows Phone development, you can get the release versions now at the Windows Phone developer portal. [...]

Leave a Reply