1. Problem
You want to create a
seismograph, by merging into a single project the use of Microsoft XNA
drawing capabilities with the sensitivity of the accelerometer (The
accelerometer has a limited range, from −2 to +2 on the scale of values
on the 3 axis but have a high precision of floating-point numbers beyond
the point.)
2. Solution
You need to use the accelerometer capabilities in association with the XNA framework.
3. How It Works
The use of the
accelerometer in our applications is a standard now. Users require
greater interaction with devices. Think of these devices as trendy
consoles on which users want more control and participation; they are no
longer the old Commodore 64 joysticks. Shakes and finger gestures are
just some of the interactions that users want, in order to feel at the
heart of what they are doing. Therefore it is time to adapt our
applications to keep in sync with the times and provide a richer user
experience. Okay, it's time to get serious. In this example, you will
learn how to access and use the accelerometer in your games, to provide
user interaction. By following the concepts used in these recipes, you
can make high-interaction games, for a better experience for your
gamers. So that we don't lose too much time in explaining what games you
can create and how, you can use your own ideas. So enough with the
talk. Let's start to write code.
4. The Code
This section does not
explain the basics for working with XNA, because doing so would require
another entire book. Instead, it explains what you need to do in order
to read the oscillations of the phone and to turn those oscillations
into a line on the screen. Obviously, that read data can be applied to
every other aspect of any other application that you want to write.
So let's go! As a first step, add
a new SpriteFont in the project of the contents (automatically created
by project template), which with his default content must be good
enough)
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
<Asset Type="Graphics:FontDescription">
<!--
Modify this string to change the font that will be imported.
-->
<FontName>Segoe UI Mono</FontName>
<!--
Size is a float value, measured in points. Modify this value to change
the size of the font.
-->
<Size>14</Size>
<!--
Spacing is a float value, measured in pixels. Modify this value to change
the amount of spacing in between characters.
-->
<Spacing>0</Spacing>
<!--
UseKerning controls the layout of the font. If this value is true, kerning
Information will be used when placing characters.
-->
<UseKerning>true</UseKerning>
<!--
Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
and "Bold, Italic", and are case sensitive.
-->
<Style>Regular</Style>
<!--
CharacterRegions control what letters are available in the font. Every character from Start to End will be built and made available for drawing.
The default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin character set. The characters are ordered according to the Unicode standard.
-->
<CharacterRegions>
<CharacterRegion>
<Start> </Start>
<End>~</End>
</CharacterRegion>
</CharacterRegions>
</Asset>
</XnaContent>
After creating this font, the
other content you need is a 1×1 pixel image that will be the texture of
your line. At this point, there is nothing in the design of Contents, so
go to the heart of the action, to the code in the .cs file of our game.
First of all, remember to resolve Microsoft.Devices.Sensors, and then add (always at the class level) a member of type Accelerometer that will be the data provider. This time you have an available method that is called Initialize, which is the ideal place to do all your initializations:
...
accelerometer = new Accelerometer();
accelerometer.ReadingChanged += new
EventHandler<AccelerometerReadingEventArgs>(accelerometer_ReadingChanged);
accelerometer.Start();
...
In the event handler, the first
thing you do is calculate the magnitude of oscillation, increasing by
100 the scale because, as you remember, the accelerometer is extremely
sensitive but assumes values from −2 to +2 for each axis, that compared
to the screen resolution are little thing:
...
void accelerometer_ReadingChanged(object sender, AccelerometerReadingEventArgs e)
{
double magnitude = Math.Sqrt(Math.Pow(e.X, 2) + Math.Pow(e.Y, 2) + Math.Pow(e.Z, 2))* 100;
}
...
But what you do with this
magnitude after it is calculated? Well, it's clear! It will be the value
of reference for your swing. But before using it to draw, you need to
take a few preliminary steps. First, at the class level, define some
members as follows:
...
VertexBuffer vertexBuffer = null;
Texture2D pointTexture = null;
List<VertexPositionColor> vertices = null;
SpriteFont font = null;
double actualX;
double actualY;
double actualZ;
float maxMagnitude = 0;
float yPosition = 240;
...
The most important members that we need to talk are:
yPosition
that represents the offset relative to the y-axis of the graph.
(Otherwise, you would have a chart that stays too high on the screen.)
vertexBuffer is your "summits" buffer, where you will load the vertices to be drawn.
vertices
is the vertex list that you'll draw. (Keep in mind that you could use
an array for greater consistency with the methods used, but we are more
comfortable working with a list and then converting the array when
necessary.)
The other fields, we would dare call them diagnostic, and you need them to see the onscreen information about the status of the accelerometer as you go forward in the execution.
Next, with all these elements set to null, something in the initialization method must be changed, as follows:
...
vertexBuffer = new VertexBuffer(graphics.GraphicsDevice,
typeof(VertexPositionColor),
1000,
BufferUsage.None);
vertices = new List<VertexPositionColor>();
...
There are still loads of fonts and textures. The best place for them is the LoadContent method:
...
pointTexture = this.Content.Load<Texture2D>("point");
font = this.Content.Load<SpriteFont>("SismoFont");
...
And the code of the event handler becomes somewhat more full-bodied:
...
void accelerometer_ReadingChanged(object sender, AccelerometerReadingEventArgs e)
{
double magnitude = Math.Sqrt(Math.Pow(e.X, 2) + Math.Pow(e.Y, 2) + Math.Pow(e.Z, 2)) * 100;
if (magnitude > maxMagnitude)
maxMagnitude = (float)magnitude;
VertexPositionColor vertex = new VertexPositionColor(new Vector3(0, yPosition +
(float)magnitude, 1), Color.White);
vertices.Add(vertex);
actualX = e.X;
actualY = e.Y;
actualZ = e.Z;
List<VertexPositionColor> newVertices = new List<VertexPositionColor>();
for (int i = 0; i < vertices.Count; i++)
{
VertexPositionColor ver = vertices[i];
ver.Position.X += 1;
newVertices.Add(ver);
}
vertices = newVertices;
if (vertices.Count > 799)
vertices.RemoveAt(0);
}
...
Step by step, what you do is as follows:
Calculate as before the magnitude of the vibration.
Check if it is greater than the event occurred, and if, you keep it in the variable.
Create a new vertex with x = 0, and y = magnitude + (your offset).
Set the fields with the values of the disclosures made by the accelerometer.
Create
a list of the same type of summits, where items pouring of the original
list, increasing with every elements of the value on the x-axis and by
allocating (and replaces) the original list.
With
the last, you ensure that you will never have more vertices needed to
show the information onscreen (in this case, we are not at all
interested in having a history of what happened).
So far you have
calculated the position of the various summits, and this means that now
the vertices are ready to be drawn. As you know, XNA is based on two
methods (Update and Draw) that are invoked cyclically, about 30 times per second. In the Update method, you will prepare the data in the buffer to be draw, while Draw will concretely do work of show vertexes on video:
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//force reading accelerometer data
//accelerometer_ReadingChanged(null, null);
if (vertices.Count > 0 && vertices.Count < 800)
vertexBuffer.SetData<VertexPositionColor>(vertices.ToArray()); ;
base.Update(gameTime);
}
This is not complicated to understand, you are setting data in vertexBuffer taking them from the vertices collection. While things get complicated (but short) in the Draw method:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
if (vertices.Count > 1)
{
for (int i = 0; i < vertices.Count - 1; i++)
{
VertexPositionColor v1 = vertices[i];
VertexPositionColor v2 = vertices[i + 1];
drawLine(
new Vector2(v1.Position.X, v2.Position.X),
new Vector2(v1.Position.Y, v2.Position.Y), v1.Color);
}
spriteBatch.DrawString(font, string.Format("Count: {0}",vertices.Count), new Vector2(20, 20), Color.Red);
spriteBatch.DrawString(font, string.Format("X:{0}", actualX), new Vector2(20, 40), Color.PeachPuff);
spriteBatch.DrawString(font, string.Format("Y:{0}", actualY), new Vector2(20, 60), Color.PeachPuff);
spriteBatch.DrawString(font, string.Format("Z:{0}", actualZ), new Vector2(20, 80), Color.PeachPuff);
spriteBatch.DrawString(font, string.Format("MaxMagnitude:{0}", maxMagnitude), new Vector2(20, 100), Color.PeachPuff);
}
spriteBatch.End();
base.Draw(gameTime);
}
Again, step by step, here is what you do in this method:
If you have more than one vertex, scroll the array of vertices, passing every vertice as a parameter to the drawLine
method, the coordinates of the current point and of the point
immediately after. (That's the reason why you do this if you have more
than one vertex.)
Show on video, thanks to the DrawString method, displaying the information that you have found for diagnostic purposes.
Close the batch data preparation and paint everything.
As you can see we call a method named drawLine, that the code is below
void drawLine(Vector2 v1, Vector2 v2, Color color)
{
float lenght = Vector2.Distance(v1, v2);
spriteBatch.Draw(pointTexture,
new Rectangle((int)v1.X,
(int)v2.X, 1, 1), color);
}
The drawLine
method does nothing more than create many small rectangles (1×1 pixels)
at a position identified by the vectors that constitute your line.
5. Usage
From Visual Studio 2010,
press Ctrl+F5 and wait for the application to be deployed and starts on
your device . Put the phone on your table.Begin to simulate an
earthquake by hitting the table (be careful: don't hit too hard. you
might get hurt).