Make the most out of serialisation to extract your data from your code and iterate more quickly during your game development phase!
This article is also available on Medium.
As you start to work on bigger Unity projects, you’ll probably soon start to see that data is a crucial part to any game, but also pretty hard to organise and maintain. In particular, it’s sometimes difficult to properly separate between what’s dynamic and what can safely be extracted to some static storage.
But we often have some purely data-related C# classes that we can clearly extract from our main codebase and store as external data files.
Today, I want to discuss why it’s interesting to do so and how we can benefit from some Unity built-ins to quickly set up data storage and serialisation using the XML format. Ready? Then let’s dive in! 🙂
Why use data files instead of direct variables?
Throughout this tutorial, we’ll work on loading and saving our hero skills data. So let’s say we have a
Skill class that looks like this:
For example, we could have a “Fireball” skill, like this:
When you start a small Unity project, the quickest and easiest way to create a bunch of
Skill instances and access them from anywhere is to define some static class somewhere, or a manager script. There, you could have a list of all the available skills for your hero.
But now, suppose we have this list of
Skill instances: at the beginning, we’ll only have two or three
Skill items; however as we start to have a few more, and perhaps other lists, and relationships between all of those, the
Data class will get too big to remain easily maintainable.
Moreover, suppose we compile this code. By the end of the project, this will probably take a while because the game will have gain in complexity. And then, after compilation, we test the game for a while and discover that it lacks balance. Thus we decide that the “Fireball” should cost less mana deal more damage; or that another “Ice Storm” skill should in fact be named “Blizzard”. If we directly embed this data in the code, we will have to fix the error and recompile everything. Then, when we spot an other error that is purely data-related, we’ll need to recompile all of it another time. This will quickly become painful.
A much better idea is to extract pure data from the code and load it from files on the system. This way, changing the cost of a unit or the requirements for a spell will be as quick and easy as changing a line in a file somewhere. And the game will automatically reload this new data next time it is launched – without having to be recompiled from scratch!
Actually, an “extreme” version of this data-driven code philosophy is the Bytecode programming pattern where you don’t really code anything but an interpreter for your data files… and then all the meat of your game comes from the content of your files. Bob Nystrom gives a brilliant explanation of it in his “Game programming patterns” book (among lots of other gems) – I really encourage you to check this book out, it’s a must-read for any game developer! 😉
How to store data?
Now the question is: what sort of data file should we write?
The most obvious file type is a plain old text file. The advantage is that it can be written and opened on any computer, given how simple it is. The drawback, however, is that it gives you a headache when you want to parse it. If we plan ahead and try to anticipate the possible evolutions of our
Skill class for example, we quickly see that there might be things like: the type of target (something more complex than just “self” or “not self”), its possible upgrades, perhaps its dependencies to other technologies…
In short: the class is already composed of too much data for a text file to be manageable, and it will probably get much worse in the future!
Instead, we can use XML files. The Extensible Markup Language, or XML, is a human-readable markup language that works with tags, just like HTML, and makes for clear and well-structured data storage. Let’s be honest, it can be hard to use in really huge projects, or when you have data with dynamic structures. But most of the time, it works well for games. Plus, C# has a built-in XML parser which makes it really straight-forward to use this type of data file 🙂
So far, we’ve defined a
Skill class with the following five properties: a code, a name, a mana cost (as an int), an amount of damage points (as a float) and a boolean flag to know whether it targets the caster or an enemy.
This can be translated to an XML of this form, for example, for the “Fireball” skill:
Note that this is arbitrary and that you can choose another structure for your buildings XML files. Here, I decided to have a “Skill” root node, then put the code and name as attributes and the damage and cost as standalone XML nodes.
I will store all of my skill XML data files in a new
Data/Skills folder at the root of the
Of course, you could also store all of the skills in a single
Skills.xml file and have a “Skills” root node, then a list of “Skill” child nodes with the structure described above… 🙂
Note: the JSON format is also a very common choice because it is just as readable, and Unity also has a built-in JSON serialisation scheme!
Preparing our test workflow
If you’ve been working with Unity for a while, you might have noticed that constantly toggling the “Play” mode on and off isn’t always the best: it takes a while to start and stop, and it has you switch between the various tabs in your Unity editor.
Here, we want to iterate quickly and test lots of little tweaks again and again; and we don’t really need any complex in-game logic and runners! So the ideal thing would be to run everything in edit mode – and that’s totally doable 😉
The trick is to define a little
DataManager class and to use the
[ContextMenu] Unity built-in attribute to add some actions to the script’s menu, that we can run directly in the editor:
At that point, you see that if you add this script to an empty game object in your test scene, you’ll have some additional entries in its contextual menu to run your function:
So we can now very easily call methods from our
DataManager in edit mode, without having to actually run and wait for our “game” – pretty cool! With that set up, let’s dive into the real meat of the article and talk about converting data back and forth between C# and XML…
Reading XML data into Unity
First, let’s see how to load data from XML.
The process of taking data written into XML and “injecting” it into your Unity game as a C# object is called deserialisation: you take the serialised XML data and unpack it so that it can be used by your game logic.
There are two ways you can go from XML to Unity C# objects: you can do it manually, by parsing the various XML nodes and attributes and building your C# objects; or you can use Unity’s
XML.Serializer tool to populate your C# instance automatically from the XML data.
The first technique is interesting, but it has a bit more overhead for the programmer and, with such a simple structure, it doesn’t really have any benefits compared to using the
XML.Serializer. Thus let’s directly jump to the automated version 😉
Parsing basic data with the
To let Unity directly translate XML files into instances of your class, all you need to do is pass your
Skill objects to an instance of Unity’s
XMLSerializer tool, like this:
DataManager script expects me to pass the name of the XML data file to load in the
skillToLoad variable, and I can then run my
But you’ll notice that we are missing some of our properties! For now, the
code and the
name are empty… the deserialiser was not able to fetch them automatically, because we wrote those as attributes and not child nodes!
To fix this, we need to better prepare our
Skill class and give more detailed info to the
Using non-default serialised structures
Unity’s XML serialiser is quite powerful, but it can’t infer everything on its own, and in particular by default it assumes you mostly use XML nodes to specify the nested data. In our case, all the attributes are therefore ignored, and our loaded “Fireball”
Skill instance is incomplete.
So we need to tell the serialiser explicitly that the
name properties are to be retrieved from attributes, rather than child nodes; this is done by adding the
[XmlAttribute] attribute to the field in the C# class:
If I re-run my
LoadXMLData() method, I see that now the serialiser fills my
Skill instance data correctly:
Sometimes, you might also want your C# class properties to be named differently from the nodes in your XML file; for example, perhaps “damage” is too long to write and you’d rather have the XML node have the
dmg tag. Or perhaps you want to change the tag of the root node in your XML file, associated with your class.
To do this, you can use the
[XmlElement] attributes to tell the serialiser the name of the tags to look for:
You can even ignore specific properties with the
[XMLIgnore] attribute, or deserialise more complex structures such as arrays with your own tag re-mapping… don’t hesitate to take a look at the Microsoft docs for more details!
Writing C# Unity objects to XML
Conversely, the reverse process of writing C# objects into the XML format is naturally called serialisation. And it’s actually about as easy to do! This could be pretty useful if you want to test out some values, tweak them in your game, and then save the result as an XML file for future reloads.
For example, let’s create another skill, this time purely in C#, and export it to XML, using another function,
If we take a look at our newly generated XML file, we see that we get a nice output with the expected format (plus some auto-generated XML-namespacing, but you don’t need to worry too much about this):
We could load and save our
Skill C# objects back and forth using our XML serialisation/deserialisation process, and we now have an easy way of tweaking the values in or out of the Unity editor!
A final note: abstracting away the serialised type
In this article, I focused solely on my
Skill class and wrote all my XML serialisation/deserialisation processes so that they handle or create instance of this class. But the real strength of Unity’s XML serialiser is that it works regardless of the type it’s given: as long as you’ve added the right attributes on your properties, the logic to load or save the data is exactly the same.
So you could very quickly make a generic XML load/save class that you’ll then be able to call with any C# serialisable class you want (like your unit data description, or a info on a level…), as shown in this nice article from the GramBlog:
Extracting your data from your code is important because it makes your Unity project more robust and maintainable, and it clearly dissociates the behaviour from the configuration. Some dev patterns like Bytecode, however, sort of regroup those two by defining the behaviour through the data, too.
There are lots of formats you can use for your data files, but the XML format is quite handy because it is readable, easy to edit and quick to implement into your Unity/C# project thanks to the C# built-ins.
What about you? Do you use XML often in your projects? Or do you use other formats? Feel free to tell me in the comments, and also share your ideas for other Unity tutorials! 🙂