What is serialization?
Serialization is a semi-automatic process where XNA gets a class
that has been marked as [Serializable] and stores/loads it from xml. An awesome idea in theory but with a few shortcomings.
Why these tutorials?
While trying to add serialization to one of my games, I’ve come to notice that there is a very real lack of documentation on XML serialization if you want to do anything more complex than serializing a struct with a few value members inside. When you get to serializing derived classes, dictionaries, trees,… and in general the normal stuff any game needs, there are a lot of hidden pitfalls to be aware of.
It’s a big list of things to go work, but I’m going to share my experiments with serialization and hopefully it will help someone else.
This tutorial is aimed only at the windows platform, simply because it’s the only one I’ve got. I promise that if anyone likes them enough to send me a WP7 or an Xbox, I’ll make sure they are compatible. :D
Note*: Check the comments to see the small change to make it work in WP7 and XBox
A look at MSDN serialization
This tutorial is based on the simplest of all options, the default MSDN tutorial for serializing xml, to be found at MSDN: Serialization. The version provided in the download of that article is aimed for the Xbox, which makes it kind of confusing on the windows platform as it uses classes that are not needed, such as the Guide class. If you are working on the Xbox and have doubts, have a look in the book: "XNA Game Studio 4.0 Programming".
My main objection to the example at MSDN: Serialization is that it does NOT tell you where it gets the "device" object, and if you download the code to find out, it uses the "Guide" to get the device in a rather confusing way.
Searching around on how to make it work I found another 3 ways to get the storage device here MSDN : Get StorageDevice. Note that even though they are ALMOST the same as in the MSDN: Serialization example code, the function call varies, not much, but enough to drive one crazy. God bless IntelliSense. If you’ve got more interest, there is a theoretical explanation on how to get a storage device at MSDN : User Storage.
For both saving and loading the steps are almost the same. Notice most of the work goes into getting the actual file we are going to save to. The function calls to achieve this vary slightly from platform to platform, but we’re going to stick with windows, as the code download at the MSDN: Serialization tutorial has a Xbox version.
- User requests to Save / Load
- Get the StorageDevice.
- Request StorageDevice
- Receive StorageDevice
- Get a StorageContainer from the device.
- Request StorageContainer
- Receive StorageContainer
- Get the Stream of the file we want to use from the container.
- Serialize / Deserialize.
With that out of the way, let’s head over to the code!
We will be using a very simple data class to serialize in this first tutorial.
First thing to notice here is that you need to mark with the [Serializable] attribute the class you want to serialize (Not necessary for serializing to XML, check r2d2rigo's comment), there are more options to explore with related attributes and you can find it here. Second, that it’s a public class, as the object you serialize must be public. Third, only public properties or fields will be serialized (age and name).
And here is how we load / save the data.
We need to get 2 objects with asynchronous calls, the device and the container. This means we need to be careful not to block the game while we wait for the calls to return, and that’s why the code looks like a magical rainbow of cases, because the Update() function might be called lots of times while we wait and we don’t want strange stuff to happen.
We create a data object with just a name and an age, when we press the left button of the mouse the age increments, press “s” to save and “l” to load the last saved data.
Following the comments in the code should give a pretty clear idea of what is going on, but we’ll go over it none the less, I’ll ignore the support code not directly related with serialization, but if there are any doubts just ask in the comments. There is some redundant code but I haven’t shortened to make it as simple as possible to follow the logic.
Both save and load work very similar. First thing we need to do is get a storage device. This requires using an asynchronous call, so we request it (2.a) by calling BeginShowSelector and when it’s ready result.IsCompleted will return true. Notice that in windows we pass PlayerIndex.One as the parameter.
We then receive the device (2.b) and request the container from the device (3.a) with BeginContainer, where we pass the name of the “container” to use, in this case, the folder StorageDemo.
We finally we receive the container (3.b) and we’re ready to go! No more magical asynchronous stuff.
The last part is where the actual serializing takes place. For saving we get ourselves a file to write on (4) and a serializer for the type of data we want to serialize (Data1 in this case), and then just serialize it into the file (5) and tidy up (6). For loading we check the file exists (4) and then open it and deserialize into our data object (5) and clean up (6).
The generated XML looks like this:
A last note on how the system is set up. The Mode2 class is a DrawableGameComponent so that the XNA framework takes care of everything (updating and drawing and so on), and in our game class we only need to add one line. Has nothing to do with serializing.
Data that XNA serializes
XNA doesn’t serialize everything that there is in the class, but the documentation is not too specific on what’s game besides “public properties and fields”. So I went ahead a did a little testing. Here are the results of testing, plus what I found searching around the net.
- Serializes public properties, not private or protected.
- Serializes public fields with both get and set, not partially implemented fields.
- Arrays of values or of objects work fine.
- Lists<> of values or of objects work fine. Tough not generic List<T>
- Cannot serialize ArrayList.
- Cannot serialize an enumeration of unsigned long with any value larger than 9,223,372,036,854,775,807.
- Typical XNA classes like Rectangle or Vector work just fine with the serialization.
- On serializing references, I found this bit from the MSDN forum:
“It will serialize references, but always as a copy. Basically what this means is that each time the serializer hits an object that has a reference to another serializable object exposed as a public property or field, it will write out the all of the public properties/fields of the object that reference points to. This is a problem if you have the same object referenced multiple times by something you're serializing. For instance...say you had two MeleeAttack's referencing the same PhysicsObject. After deserialization, you'd end up with your MeleeAttack's having references to different PhysicsObject's with identical properties. I used to get around this by giving certain types of objects a unique integer ID, and then having the objects serialize the ID rather than the reference. Then after deserialization I'd have another initialization step where I'd use the ID to look up the corresponding object and grab a reference.”
- From textures it only saves the name.
Leads for more complex serialization magic
For elements that we read from Xml that are not in the object, we can use the XmlAnyElementAttribute and XmlAnyAttributeAttribute attributes. Useful for compatibility with older versions of our code. Check it here.
To ignore a field we can use the XmlIgnoreAttribute. Check it here.
You can override the serialization of any set of objects by creating one of the appropriate attributes and adding it to an instance of the XmlAttributes class. More complex stuff that can be used to serialize things that come from dlls, or to control how to serialize your own data. Check it here.
You can use the IXmlSerializable interface to provide custom formatting for Xml serialization and deserialization. Check it here.
4 Quick rules
To end, 4 quick reference rules to keep in mind for serializing:
Note #1: You need to mark the class you want to serialize with the [Serializable] attribute.
- Note #2: Serialized classes must have a default constructor.
- Note #3: Serialization only converts the public properties and fields
- Note #3: The actual object you serialize must be public.
The serialization mechanism is awesome as in it’s automatic for the classes marked, but it’s lack of handling references correctly makes it useless for saving a big “chunk” of game data (like a whole level) as it doesn’t avoid the work of having to set up a ID system to take care of the references. It is best suited for things as saving the player preferences, or the control settings or similar.