Unity stores contents of a scene in a custom YAML file called a Unity scene file. While this is convenient for levels which are designed in Unity’s scene editor, it is not very useful for levels which are built at runtime. Moreover, it is not possible to store game related data in scene files, such as properties of weapons, cars, NPCs, etc. (actually, it is possible, but not feasible). Even though there are a few solutions for that kind of data storage in Unity, such as JSON, or SQL, the easiest of them to implement is XML. In this post, I will show you how to store your game data in XML files and read them at runtime.

To be able to save data in XML files, first we need to be able to convert objects into XML format and then convert XML format back into objects. This process is called serialization and deserialization. C# already has great tools for XML serialization, and we will build upon them to create XML serializer we need.

First, let’s create a sample class to start serializing:

public class Hero
{
    public string name;

    public bool isBoss;

    public int hitPoints;

    public float baseDamage;
}

This is a very basic version of a Hero class that you might find yourself creating for your game. You may think that it is too basic, but don’t worry, we will be adding features as we move on. However, before moving on, you should know two important key points about XML serializing:

  1. XML serializer can only serialize public fields.
  2. Class to be serialized should have a parameterless constructor.

In our case, Hero class already has nothing but public fields and since there isn’t any kind of constructor, it implicitly has a parameterless default constructor.

Let’s say we want this class to output an XML file just like the following:

<Hero>
    <name></name>
    <isBoss></isBoss>
    <hitPoints></hitPoints>
    <baseDamage></baseDamage>
</Hero>

Generally, producing an XML file in a certain format requires some modification to target class. But, in our case we are not looking for a specific format. All our fields we want to serialize are public and there is a default constructor, therefore, we can start creating the XML file:

using UnityEngine;
using System.Xml.Serialization;
using System.IO;

public class XMLSerializer : MonoBehaviour 
{
    private void Start() 
    {
        Hero knight = new Hero();
        knight.name = "Knight of Solamnia";
        knight.isBoss = true;
        knight.hitPoints = 100;
        knight.baseDamage = 50f;

        XmlSerializer serializer = new XmlSerializer(typeof(Hero));
        StreamWriter writer = new StreamWriter("hero.xml");
        serializer.Serialize(writer.BaseStream, knight);
        writer.Close();
    }
}

Let’s have a look at this code line by line:

On Line 9-13, we create a new Hero and assign its attributes.

On Line 15, we create an XmlSerializer instance with type of Hero, since it is the type of the class we are serializing. This XmlSerializer is a C# class and it can be found under System.Xml.Serialization.

On Line 16, we create a StreamWriter instance which would create the XML file itself and do the actual writing. StreamWriter is also a C# class and it can be found under System.IO

On Line 17, we tell the XmlSerializer to serialize knight (our Hero), and send it to a stream.

And finally, on Line 18, we tell writer to close streaming (i.e stop reading input and create the file).

When you drag&drop this code on an empty game object in Unity and run it, you will see that a file named hero.xml is created in your Unity project folder and its contents hold Hero data:

<?xml version="1.0" encoding="utf-8"?>
<Hero xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <name>Knight of Solamnia</name>
    <isBoss>true</isBoss>
    <hitPoints>100</hitPoints>
    <baseDamage>50</baseDamage>
</Hero>

As you can see, creating an XML file required no more than a few lines of code. But, what if we want to have different tag names in the XML file? Or, to use XML attributes instead of XML elements to store data? Or, to use arrays in our class? To achieve these, we need to modify our base class by adding some XML attributes to our fields. But first, we should change something in our code. I like my code well organized and I favor OOP’s single responsibility principle, so I will move XML serialization part into its own class. This way, we will be able to serialize any type of object with a single line of code:

using System.IO;
using System.Xml.Serialization;

public class XMLOp
{
    public static void Serialize(object item, string path)
    {
        XmlSerializer serializer = new XmlSerializer(item.GetType());
        StreamWriter writer = new StreamWriter(path);
        serializer.Serialize(writer.BaseStream, item);
        writer.Close();
    }
}

Now our XMLSerializer looks like this:

using UnityEngine;

public class XMLSerializer : MonoBehaviour
{
    private void Start()
    {
        Hero knight = new Hero();
        knight.name = "Knight of Solamnia";
        knight.isBoss = true;
        knight.hitPoints = 100;
        knight.baseDamage = 50f;

        XMLOp.Serialize(knight, "hero.xml");
    }
}

XML Serialization

As I have mentioned above, to change the behaviour of XML serialization, we need to modify our class with XML attributes. As you can see here there are many XML attributes that control serialization, but in this post, I will only cover the following:

  • [XmlElement]
  • [XmlAttribute]
  • [XmlIgnore]
  • [XmlRoot]
  • [XmlArray]
  • [XmlArrayItem]

Usage of an XML attribute is very simple: you type the necessary attribute above your field or class just like the following:

[XmlElement("Name")]
public string Name;

[XmlElement(String)]

This attribute indicates that a field will be represented as an XML element. Fields of an object is serialized as XMLElement as default, so this attribute is necessary only to change the name of a field (or force an array to act like an element but we will get to that later). Let’s modify our script file and see what kind of XML file it produces:

using System.Xml.Serialization;

public class Hero
{
    [XmlElement("n")]
    public string name;

    public bool isBoss;

    public int hitPoints;

    public float baseDamage;
}

And the output becomes:

<?xml version="1.0" encoding="utf-8"?>
<Hero xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <n>Knight of Solamnia</n>
    <hitPoints>100</hitPoints>
    <baseDamage>50</baseDamage>
    <isBoss>true</isBoss>
</Hero>

NOTE: XML attributes have more constructors with different signatures than this tutorial covers. For example, XmlElement also has XmlElement(String, Type) and XmlElement(Type). However, since this tutorial is planned to be an introductory one, I am not going to cover them here.

[XmlAttribute(String)]

This attribute indicates that a field will be represented as an XML attribute.

using System.Xml.Serialization;

public class Hero
{
    [XmlElement("n")]
    public string name;

    [XmlAttribute("boss")]
    public bool isBoss;

    public int hitPoints;

    public float baseDamage;
}

And the output becomes:

<?xml version="1.0" encoding="utf-8"?>
<Hero xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" boss="true">
    <n>Knight of Solamnia</n>
    <hitPoints>100</hitPoints>
    <baseDamage>50</baseDamage>
</Hero>

[XmlIgnore]

As I have mentioned before, a field must be public in order to be serialized. This also means that all the public fields will be serialized by the XML serializer. But sometimes, we might need a field to be not serialized without making it private. This is where [XmlIgnore] comes into play. It indicates that a field will not be represented in the XML file at all.

using System.Xml.Serialization;

public class Hero
{
    [XmlElement("n")]
    public string name;

    [XmlAttribute("boss")]
    public bool isBoss;

    [XmlIgnore]
    public int hitPoints;

    public float baseDamage;
}

And the output becomes:

<?xml version="1.0" encoding="utf-8"?>
<Hero xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" boss="true">
    <n>Knight of Solamnia</n>
    <baseDamage>50</baseDamage>
</Hero>

[XmlRoot(String)]

The object that is serialized by the XML serializer becomes the root of the XML file. As a result, object’s class name becomes the name of the root tag. Sometimes, we want to use some other name in order to provide clarification. This attribute indicates that the name of the root tag will be the provided string. It should be noted that [XmlRoot] attribute can only be used on classes, structs, enumerations or interfaces unlike the previous attributes.

using System.Xml.Serialization;

[XmlRoot("Knight")]
public class Hero
{
    [XmlElement("n")]
    public string name;

    [XmlAttribute("boss")]
    public bool isBoss;

    [XmlIgnore]
    public int hitPoints;

    public float baseDamage;
}

And the output becomes:

<?xml version="1.0" encoding="utf-8"?>
<Knight xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" boss="true">
    <n>Knight of Solamnia</n>
    <baseDamage>50</baseDamage>
</Knight>

[XmlArray(String)] and [XmlArrayItem(String)]

But, how about arrays? What if our Hero has a int array which includes combo rewards?

using System.Xml.Serialization;

public class Hero
{
    [XmlElement("n")]
    public string name;

    [XmlAttribute("boss")]
    public bool isBoss;

    [XmlIgnore]
    public int hitPoints;

    public float baseDamage;

    public int[] comboRewards;
}
using UnityEngine;

public class XMLSerializer : MonoBehaviour
{
    private void Start()
    {
        Hero knight = new Hero();
        knight.name = "Knight of Solamnia";
        knight.isBoss = true;
        knight.hitPoints = 100;
        knight.baseDamage = 50f;
        knight.comboRewards = new int[] { 1, 3, 5 };

        XMLOp.Serialize(knight, "hero.xml");
    }
}

Output of this code would be:

<?xml version="1.0" encoding="utf-8"?>
<Knight xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" boss="true">
    <n>Knight of Solamnia</n>
    <baseDamage>50</baseDamage>
    <comboRewards>
        <int>1</int>
        <int>3</int>
        <int>5</int>
    </comboRewards>
</Knight>

<comboRewards> tag is OK, but those <int> tags don’t look nice, do they? Fortunately, there are two XML attributes which modify the XML output of an array: [XmlArray(string)] which modifies the name of the array, and [XmlArrayItem(string)] which, as you might have guessed, modifies the name of the array items.

using System.Xml.Serialization;

public class Hero
{
    [XmlElement("n")]
    public string name;

    [XmlAttribute("boss")]
    public bool isBoss;

    [XmlIgnore]
    public int hitPoints;

    public float baseDamage;

    [XmlArray("rewards"), XmlArrayItem("reward")]
    public int[] comboRewards;
}

And this would produce a better output:

<?xml version="1.0" encoding="utf-8"?>
<Knight xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" boss="true">
    <n>Knight of Solamnia</n>
    <baseDamage>50</baseDamage>
    <rewards>
        <reward>1</reward>
        <reward>3</reward>
        <reward>5</reward>
    </rewards>
</Knight>

XML Deserialization

We have successfully saved our game data in an XML file, but currently it is pretty useless, because we can’t take that data back into our game. In order to do that, we need to read the XML file and create objects (namely, deserialize it). Deserialization is simpler than serialization, because we already defined the structure in our class. All we need to do is just add a few lines of code to our XMLOp.cs:

using System.IO;
using System.Xml.Serialization;

public class XMLOp
{
	public static void Serialize(object item, string path)
	{
		XmlSerializer serializer = new XmlSerializer(item.GetType());
		StreamWriter writer = new StreamWriter(path);
		serializer.Serialize(writer.BaseStream, item);
		writer.Close();
	}

	public static T Deserialize<T>(string path)
	{
		XmlSerializer serializer = new XmlSerializer(typeof(T));
		StreamReader reader = new StreamReader(path);
		T deserialized = (T)serializer.Deserialize(reader.BaseStream);
		reader.Close();
		return deserialized;
	}
}

Deserialization process is very similar to serialization process:

On Line 16, we create an XmlSerializer instance with type of T so that it can take any type to deserialize.

On Line 17, we create a StreamReader instance which would read the XML file.

On Line 18, we tell the XmlSerializer to deserialize the stream and cast it to our target type T.

On Line 19, we tell reader to close streaming and then on Line 20 we return the deserialized object.

Let’s say, we have already serialized our weapon object and created the XML file. Now we can deserialize it in another script:

using UnityEngine;

public class XMLDeserializer : MonoBehaviour
{
    private void Start()
    {
        Hero hero = XMLOp.Deserialize<Hero>("hero.xml");
        Debug.Log(hero.name);
    }
}

Conclusion

This concludes our tutorial, and as you can see, XML serialization in Unity (in C# actually) is pretty easy. All you have to do is to use XML attributes in your classes to modify serialization behaviour.

As always, here are the scripts in full. Until next time.