Are we sitting comfortably, let us begin.
In yet another one of my adventures with MonoGame and showing new and interesting ways, you can take your old XNA code and move it forward on to the MonoGame platform (and still keep your sanity), I’ll go over one of those fringe / advanced options available to old XNA projects where you can effectively build your own content.
The reasons for doing this are quite simple:
You want to add custom data at build time – e.g., bounding box information, scale, size and weight
You would like to have XML config to change how your content is built like the particle pipeline sample (http://bit.ly/13suolj)
You want to pre-pack your model’s effects at build time (Custom types / Skinned model examples)
You want your cake and you darn well want to eat it too!
There are more but these are the highlights. Basically anywhere that you want to offload processing of your content from run time to build time (why wouldn’t you?), saving you endless amounts of spin cycles, which on some platforms can mean the difference between your game flying along at 30 – 60 fps and a slideshow.
First off – Content Please
Now one of the questions I get asked (or see asked) surround content projects themselves, they are only natively supported in Visual Studio 2010 and later some partial support was added to 2012 for Phone 7 support, this is great if like me, you rock out Pro or Ultimate but not everyone has access to these. Sure you can use MonoGame from most of the express editions but, if you try and launch a Content project, it just crashes and dies (usually).
To clear things up, here are all the configurations in which content projects are definitely supported:
Visual Studio C# 2010 express*
Visual Studio Visual Basic 2010 express*
Visual Studio 2010 express for Windows Phone
Visual Studio Pro & Ultimate*
Visual Studio 2012 Pro & Ultimate**
Visual Studio Express 2012 for Windows Phone
* With XNA GS 4.0 / Windows Phone 7.1 SDK installed
** With the Windows Phone 8 SDK installed
And here are the editions where it is not supported:
Visual Web developer express 2010*
Visual Studio 2012 Express for Desktop
Visual Studio 2012 Express for Windows 8
Visual Studio 2012 Express for Web
Xamarin Studio
* Unconfirmed
I highlighted one of the supported editions above specifically because everyone asks “How do I build my content on Windows 8” or “How do I build my content with 2012 if I don’t have pro / ultimate”. The usual answer that people come back with is “You can’t, just use 2010” which as you can see from above is wrong. Even if you don’t intend to deploy to Windows Phone, you can still use the WP8SDK to install the express edition that comes with it to build your content in 2012.
One day, I’d like MS to stop messing around with this project type support, there are good reasons for doing it but leaving out one or two platforms for no good reason is just daft /Rant.
Getting Started
First off, if you haven’t started a MonoGame project before or are just starting a new one, I always recommend to start with the Content Project, get your content right first!
Later, you can choose if you want your content project included in your game solution, I’d recommend not (unless it’s a small project) to avoid building it every time. Granted VS 2012 does a much better job at recognizing if a project needs building again so you may choose to include it anyway.
For this example, I’ll be using the content from the Microsoft Custom Types sample mentioned above, you can find the source sample for this here on my GitHub MonoGame Samples fork.
If you are having trouble locating VS2012 Express for Windows Phone, you can find it here after installing the WP8SDK.
“C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\VPDExpress” (drop the (x86) if you’re on a 32 bit machine.
Prerequisites
First off, make sure you got all the right bits: (you can install these on either Windows 7 or Windows 8).
Studio 2010
Visual Studio 2010 Express and above (from the above list)
XNA Game Studio 4.0 (or) – if installed on Windows 8 see this post
Windows Phone SDK 7.1 + 7.1.1
MonoGame V3+
(optional) Zune – needed for VS2010 to deploy to device
Studio 2012
Visual Studio 2012 Pro+ (or)
Visual Studio 2012 Express for Windows Phone
Windows Phone 8 SDK*
MonoGame V3+
* If you are running on a machine that doesn’t support Hyper-V (SLAT), the emulator won’t install. You can still deploy to a device though.
*Note: You will also need to download the Microsoft Content Types sample as we will re-use some of the files from that project in this tutorial.
Now there is one limitation which is pretty self-explanatory if you are using Visual Studio 2012 for Windows Phone edition to build your content project, in that it only knows how to build phone projects, so a fair few of the other project templates are not available. However, it is still able to read the other project types (C# libraries for example) but you cannot create them in it (by default).
I’d recommend while you are following this tutorial and only have access to the express editions, to do your content project in 2012 Express for Windows Phone and build your game in 2012 Express for Windows Desktop or Windows 8. However, a little trickery will be required to get there using this route.
Create the Content Project
As has been shown a lot of times, even on my blog, once you have the above installed (whichever your flavour), you can start a new Content project using the MonoGame Templates.
See I told you Express 2012 for Windows Phone works.
And yes, if you are using 2012 like I am above, you will likely see the following error, just ignore it, damned annoying if nothing else.
Now add your content to the project (I’m using the Tank model from the custom types sample and including the two textures but not referencing them in the solution or you will get duplicate build errors because they are mentioned in the model file) first to ensure everything builds ok before we start messing with the content project, here I’m selecting Windows as the build platform.
Add a Platform for Test
With the project built, let’s just double check that the content is displaying correctly on its own, just add a MonoGame platform of your choosing to the project and let's display the model (I chose the WindowsGL project because it runs on just about anything*).
*Note: At the time of writing, the WindowsGL template has a slight bug, the reference to the SDL.dll library points to the wrong place, easily corrected, edit the game .csproj file and where it states:
<Content Include="..\..\..\..\Users\<username>\AppData\
Program Files %28x86%29\MonoGame\v3.0\Assemblies\WindowsGL\SDL.dll">
Just remove the “Users\<username>\AppData\” segment, then save and reload the project in Visual Studio.
With our project ready, build the content and then add a link to the built content files in your game project, ensuring you set the build action to “Content
” and the CopyTo
setting at “Copy if Newer” or “Copy Always”.
Lastly, some code to actually display the model as is, as follows: (the entire game class in one shot, basic model drawing stuff).
#region Using Statements
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#endregion
namespace GameName1
{
public class Game1 : Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Model model;
Matrix world;
Matrix view;
Matrix projection;
public Game1()
: base()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
model = Content.Load<Model>("tank");
world = Matrix.Identity;
view = Matrix.CreateLookAt(new Vector3(1000, 500, 0),
new Vector3(0, 150, 0), Vector3.Up);
projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
GraphicsDevice.Viewport.AspectRatio, 10, 10000);
foreach (ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = world;
effect.View = view;
effect.Projection = projection;
}
}
}
protected override void UnloadContent()
{
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
model.Draw(world, view, projection);
base.Draw(gameTime);
}
}
}
Now if you run the solution, you should get a model being drawn to the screen.
Getting the Custom Types in Your Pipeline
Now there are two schools of thought with how to use custom content types in XNA and both will work with MonoGame itself, however only one way will work (as I found) on all platforms.
The two ways are:
By using the [ContentSerializerRuntimeType]
attribute definitions
These are set within your content processor to identify your custom types by their reflection type name, then hosting the class definition for the type in your game project.
By using a common library between the content processor and your game project effectively sharing the class definition between the projects
The second option which I’m going to show you here is the one that will work on all platforms, the first is fine for Windows platforms but runs into issues with others (in my experience).
First Up the Custom Type Project
So first, we need a class library to hold our custom types, so create a new C# class library for the solution, its namespace needs to be unique in your project to ensure it's picked up by both the content processor and your game platform.
*Note as stated earlier, if you are using the 2012 Express editions, you’ll need to swap to VS 2012 Express for Windows Desktop / Windows 8 to create the class library and then switch back to the Phone Express to add the existing project to your content solution. A little tedious but it works (and it’s FREE). You will see (yet another) silly warning telling you the class library is targeting .NET 4.5 and that it needs to convert it to .NET 4.5 to read it (sheesh), just accept the default to update the project.
Once the Class Library is loaded, right-click and select properties on the Class Library project and in the Application tab, change the “Target Framework” to .NET 4. XNA and MonoGame just work better that way.
Once the Class Library project is loaded, we can setup our custom type we intend to use, you can copy the class over from the XNA sample or just replace the default Class.cs code with the following:
#region File Description
#endregion
#region Using Statements
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
#endregion
namespace CustomContentTypes
{
public class CustomModel
{
#region Fields
#pragma warning disable 649
[ContentSerializer]
public List<ModelPart> modelParts { get; private set; }
public class ModelPart
{
public int TriangleCount;
public int VertexCount;
public VertexBuffer VertexBuffer;
public IndexBuffer IndexBuffer;
[ContentSerializer(SharedResource = true)]
public Effect Effect;
}
#pragma warning restore 649
#endregion
private CustomModel()
{
}
public void Draw(Matrix world, Matrix view, Matrix projection)
{
foreach (ModelPart modelPart in modelParts)
{
BasicEffect effect = (BasicEffect)modelPart.Effect;
effect.EnableDefaultLighting();
effect.World = world;
effect.View = view;
effect.Projection = projection;
GraphicsDevice device = effect.GraphicsDevice;
device.SetVertexBuffer(modelPart.VertexBuffer);
device.Indices = modelPart.IndexBuffer;
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
device.DrawIndexedPrimitives(PrimitiveType.TriangleList,
0, 0, modelPart.VertexCount,
0, modelPart.TriangleCount);
}
}
}
}
}
There’s not too much fancy stuff here, the Custom Model type in the MS example just provides a more raw view for what goes on under the hood for drawing a model, it reads out the raw model data and stores it in its respective Index and Vertex buffers plus all the other necessary information for doing primitive drawing. It also delivers a custom Draw function for sending the Index/Vertex data to the graphics device. If you wished, you could change the colour of random vertices / alter the normal mapping of certain elements or even deform the mesh on build, all stuff that would be very expensive to do at runtime.
If you wish, you can also rename “class1.cs” to something more appropriate but it’s just for show (still a good idea though ).
The Content Pipeline Project
Now we have our custom type, we need a way to tell the content pipeline to use it when loading our model. Open up your Content project again (if it’s separate), and add a new project to the solution, this time select the “Content Pipeline Extension Library” template in the “XNA Game Studio 4.0” branch. (There is a way to do the same thing with a class library but this is easier and safer for now.)
Once it’s loaded, you should get a default “ContentProcessor1.cs” class in the project, just a starter for 10.
*Note again if you are using 2012 Express for Windows Phone to create the content pipeline extension project, you will need to update the target framework to .NET 4.0 else the content project won’t recognize it.
If you want to learn more about how content pipeline extensions work, check out the documentation on the Creators club website here, well worth a read if you want to do more advanced things with your content before you import it into your game.
Now for simplicities sake (and to prevent this post from becoming miles longer), just copy over the “CustomModelContent.cs” and “CustomModelProcessor.cs” from the XNA sample into your Content Pipeline Extension project and then update the following to match what we are doing here (I always find it best to implement these things yourself to ensure you understand what and why they work), then update the following:
Update the namespaces in the two new files to the new project namespace
In the “CustomModelContent.cs” class, update all references of “CustomModelTypes
” to the assembly name of your ContentTypes
project. e.g.:
Replace:
[ContentSerializerRuntimeType("CustomModelTypes.CustomModel, CustomModelTypes")]
[ContentSerializerRuntimeType
("CustomModelTypes.CustomModel+ModelPart, CustomModelTypes")]
with:
[ContentSerializerRuntimeType("CustomContentTypes.CustomModel, CustomContentTypes")]
[ContentSerializerRuntimeType
("CustomContentTypes.CustomModel+ModelPart, CustomContentTypes")]
Add a reference to the Content Types project from the Pipeline Extension project
Add a reference to the Pipeline extension project from the Content project (note the actual content project where the model is stored NOT the content builder project)
Finally, add a reference to the Content Types project from the game project
With that in place, you should be able to build the content project and now if you select the “Tank.fbx”, you should be able to change the tanks content process from the default “Model – XNA Framework” to your new processor type “Custom Model” which uses your new content type class for storing the final built model in.
*Note: If the new Processor type doesn’t show up, check whether you have built and referenced it correctly and that BOTH the Content Types project and the pipeline extension project are targeting .NET 4.0.
If you’re really curious, just read through the content processor and content classes, the content class just describes the custom type and the processor does all the heavy lifting to populate the new custom model type with the information stored in the “Tank.fbx” model file.
There is an alternate way, the Skinned Model sample doesn’t even use the CustomModelContent
definition, in some ways, it’s a lot cleaner. Having the CustomModelContent
class does mean you don’t have to reference the custom type from the content project but I prefer completeness. The final choice is down to you really.
Getting Back in With the Game
Now that we are finally hitting the home stretch to see what our labours have delivered, let’s just replace the game code one last time. So open up your game project and break out the Game
class and replace it with the following:
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.GamerServices;
using CustomContentTypes;
#endregion
namespace GameName1
{
public class Game1 : Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
CustomModel model;
Matrix world;
Matrix view;
Matrix projection;
public Game1()
: base()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
model = Content.Load<CustomModel>("tank");
view = Matrix.CreateLookAt(new Vector3(1000, 500, 0),
new Vector3(0, 150, 0), Vector3.Up);
projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
GraphicsDevice.Viewport.AspectRatio, 10, 10000);
}
protected override void UnloadContent()
{
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
HandleInput();
float time = (float)gameTime.TotalGameTime.TotalSeconds;
world = Matrix.CreateRotationY(time * 0.1f);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
model.Draw(world, view, projection);
base.Draw(gameTime);
}
#region Handle Input
private void HandleInput()
{
KeyboardState currentKeyboardState = Keyboard.GetState();
GamePadState currentGamePadState = GamePad.GetState(PlayerIndex.One);
if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
currentGamePadState.Buttons.Back == ButtonState.Pressed)
{
Exit();
}
}
#endregion
}
}
*Note: Just remember to update any different using
statements (different namespaces and all).
Walking through the code above all we have done is remove any model setup code because it’s all done in our custom content class, all we need to do is tell it where it is in the world (the world / view and projection matrices) and tell it to draw. For added effect, the model now spins as well.
Now I could go a lot further and tinker with both the content processor and the custom content type, but I’ll let you play.
So What Was All This About?
Well, the main purpose of this article was to go over all the requirements of using custom types in your MonoGame projects and some pointers as to where you should look to improve your asset pipeline to try and do as much with your content up front at build time. This becomes crucial with platforms like iOS where every cycle counts (don’t get me started on Android).
Oh and to show you that it is possible to build a game using JUST the 2012 express editions, no longer are you chained to the 2010 versions (granted still nothing wrong with using 2010 ).
This was a very technical article so if you have any comments / questions or queries, please let me know below.