×
Namespaces

Variants
Actions
Revision as of 08:31, 3 July 2013 by hamishwillee (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Windows Phone 8 Games - Migrating from XNA to DirectX

From Nokia Developer Wiki
Jump to: navigation, search

This article covers the entire process of converting an existing Windows Phone 7.5 2D game written in XNA to Windows Phone 8 and DirectX.

Note.pngNote: This article was a winner in the Windows Phone 8 Wiki Competition 2012Q4.

WP Metro Icon Joystick.png
WP Metro Icon XNA.png
WP Metro Icon DirectX.png
WP Metro Icon WP8.png
Article Metadata
Code ExampleTested withCompatibility
Platform(s): Windows Phone 8 and later
Windows Phone 8
Device(s): All Windows Phone 8 devices
Platform Security
Capabilities: Accelerometer
Article
Keywords: XNA, DirectX, Windows Phone 8, WP8
Created: dtimotei (06 Dec 2012)
Updated: dtimotei (16 Dec 2012)
Last edited: hamishwillee (03 Jul 2013)

Contents

Introduction

On Windows Phone 7.x developers can create games in two technologies: XNA and XAML. XNA has been a great success (at least in the Indie Gaming scene) and developers can still run their XNA games on Windows Phone 8. However developers cannot use XNA to access many of the new features of the platform: NFC, wallet, lenses, Bluetooth APIs etc. In Windows Phone 8 developers are encouraged to port their games to use the DirectX stack.

This article provides detailed instructions on how to port your XNA games to DirectX for Windows Phone 8, using the example of an existing XNA WP7.5 Game. It also provides a brief overview of some of the other alternatives.

Existing solutions

For completeness, we will explore any existing frameworks that help us migrating or creating a game for DirectX and Windows Phone 8, as well as any alternatives we have over using directly DirectX.

MonoGame

Monogame [1] is a cross-platform Open-Source implementation of the XNA library. Basically, (almost) every piece of the XNA framework one might use is forwarded to the proper subsystem (e.g.: DirectX, OpenGL) depending on the underlining system it is compiled for (e.g.: iOS, Android, Windows 8).

One strong point it has is that the migrating is very easy, in the way that you don't have to modify your code (or modify very little), but just recompile the game. However, besides this, there are some downsides as well. There is no "content pipeline" like the one we are used for in XNA. Thus, unless we create a script that invokes the Visual Studio's command line tools to compile the content and copy the output to our game; or have the game completed so we don't have to rebuild the assets while developing the game, this will be a time consuming process. Besides this, according to MonoGame's architecture diagram there are two extra layers between our code and the final subsystem.

Note.pngNote: MonoGame does not support landscape orientation for Windows Phone 8 games; only portrait orientation is supported (March 2013). MonoGame also seems to have difficulty playing various sound-effects and music files that XNA could play on Windows Phone, its gestures support is incomplete, and its gesture detection algorithm exhibits flaky behavior. MonoGame shows a lot of promise but it is not yet a mature product, particularly on Windows Phone 8.

For more details regarding how one would setup MonoGame please visit Bob Familiar's blog. It contains a series of three blog posts that show how to setup MonoGame.

Cocos2d-x

Cocos2d-x [2] is a cross-platform Open-Source framework from the creators of the well-known cocos2d framework for iOS. Just one month ago there was a release that supports Windows Phone 8, and it's good to see cross-platform frameworks like this open their way towards the Windows Phone platform. You can view more details about Cocos2d-x by visiting the Documentation page which contains also guides on how one would create a new game using their framework. Also, there are two articles on this wiki that talk about Cocos2d-x: Creating a New Cocos2d-x Project for Windows Phone 8 and Porting Cocos2d-x Games for Windows Phone 8.

SharpDX

SharpDX [3] is an Open-Source managed framework for using DirectX. You can find more about SharpDX on their features page.

DirectX TK (Toolkit)

There is a collection of helper classes united under the name of DirectX Tool Kit [4] - or in short DirectX TK - started by Shawn Hargreaves. This library contains convenience classes that help us write code that uses Direct X. It also contains a tool called MakeSpriteFont that can create fonts to be used in our game. We will use this in our project.

Prerequisites

For being able to follow this article or run the end result, we will need the following:

Existing XNA codebase

Before starting, let's take a look at what's already available in the codebase. We have two projects: The content project and the source code project.

In the content project we have different types of files that will be good candidates for seeing different types of migrations. We have images in form of sprites, tiles, overlays or backgrounds. Because a game is not complete without sounds, we have of course some sounds to be played in different moments of the game and background music. Last, we have the files that describe how the level is rendered (position of each game object and the type of tiles) in form of text files.

In the code project, except the Player and Level classes, each file contains a fairly small amount of code. The important part of the game is however in those two files that do the "hard work" like collision checking, user-input handling or level generating.

The process

The C++ and DirectX project

We will start by creating a new project of the type Windows Phone Direct3D App (Native only) called Platformer_WP8. Don't worry about the "3D" part. We will strip everything related to that so we'll have a clean project ready for our 2D game.

Creating a new DirectX project

Running the project as it is now, we'll be welcomed with a colored rotating cube. If we look into the project's structure we will see the following files:

  • Platformer_WP8.(cpp|h) - This files are the main entry for our game. Here, the main function - which is the entry point of every C/C++ program - creates a new Direct3D Application Source, which in turn creates our Platformer_WP8 class.
  • CubeRenderer.(cpp|h) - After we had our application created, we use this class to draw something on the display (currently a cube is drawn).
  • Direct3DBase.(cpp|h) - A base class from which our Renderers inherit. This contains some boilerplate code required to setup things like the DirectX device, context and render target.
  • DirectXHelper.h - Contains some utility functions: Throwing an exception if the result from a function failed and another which reads data from a file packaged with the application.
  • BasicTimer.h - Like the name says, we have a class that counts the time.

Now, let's cleanup the project so we can focus on our 2D game.

We will remove the following files: SimplePixelShader.hlsl, SimpleVertexShader.hlsl, CubeRenderer.cpp and CubeRenderer.h. Because the CubeRenderer class doesn't exist anymore, we will create a new class called GameRenderer. As a base class we will use of course, the Direct3DBase class. In the following snippets you can see the new contents of the GameRenderer class's source and header files. The methods are pretty straight-forward. We have the Render and Update methods which are common to every game loop, and the two Create* methods that create the required resources.

GameRenderer.h:

#pragma once
#include "Direct3DBase.h"
 
ref class GameRenderer sealed : public Direct3DBase
{
public:
GameRenderer(void);
 
// Direct3DBase methods.
virtual void CreateDeviceResources() override;
virtual void CreateWindowSizeDependentResources() override;
 
virtual void Render(float timeTotal, float timeDelta) override;
// Method for updating time-dependent objects.
void Update(float timeTotal, float timeDelta);
private:
bool m_loadingComplete;
};

GameRenderer.cpp:

#include "pch.h"
#include "GameRenderer.h"
using namespace DirectX;
using namespace Microsoft::WRL;
using namespace Windows::Foundation;
using namespace Windows::UI::Core;
 
GameRenderer::GameRenderer(void)
{
}
void GameRenderer::CreateDeviceResources()
{
Direct3DBase::CreateDeviceResources();
m_loadingComplete = true;
}
void GameRenderer::CreateWindowSizeDependentResources()
{
Direct3DBase::CreateWindowSizeDependentResources();
}
void GameRenderer::Update(float timeTotal, float timeDelta)
{
}
void GameRenderer::Render(float timeTotal, float timeDelta)
{
const float midnightBlue[] = { 0.098f, 0.098f, 0.439f, 1.000f };
m_d3dContext->ClearRenderTargetView(
m_renderTargetView.Get(),
midnightBlue
);
 
m_d3dContext->ClearDepthStencilView(
m_depthStencilView.Get(),
D3D11_CLEAR_DEPTH,
1.0f,
0
);
 
// Only draw once the resources are loaded (loading is asynchronous).
if (!m_loadingComplete)
{
return;
}
m_d3dContext->OMSetRenderTargets(
1,
m_renderTargetView.GetAddressOf(),
m_depthStencilView.Get()
);
}

After this, we'll have to replace all occurrences of CubeRenderer with GameRenderer', in the application's files. Also, because it may be possible (we will use it for sure in our project) that we want to access the current time inside the Render method. For this, we'll modify the Platformer_WP8.cpp file by replacing line 69 with the following snippet (we transmit the timer's values):

m_renderer->Render(timer->Total, timer->Delta);

Then, inside the Direct3DBase.h file, we change the Render method at line 18 to add the two parameters:

virtual void Render(float timeTotal, float timeDelta) = 0;

If we run our game again, we'll see just the midnight blue background, but our code is ready to be modified for the 2D game. This project will be in almost the same state as a new XNA Game project would be. For convenience you can access the "blank" DirectX game project: File:XNAToDirectX Platformer WP8 blank.zip

DirectX TK

If you loved the XNA SpriteBatch, there's nothing to worry about. DirectX TK contains a class called SpriteBatch which helps us do 2D drawing pretty much in the same way one did in XNA. Let's set it up to be used with our project.

Download the DirectX TK ZIP file from http://directxtk.codeplex.com/releases/view/98986 and extract it inside our Platformer_WP8 solution, besides the Platformer_WP8 project folder. Now, there are two ways of using the library. One would be to open the DirectXTK_WindowsPhone8 solution and build it to obtain a .lib file. This way, we can just move the .lib and header files around and use them in other projects also without having our source code tied with us. The second way - which I will be using - involves just adding the DirectXTK project to our game solution and reference it from our project.

First, we add the project by right-clicking our solution, then choosing Add -> Existing project. Select the DirectXTK_WindowsPhone8.vcxproj file and press OK. Then, right-click the Platformer_WP8 project and select Properties. In the left pane, select Common Properties and then Framework and References. In the right side press the Add new reference... button. In the new window, select Solution -> Projects, and in the right side check the DirectXTK_WindowsPhone8 entry. Press OK when you are done. The steps which we just did can be seen in the following picture. Just to be sure everything is still working fine and the library builds, recompile the solution.

Adding the DirectX TK project reference

Next step is to configure the include files (compilation phase). For this, right-click on the game project and select Properties. In the left pane select Configuration Properties -> C/C++. In the right side edit the Additional Include Directories property by clicking on the combobox arrow, then Edit.... In the top-right toolbar select the first item that looks like a folder. Then press the ... button in the new row and navigate to the Inc folder from DirectXTK's folder. Press OK for all windows to complete this phase.

Adding extra include path to the project

The final thing that has to be done (linking phase), in order to be able to use the DXTK library, is that we need to add the following line in the pch.h file:

#pragma comment( lib, "dxguid.lib")

This is required to link with the DirectX GUID library, which is used by DirectX TK.

Game content

The last configuration step is to migrate our content from the previous game. I will take in part each content type and present what we have to do in order to use it in our DirectX game.

Images

Unfortunately, at the current time there is no way to load directly the .png images we have in the Platformer's content, other than writing ourselves a PNG loader (which I doubt we want) or converting the images into DDS format [5].

Tip.pngTip: The .dds files have a specialized format that make them excellent for being used in games because they can contain a lot of other things besides the graphical representation of the image. This is the preferred format when working with images in DirectX.

For converting images to .dds files there is the DirectXTex library [6] which contains the TextConv utility. Download this specific version: http://directxtex.codeplex.com/SourceControl/changeset/22746 instead of the latest release; it contains an additional option for the texture converter that outputs premultiplied alpha in the .dds files, which will be required later when drawing the images. To compile the texconv.exe file just open the Texconv_Desktop_2012.sln solution with Visual Studio 2012 and build it. The executable can be found in the Debug or Release folder. You can copy the file in the current game's project folder (the one that contains the Platformer_WP8.vcxproj file) so it can be easily accessible.

To convert the .png files we shall create a small script that recursively converts all .png files in the Content folder to .dds. Put the following code in a file called build_dds.cmd, and save the file in the project's folder, alongside with the textconv.exe file.

@echo off
set TEXCONV=%CD%\texconv.exe
cd Content
echo Starting building the DDS files...
SETLOCAL ENABLEDELAYEDEXPANSION
for /r %%i in (*.png) do (
set ddspath=%%~di%%~pi%%~ni.dds
call :do_conversion !ddspath! %%i
)
goto :end
:do_conversion <dds> <png> (
if "%~t1" == "%~t2" goto :return
for /F %%i in ('dir /B /O:D %1 %2') do set newest=%%i
if "%newest%" == "%~n1%~x1" goto :return
echo Converting "%2" to dds...
set ddsoutputpath=%~d1%~p1
if %ddsoutputpath:~-1%==\ set ddsoutputpath=%ddsoutputpath:~0,-1%
%TEXCONV% -ft DDS -m 1 -pmalpha -o "%ddsoutputpath%" "%2"
 
:return
exit /b
)
 
:end
cd ..

A short description of the script: we take all .png files inside from the Content directory recursively. We check if the existing .dds timestamp is equal to the .png file, and also check if the .dds is newer than the .png. If this is the case, the file is not processed. Otherwise, if we don't have a .dds or the .png is newer - it was modified after the .dds was generated - we generate the dds file. This script can be run from Windows Explorer, but we'll make it run at the end of each compilation of our project.

Tip.pngTip: If you prefer, you can use the MSBuild build customization support built in Visual Studio 2012 to do the conversion step. [7] [8]

Fonts

Regarding fonts, we have also to do some processing to generate the wanted fonts. The DirectX TK library contains a tool called MakeSpriteFont. You can build that C# application by launching the DirectXTK_Desktop_2012.sln solution from DirectXTK's source code and compiling it. After you have the binary (located inside the bin/Debug or bin/Release folder), copy it to our game project's folder. You can invoke the application in the following way to generate a file with the font used by our game:

MakeSpriteFont "My Font Name" myfont.spritefont

"My font Name" can be a name just like "Times New Roman" or "Verdana". In our case it will be "Pericles". To simplify things, I have created a new script file, build_fonts.cmd, placed inside project's folder, that will generate the fonts:

MakeSpriteFont.exe "Pericles" Content\Fonts\Hud.spritefont /FontSize:14

Sounds

Sounds are split in two categories: Sound Effects (usually short duration) and Music. Music is a kind of Sound Effect but which plays in background and is much longer. For Music, we can use .wma files; but for sound effects, we need to use .wav files. Now, if you create yourself the sounds, just export them as wav. Otherwise, you can use tools like Audacity (http://audacity.sourceforge.net/) to convert them to .wav. Unfortunately Audacity cannot convert the current .wma files we already have, so we will be using a sample by Microsoft to do this. Download the AudioClip sample from http://go.microsoft.com/fwlink/p/?linkid=163608 and open the AudioClip.sln solution file. We build the binary and copy it inside our project's folder, and use it to convert the .wma files using a script similar with the build_dds.cmd script, with the following contents:

@echo off
SET AUDIOCLIP=%CD%\AudioClip.exe
cd Content
echo Converting .wma to .wav...
SETLOCAL ENABLEDELAYEDEXPANSION
for /r %%i in (*.wma) do (
set wmapath=%%~di%%~pi%%~ni.wma
call :do_conversion !wmapath! %%i
)
goto :end
 
:do_conversion <wav> <wma> (
if "%~t1" == "%~t2" goto :return
for /F %%i in ('dir /B /O:D %1 %2') do set newest=%%i
if "%newest%" == "%~n1%~x1" goto :return
echo Converting "%2" to wav...
%AUDIOCLIP% "%2" "%1"
 
:return
exit /b
)
 
:end
cd ..

Now, after we've settled how we deal with the resources, we copy the Content folder (without any extra files like the content project or the bin/ and obj/ folders with the built resources) to our project's folder. We run then all the three scripts: build_dds.cmd, build_wavs.cmd and build_fonts.cmd.

To make Visual Studio run our dds script after each compilation we do the following: right-click the project and select Properties. We select in the left pane Configuration Properties, Build Events and finally Post-Build Event. In the right side, in the Command Line text box we write: build_dds.cmd and we confirm by pressing OK. After we've run the script once, we can add the .dds, .spritefont, .wav and .wma files to our project using the well-known Add, Existing item workflow. To structure our content, you can add a new filter (Right-click on the project, Add, New Filter) called Content where we store the content files. The filters are just logical ways of organizing data; inside the generated .xap file they will replicate the physical structure. Also, make sure the files you added in the project are marked as Content (If you look at the file properties, Content should be true). This is required so they will be embedded into the final game .xap.

Sprite Batch

To create a SpriteBatch we need to do the following steps:

  • In the GameRenderer.h file we add a new private member to the class:
std::shared_ptr<DirectX::SpriteBatch> spriteBatch;
  • Of course, before using the SpriteBatch, we need to include the header file. For this, we add the following line after the already existing #include directives in the renderer's header file:
#include "SpriteBatch.h"
  • After we declared the variable, we have to initialize it. We will do this at the end of the CreateDeviceResources method:
spriteBatch = std::shared_ptr<SpriteBatch>(new SpriteBatch(m_d3dContext.Get()));

Loading the images

We now have the drawing tool (SpriteBatch), but we still need something to draw. To load the images we'll use the CreateDDSTextureFromFile (from the DDSTextureLoader.h file), and store the texture inside a variable of type ID3D11ShaderResourceView. To ease our life, we'll create a new structure that will hold the information about a .dds file we have loaded from file. There is also a method Description which will return information (e.g.: texture width, texture height) about the resource we loaded in it. The structure will be called Texture2D, and we will add the following at the beginning of the DirectXHelper.h file, after the include statements:

struct Texture2D
{
ID3D11Resource *Resource;
ID3D11ShaderResourceView *ResourceView;
 
D3D11_TEXTURE2D_DESC Description()
{
D3D11_TEXTURE2D_DESC desc;
((ID3D11Texture2D *)Resource)->GetDesc(&desc);
 
return desc;
}
 
Texture2D()
: Resource(NULL)
, ResourceView(NULL)
{
}
};

To proceed with the conversion we'll add 3 new texture fields for the overlays (win, lose and die), in the header file, under the SpriteBatch declaration:

Texture2D winOverlay;
Texture2D loseOverlay;
Texture2D diedOverlay;

After this, we'll drop the loading code inside the CreateDeviceResources method, after the SpriteBatch creation:

DX::ThrowIfFailed(CreateDDSTextureFromFile(m_d3dDevice.Get(), L"Content\\Overlays\\you_died.dds", &diedOverlay.Resource, &diedOverlay.ResourceView));
DX::ThrowIfFailed(CreateDDSTextureFromFile(m_d3dDevice.Get(), L"Content\\Overlays\\you_lose.dds", &loseOverlay.Resource, &loseOverlay.ResourceView));
DX::ThrowIfFailed(CreateDDSTextureFromFile(m_d3dDevice.Get(), L"Content\\Overlays\\you_win.dds", &winOverlay.Resource, &winOverlay.ResourceView));

You can see that there is a L character before the texture strings. That means it's not a standard string (std::string), but a wide string (std::wstring). For now, just to test that the files are loaded fine, run the project. It should still show the midnight blue background. If it starts the game but quickly closes back, you've missed something on the road (look for mistyped paths, or the images not being set as content or not even existing).

Loading the fonts

For the fonts, we'll use the class SpriteFont from the DirectX TK library. We'll need to include first the SpriteFont.h file in our renderer's header file, and then create the private font variable for the HUD text under the SpriteBatch declaration:

#include "SpriteFont.h"
...
std::shared_ptr<DirectX::SpriteFont> hudFont;

Using the same pattern as in the image loading, we add the following line in the CreateDeviceResources method, after the overlay textures creation:

hudFont = std::shared_ptr<SpriteFont>(new SpriteFont(m_d3dDevice.Get(), L"Content\\Fonts\\Hud.spritefont"));

Like before, just to test that the files are loaded fine, run the project. It should still show the midnight blue background.

Loading the sounds

For loading and playing the sounds, we have three choices: the native audio APIs XAudio2 and WASAPI [9]; and Microsoft Media Foundation [10]. XAudio2 and WASAPI are low-level APIs and require uncompressed PCM sounds, so that the sound can be played precisely, without the need for decoding. MediaFoundation can play .wma files, but with one (major) downside: only one file can be played at a time, compared to the previous two ones that can play multiple sounds at a time.

.wav files

Since XAudio2 is a low-level API, we will make use of some already written wrapper classes that make it easy for us to play the sounds. We will take six files from this Microsoft sample: http://code.msdn.microsoft.com/Basic-Audio-Sample-9a5bb0b7. We need XAudio2SoundPlayer.h, XAudio2SoundPlayer.cpp, SoundFileReader.h, SoundFileReader.cpp, RandomAccessReader.h and RandomAccessReader.cpp. Copy those six files into a new folder inside our project, called Helpers. After this, go inside Visual Studio, right click our project, and choose Add, New Filter and call it Helpers. After that, right click the newly created filter Helpers and choose Add, Existing files, and select the newly added files. Then go into the RandomAccessReader.cpp file, and at line 44, replace the uint part with unsigned int - this is required so the code will compile. One more step is to add in the pch.h file the following lines required for compilation and linking:

#pragma comment( lib, "xaudio2.lib")
 
#include <wrl.h>
#include <vector>
#include <memory>
#include <xaudio2.h>
#include <mmreg.h>

Note.pngNote: The code we just added to our project is part of a Windows 8 sample. You can see that most of the code could be ported right away into a Windows Phone 8 project. Yay for shared code!

.wma files

In order to load the .wma sounds, we'll need to write some code that initializes and sets up properly Microsoft Media Foundation. For this, there is also a sample written by Microsoft that creates the wanted wrapper so we can have a much simpler usage in our game: http://code.msdn.microsoft.com/Media-engine-sample-0bd96b86. We will need to use the two files: MediaEnginePlayer.h and MediaEnginePlayer.cpp. Copy those two files inside our Helpers folder, and add them inside Visual Studio like in the previous step. The MediaEngine sample implements the ability to show videos as well, but we won't use that in our project. One final touch to assure that our project compiles fine - by linking with with the MF library - is to add the following line at the end of the pch.h file:

#pragma comment( lib, "mfplat.lib")

Playing the sounds

Good, so we have our infrastructure for using the sounds. Let's do the actual code to load and store them somewhere so we can play them later. We will add our music player (MediaEnginePlayer) inside the GameRenderer class, while the XAudio2SoundPlayer will be globally accessible - because we have to suspend/restore the player when our application is suspended/activated.

Inside GameRenderer.h, add the following include line, and after that we create the private member that will hold the media player, which will be used to play the background music:

#include "Helpers\MediaEnginePlayer.h"
...
std::unique_ptr<MediaEnginePlayer> mediaPlayer;

Inside GameRenderer.cpp, we initialize the member inside GameRenderer 's constructor:

GameRenderer::GameRenderer(void)
{
mediaPlayer = std::unique_ptr<MediaEnginePlayer>(new MediaEnginePlayer);
}

Then, inside the CreateDeviceResources method, we add the following lines before the m_loadingComplete = true; statement:

Platform::String^ music = "\\Content\\Sounds\\Music.wma";
Platform::String^ musicPath = Platform::String::Concat(Windows::ApplicationModel::Package::Current->InstalledLocation->Path, music);
mediaPlayer->Initialize(m_d3dDevice, DXGI_FORMAT_B8G8R8A8_UNORM);
mediaPlayer->SetSource(musicPath);

Now, for the XAudio2SoundPlayer class, there is a little problem. Usually we would put it inside the Platformer_WP8 class, but because it is not a Win RT-compatible class, we either have to make it available just to the non-Win RT classes, or make it be a Win RT-compatible class. We will take the first approach because is simpler, by creating two new files in our project called: Global.h and Global.cpp, with the following contents:

Global.h:

#pragma once
#include "pch.h"
#include "Helpers\XAudio2SoundPlayer.h"
 
namespace Platformer
{
XAudio2SoundPlayer *SharedSoundPlayer();
}

Global.cpp:

#include "pch.h"
#include "Global.h"
 
XAudio2SoundPlayer *Platformer::SharedSoundPlayer()
{
static XAudio2SoundPlayer *player = new XAudio2SoundPlayer(48000);
return player;
}

Now, let's suspend and restore our player. We add the following include in the Platformer_WP8.cpp file:

#include "Global.h"

Inside the OnSuspending() method in the same file, we replace the // Insert your code here comment, with the following:

Platformer::SharedSoundPlayer->Suspend();

Also, inside the OnResuming method, we add the following:

Platformer::SharedSoundPlayer->Resume();

If you compile and run the code so far, the game should play the background music (yay!).

Migrating the classes

Now, the "hard" part is done, we have to convert the classes from C# to C++, and deal with C++-specific idioms. If you are not (that) familiar with C++ there is a nice guide out there [11]. For brevity, I won't list here all the code, but I will list just the particularities or the pieces that require more attention and are not obvious enough. At the end of this article the entire source code for the game will be available for further inspection. One might wonder why I don't write a step-by-step tutorial. I say that letting the developer try to do the conversion for the simpler things, while providing guidance for the harder ones is better.

Tip.pngTip: The first time a game's .cpp or .h file is talked about, it will have a link to its final implementation. You can use that to easily take a look at the implementation.

I will try to implement the classes in the best order as to not depend on very many unwritten classes yet. We start by creating a new filter inside our project to structure our classes. Right click the project, and select Add, New filter and Name it Classes. For each class we'll develop next, we will add a header file and, if needed, a source file inside the filter.

Tile.cs

We create a new header and source file called Tile.h and Tile.cpp. Inside Tile.h we'll create the TileCollision enumeration, inside a new namespace called Platformer. We do this to not pollute the default namespace. Thus, when we want to use an enum member we will write: Platformer::Passable.

namespace Platformer
{
enum TileCollision
{
Passable = 0,
Impassable = 1,
Platform = 2
};
}

The rest of the Tile class can be added after the namespace declaration. The conversion is pretty straight-forward. One point worth mentioning is that instead of the Vector2 class we'll use the DirectX:XMFLOAT structure. Because in C++ we can't have non-integer static const variables (equivalent to const in C#) defined in our class declaration, we have to define the two values separately inside the Tile.cpp file:

const float Tile::Width = 40.0f;
const float Tile::Height = 32.0f;
const DirectX::XMFLOAT2 Tile::Size(Tile::Width, Tile::Height);

Circle.cs

Because we don't have a predefined Rectangle class - and we need it inside the Circle class - we'll create one for us. For this, we create a new file Rectangle.h with a simple definition of the Rectangle class to mimic the XNA's one. The header file will look like this:

#pragma once
#include "pch.h"
 
struct Rectangle
{
public:
Rectangle() {}
Rectangle(int x, int y, int width, int height)
: X(x), Y(y), Width(width), Height(height)
{}
 
int X;
int Y;
int Width;
int Height;
 
int Left() { return X; }
int Right() { return X + Width; }
int Top() { return Y; }
int Bottom() { return Y + Height; }
 
RECT ToRect()
{
RECT rect;
rect.left = Left();
rect.right = Right();
rect.bottom = Bottom();
rect.top = Top();
 
return rect;
}
 
bool Contains(Rectangle other)
{
return other.Left() >= Left() && other.Right() <= Right() &&
other.Top() >= Top() && other.Bottom() <= Bottom();
}
 
bool Contains(DirectX::XMFLOAT2 vector)
{
return vector.x >= Left() && vector.x <= Right() && vector.y >= Top() && vector.y <= Bottom();
}
 
bool Intersects(Rectangle other)
{
return other.Left() < Right() && Left() < other.Right() &&
other.Top() < Bottom() && Top() < other.Bottom();
}
 
// Rectangle extensions
 
DirectX::XMFLOAT2 GetIntersectionDepth(Rectangle rectB)
{
// Calculate half sizes.
float halfWidthA = Width / 2.0f;
float halfHeightA = Height / 2.0f;
float halfWidthB = rectB.Width / 2.0f;
float halfHeightB = rectB.Height / 2.0f;
 
// Calculate centers.
DirectX::XMFLOAT2 centerA = DirectX::XMFLOAT2(Left() + halfWidthA, Top() + halfHeightA);
DirectX::XMFLOAT2 centerB = DirectX::XMFLOAT2(rectB.Left() + halfWidthB, rectB.Top() + halfHeightB);
 
// Calculate current and minimum-non-intersecting distances between centers.
float distanceX = centerA.x - centerB.x;
float distanceY = centerA.y - centerB.y;
float minDistanceX = halfWidthA + halfWidthB;
float minDistanceY = halfHeightA + halfHeightB;
 
// If we are not intersecting at all, return (0, 0).
if (abs(distanceX) >= minDistanceX || abs(distanceY) >= minDistanceY)
return DirectX::XMFLOAT2(0.0f, 0.0f);
 
// Calculate and return intersection depths.
float depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
float depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
return DirectX::XMFLOAT2(depthX, depthY);
}
 
DirectX::XMFLOAT2 GetBottomCenter()
{
return DirectX::XMFLOAT2(X + Width / 2.0f, (float)Bottom());
}
 
DirectX::XMFLOAT2 Center()
{
return DirectX::XMFLOAT2(X + Width / 2.0f, Y + Height / 2.0f);
}
};

We have one small convert function which will be required when specifying a source/destination rectangle used by the SpriteBatch and also some utility functions that ease our coding.

Tip.pngTip: There is an alternative Rectangle implementation in the form of struct Rect from the Windows::Foundation namespace. I have decided it's better to write my own class that uses the DirectX::XMFLOAT2 structure instead of the Point one that is used by the Rect class.

After we've got the Rectangle class, we can proceed writing the Circle class inside a new Circle.h file. The Intersects method will be a bit different regarding the distance calculation. We'll use DirectX Math library for it:

float distanceSquared = DirectX::XMVectorGetX(DirectX::XMVector2LengthSq(direction));

Also, we have to create a Clamp function, which we put at the end of the DirectXHelper.h file:

static float Clamp(float value, float min, float max)
{
return value < min ? min : (value > max ? max : value);
}

Gem.cs

Before getting into converting this class, we need to create the Player, Enemy and Level classes, just to exist so they can be referenced. For this, we create the source and header files (Player.h and Player.cpp) and declare a blank class:

#pragma once
#include "pch.h"
class Player
{
};

And in the .cpp file:

#include "pch.h"
#include "Player.h"

We do the same for the Enemy and Level classes (Enemy.h, Enemy.cpp, Level.h and Level.cpp), but using the Enemy and Level word instead of Player.

Also, we require also another class to gather the game time variables. For this we create the GameTime class with a single GameTime.h file with the following contents:

#pragma once
#include "pch.h"
 
struct GameTime
{
float TotalTime;
float DeltaTime;
 
GameTime()
: TotalTime(0.0f)
, DeltaTime(0.0f)
{
}
GameTime(float totalTime, float deltaTime)
: TotalTime(totalTime)
, DeltaTime(deltaTime)
{
}
};

Finally, the Gem class will be written inside the files Gem.h and Gem.cpp. Here is the first time we use the XAudio2SoundPlayer class. Like I've already said, we are using the global instance of the player, so we don't have to initialize it every time we use it. To add a sound to the player inside the LoadContent we write something like this:

SoundFileReader sound(L"Content\\Sounds\\GemCollected.wav");
collectedSound = soundPlayer->AddSound(sound.GetSoundFormat(), sound.GetSoundData());

You can see we store inside the collectedSound variable the index of the sound we just loaded. We will use that variable to specify which sound the player should play, inside our OnCollected method:

soundPlayer->PlaySound(collectedSound);

One more note: We didn't complete the LoadContent method, since we don't have (yet) a method of getting the D3D Device class required for loading the Gem's texture. We'll come back later after we implement the Level class.

Animation.cs

For this we add just a simple header file Animation.h that will contain the Animation class. The conversion is straight-forward. One thing we do that is apart from the C# class is that we memorize the Width and Height at creation time to optimize a bit the time required to read the frames count/size - since an animation is required to run pretty smooth.

AnimationPlayer.cs

What good is an Animation without playing it :)? We implement the AnimationPlayer in the the AnimationPlayer.h header and AnimationPlayer.cpp source files inside the Classes filter. This class is also straightforward to be implemented.

Accelerometer.cs

One method of input in our game is the Accelerometer. We won't replicate the C# classes, since we don't have any initialization to be done, and the Accelerometer can be read on demand besides using an event-driven approach. We can get the current state of the accelerometer using the following piece of code:

Windows::Devices::Sensors::Accelerometer::GetDefault()->GetCurrentReading()

Level.cs

So far, we've converted small classes. Now it remains the big ones: Level, Enemy and Player. Since the Level class is in the center of the game and many depend on it, we will migrate some small things from it. We will need the Direct 3D device used everywhere to load the content, and also some public properties and methods required by other classes.

An issue is that because the Level class depends on the Enemy, Player and Gem, and they depend on Level on their side too, we can't include the headers for them (would result in cyclic dependency which is not supported in C++). To solve this, we will do some forward declarations of the classes we'll use:

class Gem;
class Enemy;
class Player;

When the actual code is compiled, the compiler will do the right binding on its own.

Enemy.cs

Now, since we have the Level class stub in place and the Enemy's class files, we can proceed creating the rest of the puzzle. The code conversion is pretty easy and straightforward, because there is more math than platform-specific code. The good part is that C++ supports mathematical operations on enumerations, so we could still use an Enumeration for known directions the enemy is going and use that within comparisons to decide what to do next.

Player.cs

An issue we face here, is the missing Math.Round() function. We will put the implementation at the end of the DirectXHelper.h, after the Clamp function. This can be implemented like this, by the rules of math:

static float round(float number)
{
return number < 0.0f ? ceil(number - 0.5f) : floor(number + 0.5f);
}

Basically, what this code does is, if we have a negative number we round it "upwards" to 0, otherwise we round it "downwards" to 0.

In our C++ version we won't have available the XNA States (KeyboardState, GamepadState, etc), so we will use only what we require. In this matter, we will create a new class called TouchState with just a header file TouchState.h in our Clasess filter. This is a simple struct that tells us if a pressed touch event was caught. This is the source for the header file:

#pragma once
#include "pch.h"
value struct TouchState
{
bool IsTouchPressed;
};

One might wonder what's the value part in the front of the structure declaration. That is simply a way of declaring a public WinRT-compatible value struct [12], which can be used inside public fields of a ref class [13] (Our Platformer_WP8 and GameRenderer classes are ref classes).

Level.cs revisited

After we've implemented the whole Player and Enemy classes, we can get back to our Level class.

To hold the tiles, we use a triple pointer (something that might look very weird for new C/C++ developers) as the declaration for the matrix:

Tile ***tiles;

We have one pointer, which means is an array. Thus, we have two arrays of Tile pointers (2 + 1 = 3 pointers). The initialization of the matrix is done this way:

tiles = new Tile**[height];
for(int y = 0; y < height; ++y)
{
tiles[y] = new Tile*[width];
for(int x = 0; x < width; ++x)
{
char tileType = lines[y][x];
tiles[y][x] = LoadTile(tileType, x, y);
}
}

Instead of the TimeSpan class, we will use plain float numbers to represent seconds, since they suffice for our scenarios.

PlatformerGame.cs

And we reached the final class we have to convert. Here we combine all the pieces (classes) we've written so far. Some of the code will go in the GameRenderer.h and GameRenderer.cpp, while some other (e.g.: the check when the user presses on the phone's screen) will go inside the Platformer_WP8.cpp file.

For start, we need to change the Update function definition, to transmit data about the touch and accelerometer, to the GameRenderer The new Update function signature will be:

void Update(float timeTotal, float timeDelta, AccelerometerState accelState, TouchState touchState, Windows::Graphics::Display::DisplayOrientations orientation);

Now, we have to change in the Platformer_WP8.cpp the invocation of the Update method (inside the Platformer::Run() method):

m_renderer->Update(timer->Total, timer->Delta, Accelerometer::GetState(), touchState, DisplayProperties::CurrentOrientation);

The touchState is particularly interesting. It is declared inside the Platformer_WP8.h header file:

TouchState touchState;

And then we set its IsTouchPressed property is via the OnPointerPressed and OnPointerReleased events handlers:

void Platformer_WP8::OnPointerPressed(CoreWindow^ sender, PointerEventArgs^ args)
{
touchState.IsTouchPressed = true;
}
void Platformer_WP8::OnPointerReleased(CoreWindow^ sender, PointerEventArgs^ args)
{
touchState.IsTouchPressed = false;
}

The next "problem" is the loading of the level data from the files. Fortunately, we already have a file reader that is used in conjunction with the XAudio2SoundPlayer. There is also the ReadDataAsync method inside the DirectXHelper.h file, but we won't use that. With that, the loading of the file is straightforward:

// Load the level.
Platform::String^ levelPath = "Content\\Levels\\" + levelIndex + ".txt";
RandomAccessReader fileReader(levelPath);
auto fileData = fileReader.Read(fileReader.GetFileSize());
std::string stringData(reinterpret_cast<char*>(fileData->Data));

We use the reinterpret_cast to view the file data as char* not as unsigned char*, so we can create the std::string which will be forwarded to the Level constructor.

Finishing touches

Accelerometer

Before being able to launch the game, we have to specify in the Windows Phone 8 app package that we want to use the Accelerometer. For this, we double-click the WMAppManifest.xml file from our project. Inside the Capabilities tab we check the ID_CAP_SENSORS. If we don't do this, our game will exit with an exception.

Enabling the accelerometer reading

Screen orientation

Now, if we would start the current game, we would see that the game doesn't look good at all, filling just the left part of the screen:

The screen is not rotated according to the orientation of the phone

This is because DirectX won't automatically rotate the screen as it was done in XNA. We have to do that ourselves using a transform matrix. We start by adding a new method ComputeOrientationMatrix inside the Direct3DBase class. We also need to add three new fields to store the matrix, the current orientation and the current orientation's screen size (we just switch the height and width between themselves).

Direct3DBase.h:

...
void ComputeOrientationMatrix();
 
Windows::Graphics::Display::DisplayOrientations m_orientation;
DirectX::XMMATRIX m_orientationTransform;
DirectX::XMFLOAT2 m_orientationScreenSize;

Direct3DBase.cpp:

void Direct3DBase::ComputeOrientationMatrix()
{
m_orientation = DisplayProperties::CurrentOrientation;
 
switch (m_orientation)
{
case Windows::Graphics::Display::DisplayOrientations::Landscape:
// 90-degree rotation
m_orientationTransform = XMMatrixMultiply(
XMMatrixRotationZ(XM_PIDIV2), // rotation by an angle of PI / 2
XMMatrixTranslation(m_renderTargetSize.Width, 0, 0) // translate it inside our screen
);
m_orientationScreenSize = XMFLOAT2(m_renderTargetSize.Height, m_renderTargetSize.Width);
break;
case Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped:
// 270-degree rotation
m_orientationTransform = XMMatrixMultiply(
XMMatrixRotationZ(3 * XM_PIDIV2), // rotation by an angle of 3 * PI / 2
XMMatrixTranslation(0, m_renderTargetSize.Height, 0) // translate it inside our screen
);
m_orientationScreenSize = XMFLOAT2(m_renderTargetSize.Height, m_renderTargetSize.Width);
break;
}
}

And, the final step is, inside the Render method of our GameRenderer.cpp file, we'll specify the orientation transform matrix by replacing the usual

spriteBatch->Begin();

with

spriteBatch->Begin(DirectX::SpriteSortMode_Deferred, nullptr, nullptr, nullptr, nullptr, nullptr, m_orientationTransform);

Now, the game looks just fine:

The game is rendered the right way

Time step

The XNA game runs in a fixed timestep mode. That means that the game executes at almost fixed intervals of time (usually 30 fps). Here, we don't have that option, but instead variable timestep is used, which means our game will run faster or slower depending on system load and performance. This causes our game to malfunction (e.g.: the player dies as soon as the level starts) because the values that calculate the physics are tweaked for 30fps. The fix for our current game is simple: we change the advertised delta time from the timer, to be exactly 1/30 (0.033333). Thus, inside the Run method in the Platformer_WP8.cpp file, we change the invocations of the Draw() and Update calls to be like this:

m_renderer->Update(timer->Total, 0.03333f, Windows::Devices::Sensors::Accelerometer::GetDefault()->GetCurrentReading(), touchState, DisplayProperties::CurrentOrientation);
m_renderer->Render(timer->Total, 0.03333f);

Iconography

At last, we reached the final point of our game development cycle: the icons for the app. The process is similar to the one used in XNA on Windows Phone 7, but instead of editing the project properties, we open the WMAppManifest.xml file, and we set the right icons and tiles. You can find the Assets folder contents we need to replace in the following archive: File:XNAToDirectX Assets.zip

Design decisions

Some might wonder why I didn't use the new C++/CX properties syntax to make the C++ classes look more like the C# counterparts. Well, if I would have used that, we would had to create Win RT-compatible classes. If we went that road, then a lot of code had to be changed to match all the strict rules [msdn.microsoft.com/en-us/library/windows/apps/hh699870.aspx]. Also, Win RT classes are not so fast as the pure C++ ones. Microsoft advises to try and use native C++ code (e.g.: std::vector<> instead of Platform::Vector<>), and use Win RT-compatible only on ABI (Application Boundary Interfaces).

Cheat sheet

Here is a small table that contains the equivalent classes/structs in C# and C++ used in the porting process (except the ones that have the same name):

C# type C++ type
Vector2 or Point DirectX::XMFLOAT2
Texture2D ID3D11Resource and ID3D11ShaderResourceView
List<> std::list<> or std::vector<>
Random the functions srand() and rand()
Microsoft.Devices.Sensors.Accelerometer Windows::Devices::Sensors::Accelerometer
Color DirectX:FXMVECTOR
TimeSpan float

Conclusion

We had a long road, but it was fun nevertheless. We have seen what is the process of creating a complete game with sounds, images, animations and extra data (levels description with text files) on Windows Phone 8 using DirectX. It is not as smooth or easy like XNA, but the prospects are awesome: we have direct access to the DirectX stack. One thing worth mentioning is the shaders support in Windows Phone 8 - until now we couldn't do such things with XNA.

So now, everyone who wants to see how a DirectX game in 2D is made on Windows Phone 8 can take a look at this sample to get a start, just like the XNA platformer was a nice sample getting started on XNA and Windows Phone 7.

Source code

You can download the full source code of the converted platformer from here: File:XNAToDirectX Platformer WP8.zip. The code is also available on bitbucket at: https://bitbucket.org/timotei21/wp8_platformer.

References

A project this big has lots of source of information to form a complete experience. Below you can find more information about specific topics used in this article.

General C/C++ related

Windows Phone 8 specific

Sound

Other

This page was last modified on 3 July 2013, at 08:31.
1212 page views in the last 30 days.