Porting Moto Trial Racer to Windows Phone 7 XNA
Originally Qt Quick based Moto Trial Racer was ported to Windows Phone 7 XNA. This article covers the differences between those two implementations from the development point of view.
The look and feel of the game was tried to keep as close to the Qt Quick version as possible but some small changes were made and some new features added.
Porting the design
All the graphics, layouts, and sounds of the original game was directly copied to the XNA version. Roughly said those were the only things that could have been reused. XNA and Qt Quick are totally different frameworks for totally different use cases and they even follow different programming paradigmas, so there was a lot of rewriting to be done.
Menus
QML is a declarative language for defining user interfaces, so creating the menus for the game with it, was pretty easy and straightforward. XNA on the other hand is a game loop -based lower level framework for creating games and other applications which need to update the screen very often. For that reason it was not very fast for creating the menus or other UI related parts, and things got a bit complicated. In Moto Trial racer there was a need for a rectangle with borders and some text in the middle of it. As an example the following code snippets show the implementation of that kind of rectangle using both QML and C#/XNA.
QML:
Rectangle {
x: rectangleX
y: rectangleY
width: rectangleWidth
height: rectangeHeight
color: "transparent"
border {color: "yellow"; width: 2}
Text {
anchors.centerIn: parent
color: "yellow"
text: "Enter your name here"
}
}
C#/XNA:
Texture2D texture;
SpriteFont font;
Vector2 rectanglePosition;
Vector2 textPosition;
String string = "Enter your name here";
...
//In a initialization method
texture = new Texture2D(spriteBatch.GraphicsDevice, rectangleWidth, rectangleHeight);
font = contentManager.Load<SpriteFont>("SpriteFont2");
rectanglePosition = new Vector2(rectangleX, rectangleY);
Vector2 stringMeasurements = font.MeasureString(string);
textPosition = new Vector2(rectangleX + rectangleWidth * 0.5f - stringMeasurements.X * 0.5f,
rectangleY + rectangleHeight * 0.5f - stringMeasurements.Y * 0.5f);
Color[] colors = new Color[texture.Width * texture.Height];
for (int i = 0; i < colors.Length; i++)
{
if (i < 2 * width || colors.Length - 2 * width < i ||
i % width < 2 || width - 3 < i % width)
colors[i] = Color.Yellow;
else
colors[i] = Color.Transparent;
}
texture.SetData<Color>(colors);
...
//In a drawing method
spriteBatch.Draw(texture, rectanglePosition, Color.White);
spriteBatch.DrawString(font, string, textPosition, Color.Yellow);
As that example shows, the menus/UI needed to be totally rewritten for the XNA version. The first step was to create some often needed basic UI components like a button, and text input. Then a base class called View
was implemented and all the different views which are shown on top of the game itself derives from View
. This structure is very similar to the one in the Qt Quick version.
When those View
based classes were implemented the graphics and their positions were copied from the Qt Quick version. Because of the new levels and the level editor feature also some new views that one can't see in the QML version were needed to be implemented. Such as LevelSelector
, and LevelSaver
.
In the UI there is couple of places where a "flickable" feature was used. This means that the user can flick the view by swiping it up and down (or in some cases left and right). In QML all that was needed to implement this feature was using a Flickable
element and putting all our components that we wanted to be flicked inside it. In XNA this is implemented in the Update
methods of some View
based classes. The position of the touch input is checked every frame and the difference between that position and the position in the last frame is calculated and used as a speed of the flick. That speed is then added to the positions of the flicked components.
Game world
Also all of the graphics related to the game world like the motorcycle, driver, ground, etc. were copied from the original version as they were. But like in the menus the drawing of those graphics differes slightly between QML and XNA. In QML version the Image
element was used for frawing all the images and in XNA the graphics are drawn using the Texture2D
class and SpriteBatch.Draw
method.
QML:
Image {
anchors.centerIn: parent
source: "gfx/wheel.png"
width: 160/1500*screen.width
height: width
smooth: true
}
C#/XNA:
Texture2D texture = content.Load<Texture2D>("wheel");
spriteBatch.Draw(texture,
pos,
null,
Color.White,
body.GetAngle(),
origin,
1.0f,
SpriteEffects.None,
0.0f);
Porting the game physics and mechanics
Physics
In the original version the QML binding for the Box2D physics engine was used. In that QML binding all the "pure" and closely Box2D related code is hidden inside custom QML elements and the abstraction level is raised quite a bit. In practice this ment that a lot of Box2D related code that was needed for Moto Trial Racer was already written beforehand by somebody else. The XNA binding for Box2D on the other hand is much closer to the original C++ version of it. And because of that a lot of physics related work that was given free for the QML version was needed to be done by self in the XNA version.
Here is an example that shows how to add a Box2D box and draw it to the right position and in the right angle using QML and XNA.
QML:
// Inside a World element
Body {
x: boxX
y: boxY
width: boxWidth
height: boxHeight
fixtures: Box {
anchors.fill: parent
density: 0.8
friction: 1.0
restitution: 0.2
}
Image {
anchors.fill: parent
source: "gfx/grass.png"
}
}
C#/XNA:
PolygonShape shape = new PolygonShape();
shape.SetAsBox(boxWidth * 0.5f / Level.FACTOR, boxHeight * 0.5f / Level.FACTOR);
BodyDef bodyDef = new BodyDef();
bodyDef.position = new Vector2(boxX/Level.FACTOR, boxY/Level.FACTOR);
Body body = world.CreateBody(bodyDef);
Texture2D texture = content.Load<Texture2D>("grass");
body.SetUserData(new UserData("grass", texture));
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = shape;
fixtureDef.density = 0.8;
fixtureDef.friction = 1.0;
fixtureDef.restitution = 0.2;
body.CreateFixture(fixtureDef);
// In a updating method
world.Step(0.016666f, 10, 10);
world.ClearForces();
// In a drawing method
Body body = parts[i];
Vector2 pos = body.GetPosition();
pos *= Level.FACTOR;
UserData userData = (UserData)body.GetUserData();
Texture2D texture = userData.texture;
spriteBatch.Draw(texture,
pos,
null,
Color.White,
body.GetAngle(),
origin,
1.0f,
SpriteEffects.None,
0.0f);
Level.FACTOR
in XNA implementation is a constant that is used for mapping the Box2D coordinates to the screen coordinates and vice versa. In QML that is hidden in the plugin and you don't have care about it when declaring the world.
As one can see, in QML just the declaration of the Box2D Body is needed and after that the drawing and updating of the position and angle is handled automatically. In XNA, after we have added the box, we also need to tell the Box2D World to take a step forward in the updating method and in the drawing method ask our Body's position and angle and then draw our texture using those informations.
In QML the screen frame rate is tried to keep at 60 (fps) and the updating of Box2D is done in that same frequency. The time step of the physics engine will then be 0.016 seconds which is the preferred minimum for Box2D. By default the update rate is set to 30 in XNA and that causes a need to set the Box2D time step to twice the preferred value. With those settings the motorcycles suspensions didn't work right at all, so the rate had to be increased to 60.
Game mechanics
The game mechanics are kept as they were in the QML version. When a new game is started the current time is put into the memory and when the motorcycle contacts with the chequered flag the time difference between the current time and the starting time is calculated. The implementation is very similar between QML and XNA and almost only translation from JavaScript to C# was needed. Also in the implementation of the failing there isn't much differences, the program checks if the drivers head or a spike mat is touching anything and if either one is, then the player fails. In QML the contact checking could be done just by binding a handler function to the onBeginContact
event. In XNA it was a little more complicated and we needed to implement a IContactListener
interface and handle the contacts in its functions. Those functions are callback functions for the Box2D and they are called in the middle of physics simulation iterations and for that reason any joints or bodies can't be destroyed directly in those functions. All that could be done in those callback functions is to collect the data of those contacts and then handle the data after the simulation iterations are done. All this is of course done also in the QML version, but we just didn't have to do it by ourselves because it was already implemented in the Box2D QML plugin.
Porting the data storage and audio
Data storage
In the QML version the data storing was implemented using the SQLite database, but in the Windows Phone 7.0 version there isn't any SQLite support yet, so the storing of levels and high scores had to be done differently. It was decided to use the Isolated Storage feature of .NET framework. It offered a possibility to write the high scores and the data of user created levels to text files, and by using the same Isolated Storage the data can also be read. Those Isolated Storage files can only be written by the application, so the predefined levels had to be stored in another way. They are added in the Content
project and from there they are read by using TitleContainer
class.
Audio
The audio playing and real-time manipulation in the original version was implemented using Qt and the audio module of Qt GameEnabler, which is very similar with the one in the XNA framework. The following code snippets show a part of the audio implementation using those two frameworks.
Qt/Qt GameEnabler:
GE::AudioBuffer *buffer = GE::AudioBuffer::loadWav(":/sounds/motor2.wav", this);
m_playInstance = buffer->playWithMixer(*m_mixer);
m_playInstance->setLoopCount(-1);
m_playInstance->setSpeed(0.5f);
m_playInstance->setLeftVolume(0);
m_playInstance->setRightVolume(0);
C#/XNA:
motor = content.Load<SoundEffect>("motor2");
motorInstance = motor.CreateInstance();
motorInstance.IsLooped = true;
motorInstance.Pitch = -0.5f;
motorInstance.Volume = 0.0f;
As the above example shows, a lot of the game's audio part could be reused in the XNA version with just minor changes. In both versions the pitch of the audio is changed depending on the motorcycles rear wheel's angular velocity. The SoundEffectInstance
class in XNA has a Pitch
property which provides a direct way to adjust the pitch (and the playing speed) of the audio. In Qt GameEnabler this can be achieved by using the setSpeed
method. The only difference is that the default pitch (speed) in XNA is 0 and in Qt GameEnabler 1.
Conclusions
The Qt Quick based game Moto Trial Racer was rewritten for Windows Phone 7 using C# and XNA. All the features of the original version and some new ones was implemented successfully. The XNA framework was found to be very different than QML and to present "lower level" as QML, but still to be very useful, easy to adapt, and straightforward to use. XNA was very suitable for implementing the game world itself, but for implementing the UI it suffered from the lack of pre-maid components and caused a need for implementing those by self from the scratch. In the Windows Phone version 7.1 it is possible to use SilverLight along with XNA and it should be considered as the way for implementing the UI, as it provides same kind of possibilities as QML does.