Page 1 of 1

Custom Content and IntermediateSerializer XNA 4.0

#1 SixOfEleven  Icon User is offline

  • using Caffeine;
  • member icon

Reputation: 944
  • View blog
  • Posts: 6,342
  • Joined: 18-October 08

Posted 23 March 2011 - 03:47 PM

Loading Custom Content With IntermediateSerializer

This is an advanced tutorial on loading custom game content using the IntermdediateSerializer class in XNA. The IntermediateSerializer allows you to serialize custom content to an XML file. When you add this XML file to your Content project XNA will convert the content to an XNB file and you can load it like any other game content using the Load<T> method of the ContentManager class. If you are new to the concept of XML serialization it is the process of taking an object and serializing its data to a file with XML format.

To get started create a new Windows Game (4.0) and call it CustomContentDemoGame. You will want to add another project to the solution, a Windows Game Library (4.0). In the Solution Explorer Window right click the Solution, select Add and then New Project. Name this new project ContentLibrary. Right click the Class1.cs file that was generated and select Delete. This project will hold the classes that define the custom content that you will be creating in an editor and writing to disk. You can then add these files to your content project and when you compile your game they will be compiled to an XNB file. This allows you to protect your game resources from being edited.

The next step is to add a project that will serve as an editor. I will be creating a simple console application that you will create a few objects and serialize them into XML format. You can also create a Windows Forms application with a rich GUI to create your custom content. I've made level editors that do that and actually host an XNA tile engine inside of the Windows Forms application. Right click your solution in the Solution Explorer again. Select Add and then New Project. From the Visual C# pane choose the Console Application option. Name this new project ContentEditor.

For your other projects to know about ContentLibrary project you need to add a reference to them. If you look in the Solution Explorer window there is a References entry in each project. Right click the References entry in the ContentEditor project in the Solution Explorer and select Add Reference. Select the Projects tab and then add the ContentLibrary project as a reference. The ContentEditor project now knows about the ContentLibrary project. Repeat this process for the CustomContentDemoGame and the CustomContentDemoGameContent projects to add a reference to the ContentLibrary project.

The next step is to create a class in the ContentLibrary project that will represent your custom content. I'm going to create a simple class that will have a few items. Remember that by default only public members will be serialized. You can override this behavior using attributes. I will get to that in a bit. Right click your ContentLibrary project, select Add and then Class. Name this new class Entity. The code for the Entity class follows next.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Content;

namespace ContentLibrary
{
    public class Entity
    {
        public string Name;
        public int Age;

        [ContentSerializer]
        private bool gender;

        [ContentSerializerIgnore]
        public bool Gender
        {
            get { return gender; }
            set { gender = value; }
        }

        private Entity()
        {
        }

        public Entity(string name, int age, bool gender)
        {
            Name = name;
            Age = age;
            Gender = gender;
        }
    }
}



The first thing I did was add a using statement to bring the Content namesapce of the XNA Framework into scope for this class. I added it so that I can use the ContentSerializer and ContentSerializerIgnore attributes easily. A using statement in this context allows you to use a class name with out fully qualifying it. For example, a using statement for the XNA Framework Graphics namespace allows you to use SpriteBatch rather than Microsoft.Xna.Framework.Graphics.SpriteBatch for the SpriteBatch class. By adding the ContentSerializer attribute to a field or property you tell IntermediateSerializer to serialize that field or property even if it isn't public. I applied that attribute to the gender field so that the gender field will be serialized even though it is private. The ContentSerializerIgnore attribute works in reverse. It tells the IntermediateSerializer to ignore a field or property even if it is public. I added that to the Gender property that is public with get and set accessors so it won't be serialized. You can apply other attributes to fields and properties but I'm not going to go into that here. If you are interested you can read more about the Intermediate Serializer on Shawn Hargreaves Blog. To deserialize your custom content you need a constructor that takes no parameters. For that reason I included a private constructor that takes no parameters. I also included a public constructor that takes the name, age, and gender for the Entity as parameters. It sets the fields using the values passed in. I use the property I created to set the gender field to the parameter to pass in.

The next step will be to create some content and serialize the content to an XML document. To do that you need to add another reference to the ContentEditor project. To add that reference though you need to change a property for the project. When you create a project with Visual Studio 2010, or Visual C# 2010 Express, the target framework will be the .NET Framework 4 Client Profile. To add the reference you need to change the target framework to be the full .NET Framework 4. Bring up the properties for the ContentEditor project. You can do that by right clicking the Properties entry in the Solution Explorer window and selected Open. In the combo box under Target Framework select .NET Framework 4 rather than the client profile. A dialog box will pop up when you do that. Just click Yes to change the target framework. You can now add the needed reference to the project. Right click the References entry in the ContentEditor project in the Solution Explorer and select Add Reference. From the .NET tab you will want to choose Microsoft.Xna.Framework.Content.Pipeline. This reference is only available in Windows. It is not available on the Xbox 360. That is why I create a Console Application for creating the custom content.

The next step is to create some content. Open the code for the Program.cs file in the ContentEditor project. Change that code to the following.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;

using ContentLibrary;

namespace ContentEditor
{
    class Program
    {
        static void Main(string[] args)
        {
            Entity entity = new Entity("Tatianna", 21, false);
            Serialize(entity, "Tatianna.xml");

            entity = new Entity("Crag Hack", 33, true);
            Serialize(entity, "Crag.xml");
        }

        static void Serialize(Entity entity, string fileName)
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;

            using (XmlWriter writer = XmlWriter.Create(fileName, settings))
            {
                IntermediateSerializer.Serialize<Entity>(writer, entity, null);
            }
        }
    }
}



I added in a using statement for the System.Xml namespace to bring the XmlWriterSettings and XmlWriter classes into scope. I will get to serializing in a bit. I also added in a using statement to bring the IntermediateSerializer into scope as well as the ContentLibrary project.

In the Main method of Program.cs I create a couple Entity objects and call the Serialize method that I wrote to serialize the object. The Serialize method takes two parameters. The first parameter is the Entity to be serialized and the second is the file name for the Entity. I saved them with an xml extension but you can choose a custom extension if you want so as not to mix up your content.

The Serialize method is where the interesting stuff happens. The first step is to create an XmlWriterSettings object and set its Indent property to true. This is something to make the document look nice and from what I can gather is required by XNA. Please don't quote me on that though. :P Then in a using statement I create an XmlWriter object. In this context the using statement will dispose of any disposable objects with the block is exited. Inside the using statement I call the static Serialize method of the IntermediateSerializer class passing in Entity for the type and the XmlWriter, the Entity to be serialized, and null for the parameters. If you create an editor for a game you may want to include a method for deserializing your objects so you can modify them and write them back out. Below is the code for deserializing an Entity object. Adding a using statement for the System.IO namespace is a good idea.

        static Entity Deserialize(string fileName)
        {
            Entity data = null;

            using (FileStream stream = new FileStream(fileName, FileMode.Open))
            {
                using (XmlReader reader = XmlReader.Create(stream))
                {
                    data = IntermediateSerializer.Deserialize<Entity>(reader, null);
                }
            }

            return data;
        }



To reverse the process I have a local variable that is set to null. I create a FileStream to the file that is set to open the file in a using statement. Inside there is another using statement where I create an XmlReader to read the XML file. I then use the Deserialize method of the IntermediateSerializer to deserialize the file. Finally I return the object. If you will creating many different types of data you might want to consider using generics and static methods to serialize and deserialize many different types. I'm not going to go into that in this tutorial though.

To execute the editor you need to change the start up project for the solution. You do that by right clicking ContentEditor in the Solution Explorer and selecting Set As StartUp Project. Build and run your solution. It will take a bit for your content to serialize to XML. Your content will be in the bin\Debug folder of the ContentEditor folder. What the serialized content looks like follows next.

<?xml version="1.0" encoding="utf-8"?>
<XnaContent>
  <Asset Type="ContentLibrary.Entity">
    <Name>Crag Hack</Name>
    <Age>33</Age>
    <gender>true</gender>
  </Asset>
</XnaContent>



<?xml version="1.0" encoding="utf-8"?>
<XnaContent>
  <Asset Type="ContentLibrary.Entity">
    <Name>Tatianna</Name>
    <Age>21</Age>
    <gender>false</gender>
  </Asset>
</XnaContent>



Right click your CustomContentDemoGameContent project select Add and then Existing Item. From the file type box make sure to select XML Files. Navigate to where your content was serialized and add the Tatianna.xml and Crag.xml files. You will also want to add a SpriteFont font to the that project. Right click it again, select Add and then New Item. Choose the SpriteFont entry and accept the default by click Add.

You now want to change your game to be the start up project. Right click CustomContentDemoGame in the Solution Explorer and select Set As StartUp Project. You can build and run your project. If you navigate to the bin\x86\Debug\Content folder of the game you will see three xnb files. Crag.xnb, SpriteFont1.xnb, and Tatianna.xnb. Your custom content was compiled by the Content Pipeline into an xnb file that can be loaded using the Load<T> method of the ContentManager class!

Now, open the code for Game1.cs to load in the custom content and draw their text to the screen. Change the code for the Game1.cs file to the following. I removed the comments to conserve space.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

using ContentLibrary;

namespace CustomContentDemoGame
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Entity entity1;
        Entity entity2;
        SpriteFont spriteFont;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            entity1 = Content.Load<Entity>("Tatianna");
            entity2 = Content.Load<Entity>("Crag");
            spriteFont = Content.Load<SpriteFont>("SpriteFont1");
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            DrawEntity(entity1, new Vector2(0, 0));
            DrawEntity(entity2, new Vector2(300, 0));

            spriteBatch.End();

            base.Draw(gameTime);
        }

        private void DrawEntity(Entity entity, Vector2 position)
        {
            string text = entity.Name + "\n" + entity.Age.ToString() + "\n";

            if (entity.Gender)
                text += "Male";
            else
                text += "Female";

            spriteBatch.DrawString(spriteFont, text, position, Color.White);
        }
    }
}



So, what does the new code do. First there is a using statement to bring the ContentLibrary namespace into scope. I then added in three fields. Two Entity fields to to hold the data being read in called entity1 and entity2. I also added in a SpriteFont field called spriteFont to load the SpriteFont into.

In the LoadContent method I actually load in the content. I use the Load<Entity> method to load the custom data into the entity1 and entity2 fields. I also load in the SpriteFont.

In the Draw method in between calls to Begin and End of our SpriteBatch I call the DrawEntity method passing in the Entity I want to draw and a Vector2 for where I want to draw it.

In the DrawEntity method I create a string called text that will hold the text to be drawn. I set it to be the Name field of the Entity, a new line, the Age of the Entity converted to a string, and then another new line. If the Gender property of the Entity it true I add Male and if it is False I add Female. I then call the DrawString method of our SpriteBatch object to draw the text.

If you build and run your game you will see the two custom content items that we made drawn as text. This tutorial is only the tip of the iceberg in what you can do with IntermediateSerializer and custom game content. Like I said, I've made a level editor for creating game levels for a tile engine.

Attached Image

Is This A Good Question/Topic? 4
  • +

Replies To: Custom Content and IntermediateSerializer XNA 4.0

#2 LanceJZ  Icon User is offline

  • New D.I.C Head

Reputation: 3
  • View blog
  • Posts: 41
  • Joined: 31-January 11

Posted 26 April 2011 - 04:27 AM

This is very useful information, I have spent so far two days combing the interwebs for information on this. Then I find your tutor! I'm going to use this in my Base Defender game, thank you! Also, works just fine in XNA 3.1 too. :D
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1