An accelerometer
is a device contained within the phone that can report the device's
current orientation or position. In other words, it can tell if the
device is lying flat on a desk, being held upright, rotated onto its
side, or is in any position in between. Accelerometers have become
common in mobile devices over the last couple of years and are a
required component of all Windows Phone 7 devices, so they can be used
in games as another interesting input device.
This information presents all sorts of opportunities
for games. If we can tell the angle of the device, we can use it as a
control mechanism. Instead of touching the screen or pressing a button
to move objects on the screen, the player can simply tilt the device in
whatever direction is needed to affect the gameplay.
In this section we will investigate how to read and
interpret the data from the Windows Phone 7 device accelerometer. The
code presented here can be found in the Accelerometer example project,
an image from which is shown in Figure 1.
1. Initializing the Accelerometer
The classes that provide access to the accelerometer
are not actually part of XNA, but are instead provided via one of the
standard Windows Phone 7 libraries: Microsoft.Devices.Sensors. In order to access it, you must first add a reference to it.
Once the reference has been added, we can add a using directive, as shown in Listing 1, to save having to fully qualify the namespace each time we want to refer to the sensor classes.
Example 1. Adding a using directive for the Microsoft.Devices.Sensors namespace
using Microsoft.Devices.Sensors;
|
Next we declare a class-level variable to hold an instance of the Accelerometer object, as shown in Listing 1. This will be created during initialization and will remain for the duration of the game.
Example 2. Declaring a class variable to hold the Accelerometer object instance
private Accelerometer _accelerometer;
|
With these code sections in place, we can now instantiate and initialize the Accelerometer object inside the game's Initialize method. The code required for this is shown in Listing 2. After creating an instance, the code adds an event handler for the accelerometer's ReadingChanged
event. Unlike touch panel and keyboard input, the accelerometer
provides data updates using an event rather than allowing us to poll
it. In the event we can store the most recent reading, however, and
then query this on demand whenever we want, which gives just the same
effect from the perspective of our game code.
Once the object has been created and set up, we call its Start method so that it begins feeding information to us.
Example 2. Instantiating and initializing the accelerometer object
// Instantiate the accelerometer _accelerometer = new Accelerometer(); // Add an event handler _accelerometer.ReadingChanged += AccelerometerReadingChanged; // Start the accelerometer _accelerometer.Start();
|
Finally we need to provide the
AccelerometerReadingChanged event handler that we provided to the
accelerometer object. This is very simple and is shown in Listing 3. The accelerometer provides three values, consisting of a reading for the X, Y, and Z axes. These are stored into a Vector3 structure stored in the class-level variable _accelerometerData.
Example 3. Storing the data from the accelerometer each time it provides an updated reading.
void AccelerometerReadingChanged(object sender, AccelerometerReadingEventArgs e) { AccelerometerData = new Vector3((float)e.X, (float)e.Y, (float)e.Z); }
|
2. Using the Accelerometer Data
We can now read the x, y, and z axis readings from
the accelerometer. Together they provide a reading of the acceleration
of the device relative to freefall. What exactly does that mean?
First, let's look at the vector itself. It contains three properties that can be used to interrogate the device orientation: X, Y, and Z. Each of them is a float
value that represents the movement of the device in the real world in
the appropriate axis. If the device is lying flat and face up on a
table, the values returned for the vector will be approximately as
follows:
X = 0, Y = 0, Z = −1
The value - 1 represents the full force of gravity
applied along the appropriate axis. The x axis represents the direction
between the left and right edges of the device, the y axis the
direction between the top and bottom of the device, and the z axis the
direction between the front and back of the device. As gravity is
pulling the device toward its back while it lies flat on the table, the
accelerometer shows a value of - 1 on the z axis (-1 on this axis
represents the back of the device, whereas +1 represents the front of
the device, and this is what would appear if the device were put face
down on the table).
This z value reading is very useful because it means
we always get a movement reading relative to the force of gravity, even
when the device is not in motion. By working out which of the x, y, and
z axes the reading applies to, we can therefore work out which way up
the device is.
As you've seen, with the device face up, we get a
negative reading on the z axis. With the device upright, the
accelerometer returns a value of - 1 on the y axis (and upright but
upside down, it returns the opposite value, 1). Turn the device on its
side and you'll get a value between - 1 and 1 on the x axis, depending
on which way the device has rotated. All orientations between these
extremes return values spread across the three axes.
Because our screen is only two-dimensional, we can
for the most part ignore the value on the z axis. We can instead read
out the x and y values and apply them as acceleration to objects in the
game. When the device is flat on the desk, x and y are 0, so our
objects don't move at all. Tip the device up, and the x and y values
change based on the tilt angle, providing acceleration for our
objects—the steeper the tilt, the faster the acceleration.
The Accelerometer project in the
accompanying downloads includes all the code required to move a ball
around on the screen under control of the accelerometer. It also
displays the vector values on the screen, so you can easily see the
data coming back from the accelerometer as you change the orientation
of your device.
The project contains a single game object: BallObject, which is mostly just the same as the objects we have looked at in earlier projects. The ball offers a Velocity vector, and in its Update method it adds this to the ball position, bouncing if the edges of the screen are hit.
The one new addition is the use of the accelerometer data within the Update
code. It retrieves the data from the game class, adds the
accelerometer's x axis reading to its horizontal velocity and subtracts
its y axis reading from its vertical velocity, as shown in Listing 4.
This is what makes the ball move in response to the device being
rotated. As you can see, we observe only the x and y axis readings
because the z axis doesn't do anything useful in this 2D environment.
Example 4. Applying the accelerometer data to the ball velocity
// Add the accelerometer vector to the velocity Velocity += new Vector2(_game.AccelerometerData.X, -_game.AccelerometerData.Y);
|
You now have all the code that is required to
simulate a ball rolling around a virtual desktop. Try running the
project on a device and see how the ball reacts to the device being
tilted. Observe the way the ball moves faster when the device is tilted
at a steeper angle.
There are a couple of additional things to be aware
of when using the accelerometer. First, unlike touch input, the
returned vector is not automatically rotated to match the orientation
of the screen. The values will be completely unaffected by rotating the
device, so you will have to compensate for this in your game code if
your game plays in a non-portrait orientation.
Second, if your game has been configured to allow multiple orientations ,
XNA will automatically rotate your display whenever it detects that the
device has been rotated to a different orientation. This might be
useful for non-accelerometer-based games, but if the screen flips
upside down every time the player tries to roll a ball toward the top
of the screen, it will quickly become very annoying. To cure this,
ensure that you explicitly specify a single supported orientation when
working with the accelerometer.