From XNA to MonoGame

By Dean Ellis

A reprint from the May 2013 issue of Gamaustra's sister publication Game Developer magazine, this article explains how you can transition your XNA projects to MonoGame. Purchase the May 2013 issue here.

For a long time, the idea of making "real" games using managed languages such as C# was considered lunacy. But things have changed, and managed languages are now proving to be quite viable for making games, thanks in large part to XNA and Unity 3D, with both using C# as their main development language or scripting engine. Many new game devs learned to use XNA in order to get their games on the Xbox 360 and Windows PCs, and even though Microsoft recently announced that XNA will neither see any more active development nor be supported in Microsoft's Metro interface, those devs don't have to start over from scratch. Enter MonoGame, an open-source implementation of the XNA 4 API.

What is MonoGame?

MonoGame's project goal was initially to allow XNA developers to publish their games on iPhone, but the project has grown a lot since those humble beginnings. It now supports a number of platforms, including Mac OS X, Linux, Windows 8, Windows Phone 8, Android, and iOS. For the most part, it is free to use (though you'll need to pay for a license for the Xamarin iOS and Android frameworks) and modify, as all the code is covered by the MS-PL (Microsoft Permissive License). Now, MonoGame's goal is first to produce an XNA-compatible cross-platform API, which can be extended to new platforms as they appear. For example, because MonoGame already supported Android, we were able to add Ouya support to MonoGame in a matter of days.

Once the XNA API is stable, we're planning to extend the API with new features that people would have liked to see in XNA, and extend the API to make it even easier to do certain tasks. For most of the team involved, some of whom have been working on MonoGame for a few years now, this is a long-term project. 

Getting Started with MonoGame

First off, head over to www.monogame.net/downloads and grab the latest stable release. At the moment, MonoGame supports Visual Studio and MonoDevelop/Xamarin Studio.

If you want to use Visual Studio: The Windows installer will install project templates for all editions of Visual Studio 2010 and 2012. Particular requirements for your target platform are as follows:

To build content to xnb format, you will also need XNA Game Studio 4.0 or the Windows Phone 7.11 SDK installed. This is a temporary measure until our content pipeline replacement is completed. To install either of these SDKs on Windows 8, you need to have the Windows Games Live client first.

A full list of the prerequisites and download links can be found here.

If you want to use MonoDevelop/Xamarin Studio: MonoDevelop is a free open-source IDE that is available for Windows, Linux, and Mac OS X (www.monodevelop.com). MonoGame is available as a package, which includes the runtime for each platform. You can download this from Codeplex as an .mpack file, and install it via the AddIn Manager within MonoDevelop. Recently, Xamarin has released Xamarin Studio, which is an updated version of MonoDevelop for people wanting to use this IDE. There is also a .mpack file available for that version.

Once you have installed the required package into your chosen IDE, you will be able to create a new MonoGame project. There are a number to choose from, but to get started, you should pick the one that is native to your platform -- for Windows, create a new "MonoGame for Windows GL" application; for Mac OS X, create a "MonoGame for MacOS," and so on. Once you have a new project you will see the familiar Game class that renders the customary CornflowerBlue screen.

Skulls of the Shogun dev 17-Bit used MonoGame to build a Windows 8 version of the game.

Before you start coding, there are a few things you should watch out for. For Windows, all of the templates will work out of the box, but if you want to work on an Android project, you will need Xamarin.Android from Xamarin installed. MonoDevelop/Xamarin Studio does include templates for Mac OS X and iOS, but these will currently work only on an Apple Mac because of the underlying development requirements. For iOS, you'll need to install Xamarin.iOS, and for Mac you'll need to grab MonoMac. Also, due to some issues with the AddIn for MonoDevelop and Xamarin Studio, you will need to download the MonoGame source code and add references to the required MonoGame projects for MacOS, iOS, and Android. (The team is working on getting this fixed.) 


Getting the Code

If you want to get the code for MonoGame, you will need to install git. There are nice user interfaces for git, but most of the team use the command line. So here are a list of the commands you need to get up and running:

git clone git://github.com/mono/MonoGame.git MonoGame

cd MonoGame

git submodule update –init ThirdParty

These commands will checkout a read-only copy of MonoGame and the required third-party library to get you started. If you want to make changes and contribute back, you will need to fork the repository. Details can be found here.

Content and Assets

MonoGame, like XNA, can make use of .xnb compiled content files. These .xnb files are currently created by the XNA content pipeline; as of this writing, the MonoGame team is working on a cross-platform implementation that will work on Windows, Mac OS X, and Linux, but for now we'll need to use the XNA Content Pipeline. Also it is possible to load some native assets through the ContentManager, so if you have a .png file you want to load directly you can use the same code as you would normally:

texture = Content.Load<Texture2D>("character");

MonoGame will attempt to load an .xnb file first, but if one does not exist, it will fall back onto known native types for that type of object. This feature was originally added to help people developing for iOS and Mac who might not have access to a Windows machine to build their content. (See Figure 1 for a table of available formats for class types.)

Figure 1: Available formats for class types.
* - PVRTC compressed only, ** - Must be generated by the MonoGame Content Processor

As you can see from Figure 1, on Android you cannot make use of the .xnb files for SoundEffect or Song types; you must use the native type for that platform. This is due to the limitations of the SoundPool and MediaPlayer classes on Android. (Some of these restrictions will be removed as time goes on and the framework matures.) Also, some of the platforms only support .xnb files for certain types; this is because it is more efficient to use the compiled and optimized content for those types. For example, it's more efficient to pre-process a Model at build time into an optimized format than it is to load an .fbx file at run time and decode it on a mobile device.

Fortunately, the MonoGame framework comes with some tooling that we use to extend the existing XNA content pipeline to produce the content we need -- though since it relies on the XNA framework, this will only work in Windows. You can create a "MonoGame Content Project" within Visual Studio 2010, which will create a normal XNA Content project and an XNA project that will in turn build the content project. Both have all the required references and MSBuild target imports to allow it to use the MonoGame Processors.

Now, when you add a new asset to the content project and select the type of Processor you want to use, you will see a number of MonoGame-related processors: "MonoGame - Effect," "MonoGame - Texture," and so on. These processors will do some custom processing on the asset to optimize it for the platform you are targeting (see Figure 2).

Figure 2: Example of optimized internal .xnb formats

The new Builder project has a number of build configurations for each platform, so if you are building for iPhone you can choose the iOS configuration. If you are building for the Windows Store choose the Windows 8 configuration. The resulting output files will be placed in bin\<Configuration>\Release, so if you are building for multiple platforms, you will end up with a directory for each platform you are targeting. The last step is to add these files to the Content directory of your project -- you can do this easily by using the "Add as Link" feature in Visual Studio and MonoDevelop. This way, you can just link to the file without copying it to the project directory, which is great because if you change your content and compile it using the Content Builder, it will be automatically picked up the next time you compile your application. Note that depending on the platform you are targeting you need to set the BuildAction correctly for your assets file to be included in the final application package. 


Using Custom Effects

If you want to use Effect files from a previous XNA project or an XNA sample, you'll need to process them with the MonoGame Effect processor to compile them for that specific platform. Some of these use OpenGL rather than DirectX as their graphics API, so the Effect file from XNA will need to be converted to OpenGL shader language for it to work.

Rather than have developers rewrite their shaders to GLSL, MonoGame installs some tooling to automatically convert the HLSL in the Effect file to the appropriate shader language for the target platform. For the DirectX-based platforms, MonoGame uses the DirectX 11 tool chain to compile your Effect into a shader optimized for that platform. For the OpenGL-based platforms, the Effect is processed by a tool called MojoShader, which does a low-level conversion from HLSL to GLSL that allows the Effect to work on that platform.

Of course, this conversion process can occasionally introduce errors or unsupported features that the target platform does not support. For example, with OpenGL Shader Model 3.0, you cannot do a texture lookup in a Vertex Shader, so if your Effect uses that feature it probably won't work. Let's take a sample effect from one of the XNA samples available on the Xbox Creators Club website -- the Bloom Extract Effect is a good example . Although it is only a pixel shader, it will give you a good idea of what kind of things we need to look at.

Listing 1: Bloom extract.

// Pixel shader extracts the brighter areas of an image.

// This is the first step in applying a bloom postprocess.

sampler TextureSampler : register(s0);

float BloomThreshold;

float4 PixelShaderFunction(

float2 texCoord : TEXCOORD0

) : COLOR0

{

// Look up the original image color.

float4 c = tex2D(TextureSampler, texCoord);

// Adjust it to keep only values brighter than the specified threshold.

return saturate((c - BloomThreshold) / (1 - BloomThreshold));

}

technique BloomExtract

{

pass Pass1

{

PixelShader = compile ps_2_0 PixelShaderFunction();

}

}

If you look at the code for the effect in Listing 1, one of the first things to note is that this effect is using pixel shader 2.0 (ps_2_0). This is fine for DirectX 9, but for OpenGL and DirectX 11 this needs to change. Before we get into the code changes, it is worth noting that you can use conditional defines within effect files to change the behavior depending on the shader model you are targeting. If you are targeting shader model 4, you can use this:

#if SM4

// code

#endif

to handle that special case. These defines are still valid when using the MonoGame Content processor, so in order to make this shader support all the platforms we want, we need to update the technique section to handle all the platforms you want to support. In this case, we need to support Shader Model 4 for DirectX 11 and Shader Model 3 for OpenGL/GLES. With that in mind, the Pass becomes:

pass Pass1

{

#if SM4

PixelShader = compile ps_4_0_level_9_1 PixelShaderFunction();

#elif SM3

PixelShader = compile ps_3_0 PixelShaderFunction();

#else

PixelShader = compile ps_2_0 PixelShaderFunction();

#endif

}

For each type of Shader Model, we define a different Pixel Shader; you can also do this with Vertex Shaders, so a declaration of vs_2_0 would become vs_3_0 for SM3 and vs_4_0_level_9_1 for SM4.

Next, let's look at the parameters passed into PixelShaderFunction. In the sample effect, it only takes one parameter -- but in order for this effect to work correctly with the SpriteBatch Effect within MonoGame, we'll need to add the additional parameters. (This is a limitation with MonoGame at the moment that will probably be resolved in the future.) The good news is that adding these extra parameters does not break the effect in normal XNA, so in this case, we can just add the missing parameters like so:

void PixelShaderFunction( float3 position: POSITION0, float4 color : COLOR0, float2 texCoord : TEXCOORD0)

Watch out: In XNA 3.1, the PixelShader and VertexShader functions were called PixelShader and VertexShader. This is no longer valid in XNA 4.0 or MonoGame, which is why in Listing 1 we renamed the vertex and pixel shader functions to VertexShaderFunction and PixelShaderFunction. Of course, you can call them anything you like. 

Supergiant Games used a fork of MonoGame code to port Bastion to iPad.

If you apply this technique to all of the effects that are included in the Bloom sample, porting that project over to Windows 8, Android, and iOS simply becomes a matter of using the Content Builder to produce the .xnb files for each platform you are targeting, and then linking to those files for your project.


Tweaking MonoGame for Mobile

So you have a great Xbox 360 or Windows game that you want to port over to a mobile platform. What kind of things do you need to worry about? There are some obvious ones that will come to mind first: screen size, input, and performance. Here's how to handle those factors with MonoGame.

Screen Resolution

With certain platforms, like Windows Phone 7 and iOS, you can almost guarantee the screen sizes of the devices you are going to be working on, but with some devices and platforms it's not so easy. Windows Phone 7/8 support hardware scaling, but with Android, Ouya, Windows 8, and the Retina displays on iPad and Mac, MonoGame developers need to take into account the various resolutions they might come up against. Sometimes you'll find yourself navigating a minefield of screen resolutions ranging from 320x200 all the way up to full HD.

One technique that has been used quite well in the past is to use SpriteBatch scaling. With this method, you can design your game to run at a particular resolution, then at run time figure out a scale matrix that will resize your game to match the actual screen size of the device you are running on. This matrix can then be passed to the SpriteBatch, which will scale your graphics. In the following code, we can see some sample code for calculating the scale matrix; in this case our

virtual resolution is 800x600.

var virtualWidth = 800;

var virtualHeight = 600;

var scale = Matrix.CreateScale(

(float)GraphicsDevice.Viewport.Width / virtualWidth,

(float)GraphicsDevice.Viewport.Height / virtualHeight,

1f);

With that matrix calculated, we can now call spriteBatch with the extra matrix parameter like so:

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, null, null, null, scale);

// draw stuff

spriteBatch.End();

This will apply the scale matrix to the items being drawn in that batch. There are other considerations like aspect ratio and letterboxing that you need to take into account, but there are plenty of resources out there, which were originally written for XNA that can be reused in MonoGame and applied to new platforms. (For more on this topic, read this post.)

At a certain point, however, scaling won't solve your problems. Scale down too much and your graphics will become pixelated; scale up too much and they become blurry. At this point, you need to start looking at the assets. One technique you can use on iOS is have two sets of assets -- one for normal displays, and one for Retina displays. You can do this by adding a @2x suffix to the name of the larger asset and including that asset in your game package. The MonoGame content pipeline has been coded to make use of this trick on iOS, so if you have a raw asset or .xnb file which has a @2x version and you are running on a Retina display device, that higher-resolution asset will get loaded. All the developer needs to do it include those assets in the application. (This does make your app package larger, unfortunately.)

The other option is to have two versions of the game, one for normal and one for HD, each with different scales of assets. This way, people have the choice of downloading a larger high-definition game or the smaller normal one.

Handling Input Devices

Moving between platforms means you need to support various input devices. On the desktop platforms (Windows, Linux, and Mac), MonoGame uses the Simple Direct Media Library (SDL) to interface with the various joysticks and gamepads that might be available. The inputs from these devices are routed through the GamePad class. SDL does seem to have a problem reading the USB description of some of the Xbox 360 controllers, so on occasion it will fail to apply the correct configuration.

Mouse and touchpad input is one area where MonoGame has diverged from the normal XNA. On Windows Phone 7 and 8, if you use the XNA implementation provided by Microsoft, all of your touch events will be routed through to the mouse as well. This can make it very easy to port games over from Windows and Xbox 360 to those platforms. However, with the introduction of Windows 8, you now have devices which can have a mouse and a touch screen, and it is possible that game devices will want to use these separately. With this in mind, if you have a Windows game that you are porting to a Windows Store App, you will need to add support for touchscreens as your existing mouse code will not be used. 


Pushing Performance

Both the iOS and Android versions of MonoGame run on top of the mono platform. Mono has a great compiler, and these two mobile platforms also include a linker, which is used to reduce the package size of your application by removing code that is not needed. It also has a really good garbage collector, but even though there have been some major advances in the mono GC in the last few years, you still have to remember you are running on mobile devices which vary dramatically in terms of power (especially when it comes to Android devices). Make sure to look at your Update methods; you might be making work for the GC by creating lots of temporary objects that will just be disposed of. You should also think about whether you actually need to do a particular calculation in a certain way -- after all, the fastest code is the code that doesn't get executed. Consider the following class definition:

public class Player

{

  public Vector2 Position;

  public Vector2 Scale;

  public void Update(GameTime gameTime) { … }

 public void Draw(GameTime gameTime) { … }

}

In this segment, we are defining a player that has a position and a scale. Now suppose we are using the Matrix property of the sprite batch to render this player using the position and scale data, and also we are using that information for collision detection. One easy way to create the matrix is to do it in the update method like so:

var matrix = Matrix.CreateTransform(Position) * Matrix.CreateScale(Scale);

On desktop machines, this would probably work just fine, but on lower-powered mobile devices it could be a problem. Even though the Matrix class is a struct, and cheap to create, we are still making this calculation every call to Update. If your game is running at 30 frames per second, that alone is many matrix calculations that you might not have had to do. Instead, you could declare a field for the Matrix and just update it in the Update method. Or, even better, you could just update the Matrix only when the Position or Scale properties are changed (see Listing 2).

Listing 2: Optimizing update calls for mobile devices.

public class Player

{

  Vector2 position;

  Vector2 scale;

  Matrix matrix;

 public Vector2 Position {

   get { return position;}

   set {

       if (position != value) {

          position = value;

          UpdateMatrix();

       }

   }

 }

public Vector2 Scale {

   get { return scale;}

   set {

       if (scale != value) {

          scale = value;

          UpdateMatrix();

       }

   }

 }

 public void UpdateMatrix() {

   matrix = Matrix.CreateTransform(Position) * Matrix.CreateScale(Scale);

 }

  public void Update(GameTime gameTime) { … }

 public void Draw(GameTime gameTime) { … }

}

I know this is a simple example, but if it gets you thinking about what tiny improvements you can make, then it has done its job. Those of you who know your math will know that you can probably remove the scale and position fields from this class, and just store the matrix. 


Another example is handling firing bullets from a gun or ship. One way of doing this would be to have a List<Bullet> where we add new bullets as we need them, and remove them as they go offscreen or out of the playing area. When you add items to a list that cause the list to expand its capacity, you are creating new instances of Bullet each time. A more efficient system would be to have a cache of Bullet instances that we can use to populate an "active" list of bullets that are in use. As bullets go out of play, we can just put them back in the cache.

Of course, you probably don't need an infinite amount of bullets; most games tend to limit the number of bullets or missiles you can fire. In that case, you can keep your cache small so as not to use up too much memory, and you can set a capacity on the active bullets list in advance so that the list is not expanding during game play.

If you are having to create lots of temporary variables while loading levels or doing some other sort of major processing, you can try to call GC.Collect(0) as often as you can. This will ensure that the Garbage Collector collects those temporary items as soon as possible, rather than waiting for the next automatic collection. Also, you will want to try to avoid calling any kind of collection during game play; if the GC does have to do a major collection during an update loop you will no doubt see a definite pause in your game. It might only be for a few milliseconds, but it will be noticeable. Note that unlike GC.Collect(0), GC.Collect() does a full garbage collection across your application's heap. It can take up to 100-200 milliseconds (several frames) so you will want to avoid making calls to GC.Collect during actual gameplay, otherwise it will introduce a definite pause or lag to your game.

Lastly, don't use too many instances of SpriteBatch. Generally speaking, in XNA games you will want to keep the number of SpriteBatch instances to a minimum; this is true for MonoGame as well. MonoGame's implementation has an internal cache of SpriteBatchItems that we recycle to keep the number of object allocations to a minimum. By default, we create 1000 items in the cache, so the more instances you have the less system memory you will have to play with.

Android and iOS Linker

When targeting Android and iOS using the Xamarin tool chain, you will need to be aware of the linker that is run against your code during the build process. This linker reduces the code size of your packages by removing unused code in both your code and the framework depending on the linker settings. The result is your final package will contain an optimized mono framework, rather than the entire framework, and any unused code in your game will have been removed as well.

If you are dynamically loading objects through the content pipeline, you'll sometimes get into the situation where the linker doesn't know you need the types you are using, and it removes that code. This will usually result in a MissingMethodException when trying to construct or call methods on these linked away types. The good news is that there are ways in which you can inform the linker that you wish to preserve certain objects as they are and not remove them. One method is to use PreserveAttribute, which is provided in both iOS and Android:

#if ANDROID

  [Android.Runtime.Preserve(AllMembers=true)]

#elif IOS

  [MonoTouch.Foundation.Preserve(AllMembers=true)]

#endif

public class Example {

  public Example ()
  {
  }
}

This will make sure that on both Android and iOS this entire class is not linked away.

There are other ways to control the linker behavior; if you are interested in learning more the documentation for both iOS and Android is available on Xamarin's docs site.

http://docs.xamarin.com/guides/ios/advanced_topics/linker
http://docs.xamarin.com/guides/android/advanced_topics/linking 

Help Us Out!

This article has only touched on the surface of MonoGame, but hopefully it will motivate you to try it out -- and perhaps even make it better! We've got a few things on the horizon that we want people to help out with, like building a fully cross-platform content pipeline, adding new platforms like Windows Phone 8 and Raspberry Pi, making use of DirectX 11, and extending the XNA API even further. So if you want to help out, head over to www.monogame.net and join in.

Return to the full version of this article
Copyright © UBM Tech, All rights reserved