DCSIMG
August 2011 - Posts - Pavel's Blog
Sign in | Join | Help

Pavel's Blog

Pavel is a software guy that is interested in almost everything
software related... way too much for too little time

August 2011 - Posts

Porting an XNA game from Windows to Windows Phone (Part 3)

Published at Aug 25 2011, 12:57 PM by pavely

In the previous post, we got a working game that uses touch gestures to move the player’s ship. This may be adequate, but perhaps we can try a different approach: using the accelerometer.

Every Windows Phone device must have an accelerometer, which indicates its relative orientation in space. Tilting the phone from side to side may be more intuitive (and perhaps exciting) than simply dragging horizontally. Let’s try that.

The first thing we need to do is add a reference to the Microsoft.Devices.Sensors assembly, and add a conditional using statement to the Microsoft.Devices.Sensors namespace. This exposes the Accelerometer class that we can use to determine the orientation of the device.

Now that this is set, we can initialize the accelerometer and start getting some results:

_accel = new Accelerometer();
_accel.ReadingChanged += (sender, e) => {
    _moveOffset = -Math.Min(15, 20 * (float)Math.Abs(e.Y)) * Math.Sign(e.Y);
};
_accel.Start();

The RadingChanged event is fired often, indicating a change in the device’s orientation. Here I update a field (_moveOffset) that is later used to change the position of the ship. Note that the event is raised on a different thread (not the UI thread), which may be an issue when working with a “true” UI, such as a Silverlight application. In that case, you may need to marshal the handler code to the UI thread. Here’s a simple example from a Silverlight app, that wants to display the readings from the accelerometer in three TextBlock objects:

_accel = new Accelerometer();
var sc = SynchronizationContext.Current;
_accel.ReadingChanged += (sender, e) => {
    sc.Post(delegate {
        _x.Text = e.X.ToString();
        _y.Text = e.Y.ToString();
        _z.Text = e.Z.ToString();
    }, null);
};
_accel.Start();

This code uses the SynchronizationContext class to do the marshaling (an alternative would be to use the Dispatcher.BeginInvoke method). _x, _y and _z are TextBlock names.

In our game, this does not matter much, as we just update a variable. There is no “true” UI to be concerned about. There still may be a concern as _moveOffset may be updated while it’s being used by the UI thread within the game loop. This is not an issue here, as it’s a float, which means it should update atomically (the exact reason is out of scope for this post).

The X, Y and Z properties of the AccelerometerReadingEventArgs object are in the range –1.0 to +1.0, indicating the rotation angle around the relevant axis. In our case, the Y axis is the relevant one.

Now we can change the movement code, such as the following:

speed = Math.Abs(_moveOffset);
right = _moveOffset > 0;
left = _moveOffset < 0;

I’ve also commented out the horizontal drag gesture handling code, although we could have left that so the player can use either mechanism.

Testing

To test this, we could use the emulator, but really, in this case, there is no substitute for the actual device. Before testing, we have to indicate that we require access to the device’s sensors, otherwise a security exception will be thrown and our app terminated. This is done in the WMAppManifest.Xml:

<Capabilities>
  <Capability Name="ID_CAP_NETWORKING" />
  <Capability Name="ID_CAP_SENSORS" />
  <!--<Capability Name="ID_CAP_LOCATION" />
  <Capability Name="ID_CAP_MICROPHONE" />
  <Capability Name="ID_CAP_MEDIALIB" />
  <Capability Name="ID_CAP_GAMERSERVICES" />
  <Capability Name="ID_CAP_PHONEDIALER" />
  <Capability Name="ID_CAP_PUSH_NOTIFICATION" />
  <Capability Name="ID_CAP_WEBBROWSERCOMPONENT" />
  <Capability Name="ID_CAP_IDENTITY_USER" />
  <Capability Name="ID_CAP_IDENTITY_DEVICE" />-->

  <!-- Windows Phone OS 7.1 Capabilities -->
  <!--<Capability Name="ID_CAP_ISV_CAMERA" />
  <Capability Name="ID_CAP_CONTACTS" />
  <Capability Name="ID_CAP_APPOINTMENTS" />
-->
</
Capabilities
>

By default, a new project enables all capability flags. It’s a good idea to enable only the capabilities we actually require. That’s because in the marketplace, when an app is installed, it asks the user to agree to the app’s requested capabilities. The more capabilities requested, the greater chance the user will be “afraid” to let the app be installed. So, it’s better to request as few capabilities as possible. The networking capability, by the way, is not strictly required for our game, as we don’t do any networking, but it’s required so that the emulator can be contacted. it should be removed before uploading to the marketplace if not actually needed1.

Porting an XNA game from Windows to Windows Phone (Part 2)

Published at Aug 14 2011, 03:40 PM by pavely

In the first part we got our introduction screen up and running, with the starfield scrolling by. However, we couldn’t actually start playing because the game was waiting for a key press… which is nowhere to be found on a Windows Phone device. We simply need a little touch.

Adding Touch Support

Touch is something the XNA version of Windows and XBOX 360 have no notion of. This is specific for Windows Phone. Touch input information is available as “raw” data (touch points, with positions, etc.) and with a higher level abstraction called gestures. We’ll start with gestures, as they’re easier to work with, and only go to a lower level if needed.

The first thing we need to do is add a reference to the Microsoft.Xna.Framework.Input.Touch assembly. We’ll also add (conditionally) a using statement to every file that needs touch access like so:

#if WINDOWS_PHONE
using Microsoft.Xna.Framework.Input.Touch;
#endif

Now we need to enable gestures. This is done using the static TouchPanel class. We’ll add the following code to the AlienRaidGame constructor:

#if WINDOWS_PHONE
            TouchPanel.EnabledGestures = GestureType.Tap | GestureType.HorizontalDrag;
#endif

We’re enabling the “Tap” gesture (similar to a mouse click) and a “horizontal drag” – this will be used to move the player’s ship right or left.

Note that putting this code in the static constructor does not throw any exception, but no gesture would ever be reported!

Starting the Action

Now we can get down to business. The IntroComponent class is the one responsible for moving the game to the “Playing” state. Currently, it looks at the KeyboardState object and moves to the playing state if anything changes. We’ll refactor slightly and add a check for a tap gesture:

            bool startGame = false;
#if WINDOWS_PHONE
            if(TouchPanel.IsGestureAvailable && TouchPanel.ReadGesture().GestureType == GestureType.Tap)
                startGame = true;
#else
            var ks = Keyboard.GetState();
            if(_previousKS.IsKeyDown(Keys.Enter) && ks.IsKeyUp(Keys.Enter))
                startGame = true;
            _previousKS = ks;
#endif
            if(startGame)
                AlienRaidGame.Current.GotoState(GameState.InPlay);

Some more refactoring

We’ll need to do some more refactoring to iron things out. First, the text captions are geared towards keyboard users, such as “Press ENTER to play”. We’ll add a Constants class that would hold all relevant strings based on the platform:

    enum GameStrings {
        StartGame, GameOver
    }

    static class Constants {
        private static readonly string[] Strings = {
#if WINDOWS_PHONE
            "Touch to play",
            "Game Over. Tap to play again"
#else
            "Press ENTER to Play",
            "Game Over. Press ENTER to play again, ESC to quit"
#endif
        };

        public static string GameString(GameStrings str) {
            return Strings[(int)str];
        }
    }

Now we can select the appropriate string based on an enum. Also, I’ve added a method that centers a string horizontally. This helps with formatting such messages.

Now the screen looks like so when the game executes:

image

Tapping the screen allows the game to actually run:

image

The player’s ship doesn’t actually show at first, as its display is hard coded for a 600 pixel height instead of 480, so I changed that.

Moving & Firing

The next order of business is moving the ship and firing shots. As before, the keyboard cannot be used and the touch panel must be used instead.

How would we utilize the touch panel? It’s not a trivial question – the input method should be as intuitive and responsive as possible. As a first try, I’ve elected to go with tapping to fire shots, and a horizontal drag to move the ship left / right.

The changes needed are in the PlayerComponent.Update method as follows:

            if(_player.Active) {
                bool right = false, left = false, fire = false;
                float speed = 5.0f;
#if WINDOWS_PHONE
                if(TouchPanel.IsGestureAvailable) {
                    var gesture = TouchPanel.ReadGesture();
                    switch(gesture.GestureType) {
                        case GestureType.HorizontalDrag:
                            var delta = gesture.Delta.X;
                            if(delta > 0)
                                right = true;
                            else if(delta < 0)
                                left = true;
                            speed = Math.Min(20, Math.Abs(delta));
                            break;

                        case GestureType.Tap:
                            fire = true;
                            break;
                    }
                }
#else
                var ks = Keyboard.GetState();
                right = ks.IsKeyDown(Keys.Right);
                left = ks.IsKeyDown(Keys.Left);
                fire = ks.IsKeyDown(Keys.Space);
#endif
                if(right)
                    _player.Position.X += speed;
                else if(left)
                    _player.Position.X -= speed;

The code uses the TouchPanel’s IsGestureAvailable and then ReadGesture to get the gesture details for movement. The Delta property indicates the amount of “finger drag”. This is used here to find out the direction of drag and its intensity.

Dealing with firing is much simpler, as a simple “Tap” gesture indicates the wish to fire.

When the Game is Over

When the game is over, a game over component takes over, and allows playing again or pressing escape to exit. Naturally, no ESC can be pressed. Also, in WP7 you can’t actually “exit” an application (although XNA provides a Game.Exit method). To quit, the user should use the hardware “back” button and the game effectively exits (or press the hardware “Start” button). Another option (not recommended…) is for the game code to throw an unhandled exception…

So, we do the same thing we did when the game begins – add touch lookout for “Tap”.

image

            bool restart = false;
#if WINDOWS_PHONE
            restart = TouchPanel.IsGestureAvailable && TouchPanel.ReadGesture().GestureType == GestureType.Tap;
#else
            var ks = Keyboard.GetState();
            restart = _previousKS.IsKeyDown(Keys.Enter) && ks.IsKeyUp(Keys.Enter);
            _previousKS = ks;
#endif
            if(restart) {
                AlienRaidGame.Current.StartNewGame();
                AlienRaidGame.Current.GotoState(GameState.InPlay);
            }

That’s it! We have a game ported to Windows Phone 7!

Porting an XNA game from Windows to Windows Phone (Part 1)

Published at Aug 08 2011, 09:58 PM by pavely

XNA is one of the two APIs that’s supported on Windows Phone 7 (the other being Silverlight), so a natural thing to do is to port an already existing XNA game (running on Windows or XBOX 360) to the new Windows Phone 7 platform. I wanted to see how easy (or maybe not so easy) it would be to do the actual porting.

My starting point is a relatively simple 2D game, the one developed throughout the XNA 2D tutorial I did a some months back. We’ll start with the final game project (discussed in the last part in that series), and try to do as little work as possible to make it run on a Windows Phone 7 device.

Let’s begin!

A typical XNA project has 2 actual Visual Studio projects: one is the game code we write and the other one contains the game “assets”: textures, audio files, fonts, models, etc. This is more efficient and sharable than using a single project for everything (which was the case with pervious XNA versions prior to version 4).

The first step (after opening the solution in Visual Studio) is to “copy” the project with the actual code to the Windows Phone 7 platform settings. Fortunately, this is easy to do in Visual Studio, by right-clicking the project and selecting “Create Copy of Project for Windows Phone 7”:

image

(this actually works for converting to XBOX 360, as can be seen from the image).

The result is a third project, that uses the same source files as the original. Note that these files are not copies, but the actual files (it’s a bit confusing as there is no “shortcut” icon on those files). This means any change I make would also affect the original game project, so some care is necessary. The new project defines the symbol WINDOWS_PHONE, that can be used to distinguish phone specific code using #if blocks, much the same way as it’s done with XBOX 360 vs. Windows (XBOX symbol is defined):

image

One example is the Main method. In WP7 XNA, it has no purpose. XNA creates an instance of the first Game-derived class it finds. So, Main is unnecessary (but not harmful, as it’s just a method). It’s automatically excluded:

namespace AlienRaid {
#if WINDOWS || XBOX
    static class Program {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static void Main(string[] args) {
            using(AlienRaidGame game = new AlienRaidGame()) {
                game.Run();
            }
        }
    }
#endif
}

Let’s Build!

Now let’s build the project. Unfortunately, we get some compilation errors. It turns out, that the Windows Phone SDK uses the C# 3.0 compiler, so that C# 4.0 features are unavailable. I’ve used optional parameters in the original game, so they don’t compile. We have to remove those and return to the “classic” pre-C# 4.0 way of overloading methods/constructors with fewer arguments, that call the primary method/constructor. Adding #if/#else/#endif for this feature seems overkill.

Now we can finally run the new project. The emulator starts up, and after rotating it sideways, it looks like this:

SNAGHTML46649000

It may be difficult to see, but the starfield does not occupy the entire screen space. Perhaps this is due to the different resolution of Windows Phone. WP7 mandates a resolution of 480x800 (technically, 480x320 may also be supported, but no current device has this resolution). The original game uses 800x600, so the ratio is different (4/3 in the original game, 5/3 in WP7).

Let’s change that:

#if WINDOWS_PHONE
            graphics.PreferredBackBufferHeight = 480;
            graphics.PreferredBackBufferWidth = 800;
#else
            graphics.PreferredBackBufferHeight = 600;
            graphics.PreferredBackBufferWidth = 800;
#endif

If we run it now, we strangely see no noticeable change!

This seems weird indeed, so let’s break with the debugger in the code where a new star is born:

if((star.Position.Y += star.Velocity.Y) > height) {
    // "generate" a new star
    star.Position = new Vector2(_rnd.Next(Game.Window.ClientBounds.Width), -_rnd.Next(20));
    star.Velocity.Y = (float)_rnd.NextDouble() * 5 + 2;
    star.Color = new Color(_rnd.Next(256), _rnd.Next(256), _rnd.Next(256), 128);
}

Looking at Game.Window.ClientBounds.Width, we find that it’s equal to 480 and Height is equal to 728! (Where did 728 came from? we’ll discuss this shortly. For now pretend it returns 800). Just the other way around!

So maybe we should just switch the values:

#if WINDOWS_PHONE
            graphics.PreferredBackBufferHeight = 800;
            graphics.PreferredBackBufferWidth = 480;
#else

The result is less that satisfying. The phone seems to be “stuck” in portrait mode, and rotating it (the emulator or the phone) seems to make the stars go sideways… oops.

SNAGHTML674e30

So, how can we solve that? The previous state was definitely better. Maybe we should revert to that and just set the correct width and height based on the platform and not the Game.Window.ClientBounds property.

Let’s add two properties, WindowWidth and WindowHeight to the AlienRaidGame class and initialize them based on platform:

public int WindowWidth { get; private set; }
public int WindowHeight { get; private set; }

This is done when setting the preferred width & height:

#if WINDOWS_PHONE
            graphics.PreferredBackBufferHeight = WindowHeight = 480;
            graphics.PreferredBackBufferWidth = WindowWidth = 800;
#else
            graphics.PreferredBackBufferHeight = WindowHeight = 600;
            graphics.PreferredBackBufferWidth = WindowWidth = 800;
#endif

Now we need to replace all occurrences of Game.Window.ClientBounds.Width/Height with those properties. A few solution wide search & replace does the trick. Now it looks much better:

SNAGHTMLad5228

By the way, changing the GraphicsDeviceManager.SupportedOrientations property does not help with this issue.

Now, where did that 728 came from? Why not 800? Where did that 72 go?

The 72 pixels are reserved for the “status” of WP7, which shows the battery state, wi-fi connectivity, etc. If you want to take that space as well, just set the game to “full screen mode”. This is what it means in the context of the Windows Phone device:

graphics.IsFullScreen = true;

This gives the entire 800 pixels at your disposal.

What’s Next?

Now that the game seems “oriented”, let’s play. But, wait… how do we do that? The screen says “Press ENTER to play”. Oops, no ENTER on Windows Phone… no real keyboard.

We need to change that. But that will be left for the next part.

WPF Tip: Using a MouseDoubleClick event where none exists

Published at Aug 03 2011, 10:46 AM by pavely

Consider the following simple piece of XAML:

       <Border Margin="10" BorderBrush="Black" BorderThickness="2">
            <Grid Margin="4">
                <Rectangle Fill="Red" />
                <TextBlock Text="Hello" FontSize="15" />
            </Grid>
        </Border
>

Now suppose we want to handle a double click event of that border element (and anything inside it). Searching for a MouseDoubleClick event on a Border reveals there isn’t any.

WPF actually defines such an event on the Control class. That means non-Control derived types, such as Border, all panels, all shapes and TextBlock, don’t have it. So how can we handle a mouse double click if we need it?

We could handle a normal mouse down and up events, measure the time between such successive events, calculate,… too complicated for such a seemingly simple task.

Here’s an alternative: use a ContentControl. A ContentControl derives from Control and has no special formatting on its own. Here’s a revised markup:

<ContentControl MouseDoubleClick="OnDoubleClick">
    <Border Margin="10" BorderBrush="Black" BorderThickness="2">
        <Grid Margin="4">
            <Rectangle Fill="Red" />
            <TextBlock Text="Hello" FontSize="15" />
        </Grid>
    </Border
>
</
ContentControl
>

Visually, it looks the same, but now the MouseDoubleClick event is available.