For some time now I’ve been asked if I would do a session on the “Darkside of MonoGame” about using XML with MonoGame and the Content Pipeline, for a while I put it off as I had my own schedule and agenda with the channel. Eventually I got worn down and I’ve succumbed to the demands of my viewers (it can happen )
The video for this post can be found here if you prefer video:
<iframe title="Getting Started with MonoGame using XML" src="//www.youtube.com/embed/wCc_rLrqsIo?rel=0&enablejsapi=1&origin=https%3A%2F%2Fwww.gamedeveloper.com" height="100%" width="100%" data-testid="iframe" loading="lazy" scrolling="auto" class="optanon-category-C0004 " data-gtm-yt-inspected-91172384_163="true" id="930250473" data-gtm-yt-inspected-91172384_165="true" data-gtm-yt-inspected-114="true"></iframe>This and more content can be found on my dedicated MonoGame channel here: http://bit.ly/darksideofmonogame
Why use XML?
For as long as there have been games, there has been a need to generate game content outside of the game and avoid writing all this pesky code stuff, everything from:
Level design
Lists/designs of characters and items
Scripted events
Just because some article said it was a good idea on CodeProject
It can be very powerful to use, if wielded correctly and it gives power to your content / mod creators of your game. Any situation that requires lots of configuration or walls of text just works better if it’s separated from your code base and manageable outside of the core code.
Why use the MonoGame Content Pipeline?
Loading text (which is all xml is at the end of the day) can be slow, very slow if it’s pages long, so you don’t really want to be doing that at run-time inside your game. Sure, you can but should you? You can hide this behind clever loading screens or whilst the main menu is up, but at the end of the day your game needs to:
Load in the file direct from disk as a stream
Close the stream
Create an XML Serializer
Parse through the stream and generate the data
Destroy the serializer (esp if you like your memory back)
Rinse / repeat for every file
Don’t get me wrong, there is nothing wrong with this path specifically but it is wasteful and if anything goes wrong or you mistyped something in the XML, it’ll only fall down flat on its face when you run/load that file. You can just create a separate tool to validate the XML for you but that’s even more work to do.
With the content pipeline in MonoGame however, the majority of this work is done offline when you are building your project, in fact the XML validation happens inside the pipeline itself so you don’t even need the game in order to test and build the XML, it can all be done separately and you know it will all “just work”™.
Another benefit is both size and compression. When using the Content Pipeline, all the assets are specifically serialized and compressed for each platform to cater with all their specific differences. How you binary serialize and deserialize on one platform is different to some others (just ask console developers!).
Lastly is ease of use and support. With MonoGame, we inherited the IntermediateSerializer which the XNA god Shawn Hargreaves created for XNA. This little helper which he crafted between builds, greatly helps with serialization and can even support lists and dictionaries out of the box (as well as some xml performance improvements). That combined with turning every XML asset in to “just another asset” which is loaded from the Content Pipeline just like anything else, loading it becomes as simple as Content.Load<MyXML>, easy.
Setting up your data
Right, when getting started with XML, you need to understand there are three main components to handling XML serialization with MonoGame, the XML schema, the Data Class and the Game code:
image
This is a simple architectural principle to deal with when handling any content that is provided externally to your project (or internally in some cases) whereby you have a rigid schema, which will be populated by an unlisted source (conforming to the schema) and then consumed by game functions and logic. The last two can be merged but can likely create troubles later if you start manipulating the wrong data, so my advice is to simply keep them separate, for example:
My Data schema has the following properties:
Level name
Level Difficulty
Array of Enemies
Array of Items
List of Exits
Now if the level loads with a certain number of enemies and the player starts killing them, do you simply start hacking away at the Enemies collection held in the XML? Doing so means you need to completely reload the XML should the player wish to restart the level. Alternatively, you might have a separate array either to track which enemies have been killed and then simply keep track of who’s dead or who isn’t. All comes down to the style of game.
Another view is if you use the Data Schema to run your game, or if you simply refer to the data within specific game functions, for example compare these two classes:
A Class extending schema
public class MyLevel { //Data //The name of the Level public string LevelName; //Number of enemies at the start of the level public int MaxEnemies; //Time Limit to complete the level public float TimeLimit; //Functions public void KillEnemy() { //Do Kill } }
Class with data property
public class GameLevel { //The current loaded level data MyLevel currentLevel; //Current Enemies private int enemyCount; //Time progressed in this level private float levelElapsedTime; //Functions public void KillEnemy() { //Do Kill } }
One example simply extends the data class with additional functions (mixing data with function), the other takes the data in to itself and then works with it. My recommendation is to use the second as it keeps a clear line between what is your loaded data and what you do with that data in your game but ultimately, it’s up to you.