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.
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.
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:
C# |
---|
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:
C# |
---|
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:
C# |
---|
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:
C# |
---|
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:
C# |
---|
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:
C# |
---|
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:
C# |
---|
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:
C# |
---|
//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:
C# |
---|
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:
C# |
---|
using Microsoft.Xna.Framework.Input; |
Locate the Level.Update
method definition and modify it to match
the following:
C# |
---|
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:
C# |
---|
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:
C# |
---|
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:
C# |
---|
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.
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:
C# |
---|
Vector2 screenOffset = new Vector2(16, 80); |
Modify the existing SpriteBatch.Draw
call by adding the
screenOffset
vector value to the second parameter:
C# |
---|
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:
C# |
---|
public void Update(GameTime gameTime, AccelerometerState accelState, TouchCollection touchState) |
In the same method, modify the first line of code to match the following:
C# |
---|
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:
C# |
---|
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:
C# |
---|
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:
C# |
---|
// 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:
C# |
---|
Vector2 screenOffset = new Vector2(16, 80); |
Modify the existing sprite.Draw
call to match the following:
C# |
---|
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:
C# |
---|
Vector2 screenOffset = new Vector2(16, 80); |
Modify the last line of code in the method (the Draw
method call)
to match the following:
C# |
---|
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:
C# |
---|
Vector2 screenOffset = new Vector2(16, 80); |
Modify the Draw
method call to match the following:
C# |
---|
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.