4. Hero Ship Class
We create another class named UserGameObject that inherits from the GameObject class. This class represents the player, which is the Hero Ship in AlienShooter,
which makes it unique to the other classes since it takes user input in
the form of accelerometer and touch. The accelerometer input will move
the ship left or right by tilting the phone. It has a window so that if
the phone is relatively flat the ship does not move. Touching the screen
will fire missiles.
4.1. User Input
By default, the Game Management sample does not include support for Accelerometer input. The GamePlayScreen class has a HandleInput method that takes a parameter of type InputState, which is a class that captures keyboard, controller, and touch input. It does not capture Accelerometer input by default.
Example 2. AccelerometerState Class File
public class AccelerometerState
{
public double X;
public double Y;
public double Z;
public DateTimeOffset Timestamp;
}
|
The modifications to the InputState class are pretty straightforward:
//robcamer - Add a private instance of AccelerometerState
public AccelerometerState CurrentAccelerometerState { get; private set; }
//robcamer - Add private field for the accelerometer
private Accelerometer accelerometer;
The constructor for the InputState class is modified to instantiate the previous two private variables, as well as add an event handler to the Accelerometer:
CurrentAccelerometerState = new AccelerometerState();
//Robcamer - initialize accelerometer
accelerometer = new Accelerometer();
accelerometer.ReadingChanged +=
new System.EventHandler<AccelerometerReadingEventArgs>(accelerometer_ReadingChanged);
accelerometer.Start();
The values of CurrentAccelerometerState are updated in the accelerometer_ReadingChanged event handler:
void accelerometer_ReadingChanged(object sender, AccelerometerReadingEventArgs e)
{
CurrentAccelerometerState.X = e.X;
CurrentAccelerometerState.Y = e.Y;
CurrentAccelerometerState.Y = e.Z;
CurrentAccelerometerState.Timestamp = e.Timestamp;
}
4.2. Processing Input
Now that we have the modifications in place for the InputState class, two private fields related to user input are added to the UserGameObject class.
private AccelerometerState _accelerometerState;
private Vector2 _leftRightVector = new Vector2(5, 0);
The _accelerometerState and _leftRightVector fields are modified in the UserGameObject.HandleInput method.
public void HandleInput(InputState input)
{
//Must check for TouchLocationState as wel as Count
//Otherwise, FireMissile will be called twice
//Once for 'Pressed' and once for 'Released'
if ((input.TouchState.Count > 0) &&
input.TouchState[0].State == TouchLocationState.Pressed)
{
FireMissile();
}
_accelerometerState = input.CurrentAccelerometerState;
if (_accelerometerState.X > .1)
{
Velocity = _leftRightVector;
}
if (_accelerometerState.X < -.1)
{
Velocity = -_leftRightVector;
}
//near Zero tilt left or right so
//set velocity to zero
if ((_accelerometerState.X < .1) &&
(_accelerometerState.X > -.1))
Velocity = Vector2.Zero;
}
If the _accelerometerStateX component is greater than .1, a positive or negative velocity is applied to the Velocity vector via the leftRightVector variable in the proper tilt direction. Likewise, if the user holds the phone close to neutral, Velocity is set to zero. If you don't like how it responds, play with the .1 value to see what feels good to you.
The other part of the HandleInput session is to detect if the screen is touched. Each touch fires to TouchState values, one for TouchLocationState.Pressed and one for TouchLocationState.Released. If the screen is touched the FireMissile method is fired.
The UserGameObject class manages a collection of Missile objects since they are closely associated with the hero ship:
public List<MissileGameObject> Missiles;
public int MaxNumberofMissiles;
The Missile related properties are public properties because the GameplayScreen
will need to check collisions between missiles and enemy alien ships.
Also, the number of available missiles could be adjusted dynamically as
part of game play for a "blitz" mode, where the hero ship can fire more
than three at a time as an example. The Missiles collection is instantiated in the UserGameObject constructor:
MaxNumberofMissiles = 3;
Missiles = new List<MissileGameObject>();
for (int i=0;i < MaxNumberofMissiles; i++)
Missiles.Add(new MissileGameObject(loadedTexture,"missile",screenRect));
When the screen is touched, the FireMissile method code searches for an available missile. Only three are available by default, and sets the missile property to Alive and the MissilePosition property to the same Position value for the hero ship / UserGameObject class. Note that once the Missile is set to Alive, it automatically starts moving based on the default logic implemented in the GameObject class.
4.3. UserGameObject Class Core Methods
The UserGameObject class overrides the Update method with custom code to position the spaceship correctly with user input. It also manages the Missiles collection by calling Update for missiles where Alive is true, i.e., they are in flight. Here is the UserGameObject.Update method:
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
if (Position.X < SpriteCenter.X)
Position = new Vector2(SpriteCenter.X,Position.Y);
if (Position.X > (_screenRect.Width - SpriteCenter.X))
Position = new Vector2(_screenRect.Width-SpriteCenter.X,Position.Y);
for (int i = 0; i < MaxNumberofMissiles; i++)
{
if (Missiles[i].Alive == true)
Missiles[i].Update(gameTime);
}
}
The Draw method is overridden as well:
public override void Draw(GameTime gameTime, Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
{
for (int i = 0; i < MaxNumberofMissiles; i++)
{
if (Missiles[i].Alive == true)
Missiles[i].Draw(gameTime, spriteBatch);
}
base.Draw(gameTime, spriteBatch);
}
The Draw method checks to see if each Missile is Alive and then calls Draw for the Missile object. The last item in the UserGameObject class to cover is the LoadContent method. In this method, the ContentManager instance is passed in so that the UserGameObject can make a sound effect when a missile is fired from the hero ship. Here is the method:
public void LoadContent(ContentManager content)
{
_missileLaunchSoundEffect =
content.Load<SoundEffect>("SoundEffects/MissileLaunch");
}
4.4. Sound Effects
XNA Game Studio has very rich audio mixing capabilities to support Dolby quality sound. For our game the SoundEffect class provides a quick and easy way to play audio during a game with the Play method. We add three sound effects to the AlienShooterContent project in the SoundEffects folder:
Explosion.wma
HeroShipDamage.wma
MissileLaunch.wma
One item to note is that I recorded the sounds using
the Windows 7 Recorder tool, which generates a .wma file. When added to
the Content project the XNA Framework automatically chose the Song - XNA Framework Content Processor. This format cannot be played by the SoundEffect class. Simply change the Content Processor to Sound Effect - XNA Framework and the audio plays fine.
Let's now move on to a discussion of the Missile class.