Content processors in Monogame

The flatshaded models are the result of modelling the object in Wings3D then exporting it to XML (vertices and faces). I made my own tool that reads the XML and converts the model to my own VertexPositionNormalColor format. In the tool I can do various tweaks and color the model. This tool then exports the data to an XML file with the extension VPNC.

The file is quite simple really:

It defines 1 black triangle with the normal pointing up.

To handle the meshes in my game I made a simple content processor- this makes using my own models as easy as:

I may make more content processors later for the tracks or any other objects that I use external tools for.

Disclaimer: There may be easier ways to handle flatshaded models (via shaders for example) that’s another topic. This topic shows how to create a content processor, I use my flatshaded model as example.

Continue reading if you want to know how to make a content processor yourself.

So how do you make a content processor?

Right, it’s easier than you may think. The content processor is a Class Library with a few mandatory components, I’lll show you how these work and why these are here.

Let’s get to work. First create a new Class Library project. I named mine ‘FlatShaded’.

First the object that we plan to use: the FlatShadedModel. For now, the model only holds the mesh. So I made a simple class:

The purpose of this class is twofold. First, the content processor need to ‘understand’ the structure the object it is providing. Second this way the object can be used in other projects as well since all there is to it is contained in this class library.  The “Content.Load” line in the top of this article is actually pointing towards this FlatShadedModel class.
Note: I’ve also included the VertexPositionNormalColor class I wrote about earlier.

Now we’re ready to make our content processor. Before we can do this we should add the Monogame.Framework.Content.Pipeline reference. It is rather easy as you can get the NuGet package. Note that this is currently a prerelease package so if you’re using the Visual Studio Nuget Package Manager, be sure to tick the ‘prerelease’ checkbox.
If you’re not able to use the NuGet Package Manager, the dll also comes with the Pipline tool. The dll resides in the same directory, which is by default:

With the Monogame.Framework.Content.Pipeline.Portable reference added, time to create some content processors!

The content workflow

The content processor has a workflow like this:

  1. The file is read by the ContentImporter and sent to the ContentProcessor.
  2. The ContentProcessor turns the data read by the ContentImporter into an object and passes this object to the ContentWriter.
  3. The ContentWriter writes a stream of Monogame.Framework variables into a content file.
  4. In the project where the contentfile is used, the ContentReader reconstructs the object from the stream the ContentWriter had written.

Let’s take a look at these components in detail:


The Content Importer’s main task is to read the contents of the file and pass it to the content processor. Let’s have a look at the code:

The line [ContentImporter …. (line 7) tells the Pipeline tool to use this content importer for files with the .vpnc extension. It also tells the Pipeline tool to use VPNCProcessor as the default processor. It is quite possible to have multiple processors for the same type of file. Hence the separation between the Importer and Processor. In this example I only have one, so the default processor is enough.
The VPNC file is actually XML document with a different extension. So our ContentImporter will read it and passes it to the Content Processor.
Line 14 is a helpful debug line that shows up in the Pipeline tool or your build output window.

So the file has been read into an XDocument; so let’s see what the next stage looks like!


The ContentProcessor’s task is to construct the usable object so it can be passed to the ContentWriter. In reality the ContentProcessor could be a structure that just holds the variables, but I usually construct the entire object so I am certain everything is working correctly and any errors in the .VPNC file are caught here. Let’s have a look at the code:

The first interesting line is #13: note the class is named VPNCProcessor. This is the same name as the ‘default processor’ in our importer. Further it is based on the ContentProcessor class. We tell the content processor it can expect an XDocument as input and should output a FlatShadedModel as this is the type of content we’re processing.

I’ll skip the constructor for now, I’ll get to that in a moment.

The important function is at line #21. This is where the input XML document is read and the FlatShadedModel is created from the data. The input type is XDocument as defined by the ContentImporter and the output is FlatShadedModel so we can pass that one to the ContentWriter.

The Processor’s constructor is currently empty- you may add parameters to the Pipeline process. For example, maybe you want to scale the model or do some different preprocessing things. Variables that you can change in the Pipeline tool can be added like this:

Be sure to set ScaleModel in the constructor to 1f. The ‘DefaultValue’ is the number displayed in the tool, not the actual value of the variable!


Almost done! Next step is to actually write the values we need into the content file. The ContentWriter takes care of that.

As you can see, the ContentWriter simply writes the values as a serializer into a file. It takes the FlatShadedModel as input. For my model all I need is the array of VertexPositionNormalColor. So I first write the lenght of the array, so I can initialize my array later. Then I simply need to write all positions, normals and colors as a list of values. The write function has overloads for most datatypes including most of the native XNA/Monogame ones (see lines #16, #17 and #18).

This step requires a bit of planning ahead; the values are written as a long chain of data. You have to think what you’ll need to reconstruct your object based on that info. This is the reason I write the lenght of my array as a value. For more complex objects (a TMX map made with Tiled for example) planning is required.

At lines #22 and #27 you’ll notice references required to read the model. Since I usually contain the object and the contentprocessors in one project, it is fairly straightforward. If you have the object and the contentprocessors separately, you’ll have to return a string to the assembly where your ContentReader is located.

Speaking of the ContentReader…


The ContentReader is able to construct the object from the datastream the ContentWriter had written. Let’s have a look at the code:

The reader is fairly straightforward. In the writer, I’ve written the lenght of the array first. In line #13 I read the first value from the file as Int32 (since that is the type used to write it!). A new array of VertexPositionNormalColor is constructed in line #14. Next all values are read in the order they were written.  The values are read in the same format as they were written.
This is all used to reconstruct the FlatShadedModel which is then returned.

So now all components for the contentprocessor are done!

Make it work

Once the Class Library is compiled, we can use it in our projects. Reference the Flatshaded.dll (or reference the project in your solution).

The Pipeline tool must be aware of the new contentprocessor.  Open the content.mgcb file in a text editor. Lookup the References section:

Add the (relative) path to the dll. This can also be done from the Pipeline tool, I find it easier to add it from the content.mgcb file.

When you now add a vpnc file (for example the XML on the top of this page- saved as demo.vpnc) to the Pipeline tool, you should see the content processor being used in the bottom left corner:


Now you should be able to load your demo.vpnc file like this in your ContentLoad() method:

This should get you started creating content processors for your own objects! Happy coding!


2 responses on “Content processors in Monogame

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Verplichte velden zijn gemarkeerd met *