4
\$\begingroup\$

I'm trying to load levels in XNA from XML files. I have currently got a system to do this working, but it looks like it might get horribly complex as I add more objects later on. My world currently consists of Planet entities along with physics props, scenery, static props, etc. It will later include NPCs, interactive machines, and a lot more content (but I want to sort this out before going any further).

Based on a tutorial I read, my level loader loads a LevelData entity from XML. This contains arrays of PlanetLevelData, PropData, and StaticData. My test file is as shown:

<?xml version="1.0" encoding="utf-8" ?>

<XnaContent>

<!-- TODO: replace this Asset with your own XML asset data. -->

    <Asset Type="DataTypes.LevelData">`

        <PlanetData>
            <Item>
                <Position>5.2 5.2</Position>
                <BodyRadius>300</BodyRadius>
                <FieldRadius>450</FieldRadius>
                <Name>Jupiter</Name>
                <Rotation>0</Rotation>
                <ImageName>-</ImageName>
            </Item>
            <Item>
                <Position>-2 -2</Position>
                <BodyRadius>300</BodyRadius>
                <FieldRadius>450</FieldRadius>
                <Name>Jupiter2</Name>
                <Rotation>0</Rotation>
                <ImageName>-</ImageName>
            </Item>
        </PlanetData>

        <PropData>
            <Item>
            <Type>Crate</Type>
            <Position>2 3</Position>
            </Item>
        </PropData>

        <StaticData>
            <Item>
                <Type>FromImage</Type>
                <Position>-4.5 -4.5</Position>
                <ImagePath>Lamp</ImagePath>
                <Rotation>2</Rotation>
                <Radius>0</Radius>
                <Vertices></Vertices>
                <PlanetOwner>Jupiter2</PlanetOwner>
            </Item>
        </StaticData>

    </Asset>

</XnaContent>

In other words, I have to put all my content files into arrays. However, for me to have different types of StaticData from the XML file, I have to specify the type with a String (called Type).

I then load the XML using Content.Load<LevelData>("name here.xml"). I go through every PlanetData in the array and create a new Planet based on it's properties. I also use switch statements based on the Type field to specify which type to create...

Is this the only way to load level data into objects using XML (I hope not, it's hideous to use). I couldn't really find much on this topic...

\$\endgroup\$
4
  • \$\begingroup\$ I'm planning on writing my own level editor. I just want a way of loading the objects easily from a file... \$\endgroup\$ Mar 2, 2013 at 15:27
  • 2
    \$\begingroup\$ possible duplicate of How to use XML files as content files in XNA? \$\endgroup\$
    – House
    Mar 2, 2013 at 16:21
  • 1
    \$\begingroup\$ Are you sure XML is the best way? You know you could save a lot of space by using binary formats. \$\endgroup\$ Mar 2, 2013 at 16:23
  • 3
    \$\begingroup\$ user1306322: If you include your XML file in the content project, it will compile it into a compressed binary format. \$\endgroup\$ Mar 2, 2013 at 17:49

3 Answers 3

4
\$\begingroup\$

The content project supports XML files, and it uses a special type of serializer called IntermediateSerializer, which is better suited for game data than XmlSerializer.

No need to read your PlanetData and figure out how to convert it to a Planet, you can directly serialize a Planet and have it seamlessly loaded at runtime. In your editor, you can create a Planet object and serialize it to XML:

Planet Jupiter = new Planet();
Jupiter.Position = new Vector2(5.2, 5.2);
Jupiter.BodyRadius = 300;
Jupiter.FieldRadius = 450;
Jupiter.Name = "Jupiter";
Jupiter.Rotation = 0;

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;

using( XmlWriter writer = XmlWriter.Create( "Jupiter.xml", settings ) )
{
    IntermediateSerializer.Serialize( writer, Jupiter, null );
}

This serializes an individual Planet object called Jupiter.xml that you can load with your content manager just like you would load a texture or a model. Include it in your content project and call the following code at runtime:

Planet jupiter = contentManager.Load<Planet>( "Jupiter" );

If you want to serialize a single Level object containing all of your actors, just fill out your Level class with its members and serialize the Level object in the same fashion. When you load the Level object it will already be filled with all of the appropriate objects.

Shawn Hargreaves has an excellent series of posts on the IntermediateSerializer, under the "IntermediateSerializer" heading:

http://www.shawnhargreaves.com/blogindex.html

\$\endgroup\$
5
  • \$\begingroup\$ Cool! I'll have to check out the link you provide because I'm not fully understanding what you did there but this looks like a pretty good alternative to my option though both answers would definitely get the job done. \$\endgroup\$ Mar 2, 2013 at 17:52
  • \$\begingroup\$ If you serialize your data, add/remove properties to your objects, and de-serialize, does it break the deserialization? \$\endgroup\$
    – ashes999
    Mar 2, 2013 at 18:09
  • \$\begingroup\$ Yes, it breaks the deserialization, which has caused me plenty of headaches. For new variables, you can get around it by putting the attribute [ContentSerializerAttribute( Optional = true )] on the new variable. Removing variables still makes me re-serialize all my content. \$\endgroup\$ Mar 2, 2013 at 18:16
  • \$\begingroup\$ This looks really good, but how do you load polymorphic data? For example if I had ElectricalAppliance as a subclass of PhysicsProp, how would I load that from an xml file? \$\endgroup\$ Mar 3, 2013 at 7:37
  • \$\begingroup\$ It handles polymorphism seamlessly; just do it like I did in the original post. IntermediateSerializer outputs the type of the class. You should never have to touch the XML yourself, IntermediateSerializer is very robust. \$\endgroup\$ Mar 3, 2013 at 16:22
5
\$\begingroup\$

There may be a way to do this with the content pipeline, but I have done it without and instead used standard XML Serialization.

The approach I am about to show is just one way you can structure your data and may or may not be relevent to what you are trying to accomplish. My goal with this answer is to provide you enough direction so that you will be able to create your own level structure and apply it to your game.

To start with I have an XML file in my content project that looks something like this:

<?xml version="1.0" encoding="utf-8" ?>
  <Screen>
    <TileSet Width="16" Height="16">
      <Tiles>
        <Tile GroupId="G" TileId="1" TextureName="Generic" Path="tempSprites/Terrain" X="9" Y="3" />
        <Tile GroupId="G" TileId="2" TextureName="Generic" Path="tempSprites/Terrain" X="10" Y="3" />
        <Tile GroupId="G" TileId="3" TextureName="Generic" Path="tempSprites/Terrain" X="11" Y="3" />
      </Tiles>        
    </TileSet>
    <ScreenDefinition Width="40" Height="24" BaseGroup="G">
      <TileReferences>
        <TileReference Id="G2" X="0" Y="0" />
        <TileReference Id="G3" X="15" Y="0" />
        <TileReference Id="G2" X="15" Y="15" />
        <TileReference Id="G3" X="0" Y="15" />
      </TileReferences>
    </ScreenDefinition>
  </Screen>

All XML Attributes and sub elements you see are properties within the object class.

So following this, Screen is our root elemment and is a class within my project. This class contains a TileSet object property and a ScreenDefinition object property. The code for this class is as follows:

[XmlRoot()]
[Serializable]
public class Screen
{
    public Screen()
    {
    }

    [XmlElement]
    public TileSet TileSet;
    [XmlElement]
    public ScreenDefinition ScreenDefinition;
}

TileSet looks something like this:

[Serializable]
public class TileSet
{
    public TileSet()
    {
    }

    [XmlArray]
    public List<Tile> Tiles { get; set; }

    private int width = 0;
    [XmlAttribute]
    public int Width
    {
        get { return width; }
        set { width = value; }
    }

    private int height = 0;
    [XmlAttribute]
    public int Height
    {
        get { return height; }
        set { height = value; }
    }
}

The key part of this class is the [XmlArray] attribute on the Tiles property. This is what helps define that object to be deserialized into a collection of Tile objects when loaded.

The rest of the data classes I will not show because they follow the same general pattern that you have seen here with these two classes.

To implement this it is incredibly painless. First you need to do some configuration to your XML file so that its not processed by the content pipeline.

  1. In Solution Explorer, click your XML file
  2. From the properties window, Set the Build Action property to "None"
  3. From the properties window, set the Copy to Output Directory property to either "Copy always" or "Copy if new" (whatever fits your needs)

This sets us up so that you can access the XML file right from the file system. Assuming you added the XML file to a Content project the file will be in the bin/Content/Your/Folder/Structure/Here folder.

Now you are ready to actually load the data from the file, and its as easy as this:

XmlSerializer serializer = new XmlSerializer(typeof(Screen));
StreamReader reader = new StreamReader(filePath);
Screen screen = (Screen)serializer.Deserialize(reader);
reader.Close();

Anywhere you see the type "Screen" is going to be what you've defined as your root element in your XML file and in code (see the XmlRoot() attribute on the Screen class).

And with all of this you should be able to dynamically load all your XML data into objects at run time. The data setup can be tedious depending on your data structure but it works really well once you learn the ins and outs and all the caveats of serialization.

\$\endgroup\$
9
  • 1
    \$\begingroup\$ Thanks - but unfortunately my game is not even remotely tile-based... \$\endgroup\$ Mar 2, 2013 at 21:18
  • 1
    \$\begingroup\$ Tiles have absolutely nothing to do with it. This concept can be applied to any sort of data structure. I explained these concepts in enough detail where you should be able to apply it to whatever your situation is. I am only going to provide you with an example. I will not write your code for you. \$\endgroup\$ Mar 3, 2013 at 4:51
  • \$\begingroup\$ Sorry! I read this over in detail, and this looks incredibly useful, exactly what I was looking for! Thank you! \$\endgroup\$ Mar 3, 2013 at 7:42
  • \$\begingroup\$ So, the Tile class will have [XmlAttribute] public char GroupId {get and set code}, am I right? \$\endgroup\$ Mar 3, 2013 at 7:46
  • \$\begingroup\$ Yup. I actually just used strings but the concept is the same :) \$\endgroup\$ Mar 3, 2013 at 8:05
3
\$\begingroup\$

There's definitely ways to make this a lot less tedious.

  1. Your level file should describe the entities and their relationships in the world, so that when you deserialize the XML through ContentManager.Load, you get a Level instance that is ready to use. No additional processing needed.

  2. The reason you find it tedious is because you are MANUALLY adding all the content in the XML file. What you should be doing instead is simply adding all the stuff in the editor like usual and then simply let the IntermediateSerializer do the work for you when you save the level.

    var xmlSettings = new XmlWriterSettings
    {
            Indent = true,
            IndentChars = "\t",
            NewLineChars = "\n"
    };
    
    var destinationFileName = "[DestionFilename]";
    using (var outputFile = XmlWriter.Create(destinationFileName, xmlSettings))
    {
            IntermediateSerializer.Serialize<SceneGraph>(outputFile, scene, destinationFileName);
    }
    

This can then be loaded using step1.

NOTE: The SceneGraph above is my "Level".

UPDATE:

@WilliamOsborne They are loaded like any other object. InterMediateSerializer automatically adds the Type attribute when serializing and loads the appropriate type when deserializing.

Assume that your Galaxy has a list of astronomical objects in a list:

    class AstronomicalObject { .. }         //Base class
    class Planet : AstronomicalObject { .. }
    class Asteroid : AstronomicalObject { .. }

    class Galaxy
    {
        public List<AstronimicalObject> AstronomicalObjects
        {
            get;
            set;
        }
    }

When the Galaxy is serialized the output will look something like this:

    ...
        <AstronomicalObjects>
            <Item Type="Planet">...</Item>
            <Item Type="Planet">...</Item>
            <Item Type="Asteroid">...</Item>
        </AstronomicalObjects>
    ...
\$\endgroup\$
3
  • \$\begingroup\$ What about derived classes? How would I load those in XML? \$\endgroup\$ Mar 3, 2013 at 8:11
  • \$\begingroup\$ @WilliamOsborne I've updated my answer for you. \$\endgroup\$
    – Dadgron
    Mar 3, 2013 at 11:39
  • \$\begingroup\$ I found the answer on Shawn Hargreaves' site, but thanks anyway! \$\endgroup\$ Mar 3, 2013 at 12:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .