Automatic XNB serialization in XNA Game Studio 3.1

Originally posted to Shawn Hargreaves Blog on MSDN, Wednesday, March 25, 2009

One of the new features in Game Studio 3.1 is automatic serialization for .xnb files. This is a feature I have wanted ever since we first designed the Content Pipeline, so it makes me very happy that we finally found time to implement it!

 

Short Version

You know those ContentTypeWriter and ContentTypeReader thingamyhickies? You don't need them any more! Delete them, and the Content Pipeline will automatically serialize your data using reflection.

Note: you can still write a ContentTypeWriter and ContentTypeReader by hand if you want. You just don't have to any more.

 

Ever So Slightly Longer Version

If the Content Pipeline encounters a type that has no matching ContentTypeWriter, it will dynamically create one for you. This works in a similar way to the IntermediateSerializer, but it reads and writes binary .xnb files instead of XML.

 

Example

The trick to successfully using the Content Pipeline is to understand which code runs at build time versus runtime. Even though the serialization is now automatic, you can still get in a muddle if you have things like cyclic references where your game tries to use a type that is defined inside itself while building itself!

Here's an example of using the new serializer:

Create a new Windows Game project. Let's call this MyGame.

Right-click on the Solution node, Add / New Project, and choose the Windows Game Library template (not Content Pipeline Extension Library, because we want to use this at runtime as well as build time). Call it MyDataTypes.

Add this class to the MyDataTypes project:

    public class CatData
    {
        public string Name;
        public float Weight;
        public int Lives;
    }

We're going to use this type to build some custom content, so we need to reference it during the Content Pipeline build process. Right-click on the Content project that is nested inside MyGame, choose Add Reference, and select MyDataTypes from the Projects tab.

Now we can add this file (let's call it cats.xml) to our Content project:

    <?xml version="1.0" encoding="utf-8" ?>
    <XnaContent>
      <Asset Type="MyDataTypes.CatData[]">
        <Item>
          <Name>Rhys</Name>
          <Weight>17</Weight>
          <Lives>9</Lives>
        </Item>
        <Item>
          <Name>Boo</Name>
          <Weight>11</Weight>
          <Lives>5</Lives>
        </Item>
      </Asset>
    </XnaContent>

Hit F5, and the content will build. Look in the bin directory, and you will see that it created a cats.xnb file. If you build in Release configuration, this file will be compressed. With the above example, my 326 byte XML file becomes 270 bytes in .xnb format, but the compression ratio will improve as your files get bigger.

Before we can load this data into our game, we must reference the MyDataTypes project directly from MyGame as well from its Content sub-project, in order to use our custom type at runtime as well as build time. Once we've done that, we can load the custom content:

    CatData[] cats = Content.Load<CatData[]>("cats");

If you create a copy of this project for Xbox 360, you will notice that although the Xbox version of MyGame references the Xbox version of MyDataTypes, its Content project still uses the Windows version of MyDataTypes, even though it is building content for Xbox. This is an important point to understand. Because our custom type is used both at build time (on Windows) and at runtime (on Xbox), we must provide both Windows and Xbox versions of this type.

 

Type Translations

Remember how some types are not the same at build time versus runtime? For instance a processor might output a Texture2DContent, but when you load this into your game it becomes a Texture2D.

The .xnb serializer understands such situations, as long as you give it a little help. For instance if I used this type in MyGame:

    public class Cat
    {
        public string Name;
        public Texture2D Texture;
    }

I could declare a corresponding build time type in a Content Pipeline extension project:

    [ContentSerializerRuntimeType("MyGame.Cat, MyGame")]
    public class CatContent
    {
        public string Name;
        public Texture2DContent Texture;
    }

Note how both types have basically the same fields, and in the same order, but the runtime Cat class uses Texture2D where the build time version has Texture2DContent. Also note how the build time CatContent class is decorated with a ContentSerializerRuntimeType attribute. This allows me to use CatContent objects in my Content Pipeline code, but load the resulting .xnb file as type Cat when I call ContentManager.Load.

 

Performance

Every silver lining has a cloud, right?

The automatic .xnb serialization mechanism uses reflection. Reflection is slow, and causes a lot of boxing, which can result in a lot of garbage collections.

Fortunately, this is not as bad in practice as it sounds on paper. Many custom data types are small, or at least the custom part of them tends to contain just a few larger objects of types that have built-in ContentTypeWriter implementations (textures, vertex buffers, arrays or dictionaries of primitive types, XNA Framework math types, etc). In such cases the performance overhead will be low. When I converted a bunch of existing Content Pipeline samples to use the new serializer, it did not significantly affect their load times.

But if you have a collection holding many thousands of custom types, you may see poor load performance. In such cases, you can provide a ContentTypeWriter and ContentTypeReader for just the specific types that are slowing you down. The new system is easier to use, but it can be more efficient to do things the old manual way.

Blog index   -   Back to my homepage