Export (0) Print
Expand All

Platformer: Adding Touch Support

XNA Game Studio 3.1
Extends the base Platformer starter kit code by adding touch screen and accelerometer support for input.

This extension modifies five areas of the Platformer starter kit. The following steps assume that you have generated a new Platformer starter kit project and it is loaded into XNA Game Studio.

Ee515063.note(en-us,XNAGameStudio.31).gifTip

To prevent build warnings and errors, it is recommended that you remove both the Windows and Xbox 360 versions of the project from the newly-generated Platformer solution. It is also recommended that you set the solution platform to Zune.

With these changes in place, you can now build and debug the starter kit without misleading errors and warnings.

Adding touch screen and accelerometer support involves the following major steps:

  • Modifying PlatformerGame.cs to center the gameplay screen and accept touch screen input.
  • Modifying Level.cs to support input from the accelerometer and touch panel.
  • Modifying Player.cs to control the player character using input from the accelerometer and touch screen.
  • Modifying Enemy.cs to offset the enemy location. This accounts for the overall offsetting of the gameplay screen required by the higher resolution of a Zune HD device.
  • Modifying Gem.cs to offset the gem location. This accounts for the overall offsetting of the gameplay screen required by the higher resolution of a Zune HD device.

For more information on touch screen and accelerometer support, see Zune HD Input Overview.

Modifying PlatformerGame.cs

The original Platformer starter kit was designed for Zune devices with a resolution of 240 x 320. However, the Zune HD device supports a higher resolution: 272 x 480. If you were to install an unmodified version of Platformer on the new Zune, two things would be obvious immediately:

  • The gameplay screen does not fill the entire screen. The right edge of the level is not flush with the screen edge, and a large portion of the default cornflower blue screen is visible on the bottom.
  • There is no way to control the player character. Unlike the earlier Zune device, the Zune pad is gone and there is no dedicated Back button.

The first set of modifications you'll make addresses the higher resolution issue. First, the preferred back buffer dimensions will be increased, and then an offset will be used to shift the gameplay screen over and down. This offset centers gameplay much like letterboxing centers a 4:3 image on a widescreen television. After the modification, you will see thin black bars (on the sides) and thick black bars (on the top and bottom).

Open the PlatformerGame.cs file and, near the top of the file, modify the width and height of the back buffer to match the following:

private const int BackBufferWidth = 272;
private const int BackBufferHeight = 480;

The next modification changes the default cornflower blue screen color to black. This supports the illusion of letter boxing. In the Draw method, modify the Clear method call to match the following:

graphics.GraphicsDevice.Clear(Color.Black);

The final modification uses a 2D vector to offset the text drawn on the gameplay screen. In the same file, locate the DrawHud method and declare a new Vector2 object with a horizontal and vertical offset at the top of the method:

Vector2 screenOffset = new Vector2(16, 80);

This vector is added to the current position of any shadowed text, causing the text to be drawn farther to the right (half the difference between the old and new screen width) and farther down (half the difference between the old and new screen height). Let's modify those placements now. In the same method, find the two calls to DrawShadowedString. Modify the third parameter (hudLocation) by adding the new offset vector to the current value. The code below is the result of modifying the second call:

DrawShadowedString(hudFont, "SCORE: " + level.Score.ToString(),
        hudLocation + screenOffset
        + new Vector2(0.0f, timeHeight * 1.2f), Color.Yellow);

That completes the modifications needed to center the gameplay screen on the Zune device. The next modifications focus on adding touch screen support for responding to game status messages.

First, let's add support for the state of the device's touch screen and accelerometer. After adding the back buffer constants, insert the following code:

AccelerometerState accelState;
TouchCollection touchState;

These states are used mainly by the PlatformerGame and Player objects. Due to the nature of the touch screen and accelerometer states, you should only retrieve these device states once per frame. Then you pass the current state value (either accelState or touchState) to other objects that need input from those devices. It is not recommended that you call either of the GetState methods more than once per frame as different values will result.

In the same file, locate the Update method, and modify the call to Level.Update to pass the states of the accelerometer and touch screen to the Level object:

level.Update(gameTime, accelState, touchState);

Now that the device states have been passed on, you'll add new code to the HandleInput method that responds to user touches on the screen when a status overlay message is displayed (game win or loss). The first step is to retrieve the states of the touch screen and accelerometer with calls to the GetState method. Add the following code after the existing GetState calls:

accelState = Accelerometer.GetState();
touchState = TouchPanel.GetState();
bool buttonTouched = false;

The next modification looks at the collection of touch locations and checks each location for a TouchLocation.Pressed state. If one is found, the buttonTouched variable is updated to true. The code used for this check is fairly common when querying the state of the current touch locations. For demonstration purposes, the three major states are checked, but the code only reacts to screen presses. For more information on using states, see Zune HD Input Overview. Add the following code after the check for the exit condition:

//interpert touch screen presses
foreach (TouchLocation location in touchState)
{
    switch (location.State)
    {
        case TouchLocationState.Pressed:
            buttonTouched = true;
            break;
        case TouchLocationState.Moved:
            break;
        case TouchLocationState.Released:
            break;
    }
}

Now that the buttonTouched variable holds the correct value, modify the continuePressed assignment to also use this value:

bool continuePressed =
        keyboardState.IsKeyDown(Keys.Space) ||
        gamepadState.IsButtonDown(ContinueButton) || buttonTouched;

At this point, some of the gameplay screen elements are offset properly, and the game responds to touches when a status overlay message is displayed. It's now time to move on to the modification of the Level.cs file.

Modifying Level.cs

Modifications in this file consist mainly of applying position offsets to some elements. In addition, you'll update the Level.Update definition and pass those state values onto the Player object. That's the object that will mainly use these values.

First, add the Microsoft.Xna.Framework.Input namespace to the list of namespaces at the top of the file. This provides quick access to the new input-related types:

using Microsoft.Xna.Framework.Input;

Locate the Level.Update method definition and modify it to match the following:

public void Update(GameTime gameTime, AccelerometerState accelState, TouchCollection touchState)

Further down in that method, find the call to Player.Update and modify it to match the following:

Player.Update(gameTime, accelState, touchState);

That completes the state-related modifications. The remaining changes offset more screen elements to further improve the letterbox appearance of the gameplay screen.

Locate the Draw method and add a screen offset vector declaration to the beginning of the function:

Vector2 screenOffset = new Vector2(0, 80);

You can now use this offset to move the backgrounds to the center of the screen. Modify both SpriteBatch.Draw calls by adding the screenOffset vector value to the second parameter:

spriteBatch.Draw(layers[i], Vector2.Zero + screenOffset,
    Color.White);

The next set of modifications are to the DrawTiles method, located after the Draw method. There is a slight difference between the screen offset vector used earlier. The vector used in the DrawTiles method also shifts the tiles to the right, centering them in the screen. This is necessary because the maps are slightly thinner than the width of the display.

Ee515063.note(en-us,XNAGameStudio.31).gifTip
The background textures, used by the Zune version of Platformer, have always been wider than the actual screen of the Zune device. Therefore, only the first portion of each background texture was seen even though the entire texture was loaded by the application. To achieve a solid edge to the gameplay screen (both tiles and background centered on the screen) you could modify the background layers of the LowResolutionContent project by specifying a width of 320 for each background layer. This is the original width, which matches the layout of the level tiles. However, if you do this, you will also have to change the value of the screenOffset vector used in the Level.Update method as follows: new Vector2(16, 80);.

Add a screen offset vector declaration to the beginning of the function:

Vector2 screenOffset = new Vector2(16, 80);

Modify the existing SpriteBatch.Draw call by adding the screenOffset vector value to the second parameter:

spriteBatch.Draw(texture, position + screenOffset,
                    Color.White);

At this point, the project doesn't compile (due to the Player.Update change), but the next step fixes that.

Modifying Player.cs

This is the main modification for the project. In this step, you'll modify the input code of the player character so that it responds to accelerometer and touch screen inputs. There are many ways to modify the original control schema. This extension takes the simple approach and uses a combination of both accelerometer and touch screen inputs. The player character is controlled by tilting the device. Tilt it to the left and the player character runs to the left edge of the screen; tilt it to the right and he runs to the right edge. Due to the sensitivity of the accelerometer there is a built-in dead zone that makes it easier to prevent the player character from constantly running back and forth. This can be modified to suit your individual preference. You could even expose this to the player as a customization feature. Finally, tapping the screen causes him to jump. The jump is determined by his velocity and current direction.

Open the Player.cs file and locate the Update method. You'll need to modify the declaration to match the call made in the Level.cs file. It should match the following:

public void Update(GameTime gameTime, AccelerometerState accelState,
    TouchCollection touchState)

In the same method, modify the first line of code to match the following:

GetInput(accelState, touchState);

This passes the current states of the accelerometer and touch screen so that the player character position and jumping state are properly updated. The next modification updates the GetInput method signature to match this new call, and it adds new code that controls the player character through touch and the tilt of the device.

Modify the first line of the GetInput method to match the following:

private void GetInput(AccelerometerState accelState,
    TouchCollection touchState)

In the method body, find the location of the following comment: // Check if the player wants to jump. . Just before this comment, insert the following code:

if (Math.Abs(accelState.Acceleration.X) > 0.10f)
{
    if (accelState.Acceleration.X > 0.0f)
        movement = 1.0f;
    else
        movement = -1.0f;
}

//override digital if touch input is found
// Process touch locations.
bool touchJump = false;
foreach (TouchLocation location in touchState)
{
    switch (location.State)
    {
        case TouchLocationState.Pressed:
            touchJump = true;
            break;
        case TouchLocationState.Moved:
            break;
        case TouchLocationState.Released:
            break;
    }
}

This code does two things: checks for accelerometer changes and for presses on the touch screen.

Player movement is controlled by the side-to-side tilt of the device. Tilting the device to the left turns and runs the character in that direction. Tilting to the right turns and moves the character to the right. Due to the sensitivity of the accelerometer it is necessary to code in a "dead zone" for the accelerometer input. The tilt value, along the x-axis, must be greater than 0.1 for the character to begin running. This allows small movement of the device along the x-axis without moving the character. If the acceleration value exceeds the threshold, the movement variable is updated with the proper value (positive for left movement, negative for right).

After the movement is determined, the touch screen is checked for any presses that occurred during the current frame. If any are found, the touchJump variable is set to true.

The final modification to this method is to add the touchJump value to the calculation of the isJumping variable. Modify the existing assignment to match the following:

// Check if the player wants to jump.
isJumping =
    gamePadState.IsButtonDown(JumpButton) ||
    keyboardState.IsKeyDown(Keys.Space) ||
    keyboardState.IsKeyDown(Keys.Up) ||
    keyboardState.IsKeyDown(Keys.W) ||
    touchJump;

There is one more area to modify before you're done with this file: rendering of the player character. You can do this by modifying the existing Player.Draw method.

As with earlier modifications to the placement of game play elements, the player character also is offset using vector addition. The modifications are similar to those done in the past. Locate the Draw method, and add the following code to the beginning of the method:

Vector2 screenOffset = new Vector2(16, 80);

Modify the existing sprite.Draw call to match the following:

sprite.Draw(gameTime, spriteBatch, Position + screenOffset, flip);

This completes the Player.cs modifications. The remaining modifications finish the letterbox effect of the gameplay screen by shifting the enemies and gems in the current level.

Modifying Enemy.cs

In this file, the only code you need to modify is the Enemy.Draw method. Open the Enemy.cs file, and locate the Draw method. At the beginning of the method, add the following code:

Vector2 screenOffset = new Vector2(16, 80);

Modify the last line of code in the method (the Draw method call) to match the following:

sprite.Draw(gameTime, spriteBatch, Position + screenOffset, flip);

After modification, the enemy sprites are properly shifted on the gameplay screen to match the other game elements.

Modifying Gem.cs

As with the Enemy.cs file, the only modification being made is to the Gem.Draw method. Open the Gem.cs file and locate the Draw method. At the beginning of the method, add the following code:

Vector2 screenOffset = new Vector2(16, 80);

Modify the Draw method call to match the following:

spriteBatch.Draw(texture, Position + screenOffset, null, Color,
        0.0f, origin, 1.0f, SpriteEffects.None, 0.0f);

After modification, the gem sprites are properly shifted on the gameplay screen to match the other game elements.

Community Additions

ADD
Show:
© 2014 Microsoft