Reading XML file data in an XNA game

The question of how to read XML files seems to come up multiple times every day on the AppHub forums. For most games, using the Content pipeline is overkill. Fortunately, there’s an easier way – LINQ to XML.

Say you’re working on a dungeon crawler and you’ve got an XML file that describes rooms in the dungeon:

<?xml version="1.0" encoding="utf-8" ?> 
<Rooms> 
  <Room> 
    <id>1</id> 
    <width>10</width> 
    <height>10</height> 
    <locationx>2</locationx> 
    <locationy>3</locationy> 
    <items> 
      <item> 
        <id>5</id> 
        <locationx>3</locationx> 
        <locationy>7</locationy> 
      </item> 
    </items> 
  </Room> 
  <Room> 
    <id>2</id> 
    <width>5</width> 
    <height>5</height> 
    <locationx>15</locationx> 
    <locationy>25</locationy> 
    <items> 
      <item> 
        <id>2</id> 
        <locationx>1</locationx> 
        <locationy>1</locationy> 
      </item> 
    </items> 
  </Room> 
</Rooms>

So you’ve added this file containing this data to your Content project. Make sure you change the Build Action for the file to Content and the Copy to Output Directory to Copy always.

How do you get this data into some objects that are easy to work with in your code. One simple way would be to create two classes:

    public class Room
    {
        public int id { get; set; }
        public int width { get; set; }
        public int height { get; set; }
        public int locationx { get; set; }
        public int locationy { get; set; }
        public List<Item> items { get; set; }
    }

    public class Item
    {
        public int id { get; set; }
        public int locationx { get; set; }
        public int locationy { get; set; }
    }

All that needs to be done to get this data into a list of Room objects is:

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Xml.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 Microsoft.Xna.Framework.Storage;

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

        List<Room> rooms;

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

        protected override void Initialize()
        {
            System.IO.Stream stream = TitleContainer.OpenStream("Content\\Rooms.xml");

            XDocument doc = XDocument.Load(stream);

            rooms = new List<Room>();

            rooms = (from room in doc.Descendants("Room")
                       select new Room()
                        {
                            id = Convert.ToInt32(room.Element("id").Value),
                            width = Convert.ToInt32(room.Element("width").Value),
                            height = Convert.ToInt32(room.Element("height").Value),
                            locationx = Convert.ToInt32(room.Element("locationx").Value),
                            locationy = Convert.ToInt32(room.Element("locationy").Value),
                            items = (from i in room.Descendants("item")
                                     select new Item()
                                     {
                                         id = Convert.ToInt32(i.Element("id").Value),
                                         locationx = Convert.ToInt32(i.Element("locationx").Value),
                                         locationy = Convert.ToInt32(i.Element("locationy").Value)
                                     }).ToList()
                        }).ToList();


            base.Initialize();
        }

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

        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            base.Update(gameTime);
        }

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

            base.Draw(gameTime);
        }
    }
}

 

The only problem with using LINQ to XML, that I haven’t figured out a way around, is that debugging the query is basically impossible. If you don’t have the query exactly right you’ll get an error and you may not know what the problem is. In my case some missing parentheses in getting the Items list set up correctly caused me some problems and it wasn’t immediately obvious why. Part of that is probably my n00bness in using LINQ. Hopefully Microsoft will come up with a way, if it doesn’t already exist, to make debugging easier.

10 Comments

  1. [...] This post was mentioned on Twitter by asdeoz, Jim Perry. Jim Perry said: My longest blog post so far this year. Hopefully a sign of things to come. :) http://bit.ly/efSlMO [...]

  2. Satijn says:

    Thanks, I have looking for days for a xna tutorial like this!

  3. jeffery says:

    how do i know about how this works?
    from room in doc.Descendants(“Room”)
    select new Room()

  4. jeffery says:

    It seems your getting room which is descending down from rooms item is my guess but still confused.

  5. MrCorzell says:

    The LINQ expressions are a bit more in the functional language paradigm than most C#. You’re essentially doing a select (like in SQL) on any Room element and building that up into an IEnumerable which then you simply are saying .ToList() on to get the final result that is more digestable for the game. The same goes for the items inside each room. The functional / imperative mix of C# can be quite tricky at first if you’ve never seen a functional language.

  6. SinthiaV says:

    Do you know your code blocks are being heavily clipped on the right on my very wide screen? I also see lots of white space on both sides.

  7. Ritual says:

    Hey your tutorial is just what i was looking for.
    adding more to your tutorial.. how would you cycle thru the ites if you had more than one under the same level.?

  8. Mach X Games says:

    That’s not XML, unless I’m not seeing it correctly. As for looping through the items (which I’m guessing is what you meant), you would select the items just like I do the rooms. You can then loop through them just like any other array.

    • Ritual says:

      sorry it was xml, the website changed it. not sure how to put xml tags in here without it been changed.

      thanks i found out that it already gets all of the items if there is more then 1.
      now my other question..
      what if i just wanted to pick a specific room lets say room 1 instead of loading every single room how can i search for a specific room and load that one.

      the way i have it is

      creating a Xelement called leveldefinition
      going thru rooms
      foreach (var room in rooms)

      checking for room i want
      it so
      leveldefinition = room

      then i add your code just changed doc to leveldefinition..

      when i run it it dosent look like it goes inside
      rooms = (from room in doc.Descendants(“Room”)

      i can upload my code if i knew how

Leave a Reply