Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

Using XNA's XML content importer, is it possible to read in an array of objects with different subtypes?

For instance, assume these are my class definitions:

public abstract class MyBaseClass
{
    public string MyBaseData;
}

public class MySubClass0 : MyBaseClass
{
    public int MySubData0;
}

public class MySubClass1 : MyBaseClass
{
    public bool MySubData1;
}

And this is my XML file:

<XnaContent>
    <Asset Type="MyBaseClass[]">
        <Item> <!-- I want this to be an instance of MySubClass0 -->
            <MyBaseData>alpha</MyBaseData>
            <MySubData0>314</MySubData0>
        </Item>
        <Item>  <!-- I want this to be an instance of MySubClass1 -->
            <MyBaseData>bravo</MyBaseData>
            <MySubData1>true</MySubData1>
        </Item>
    </Asset>
</XnaContent>

How do I specify that I want the first Item to be an instance of MySubclass0 and the second Item to be an instance of MySubclass1?

share|improve this question
    
What logic do you use to say that the first instance of Item should be MySubClass0 and the second instance of Item be MySubClass1? Is this an arbitrary designation, something where you just know that this is the way things are? Or is it some property in the XML file? Because it really should be the latter. –  Nicol Bolas Apr 2 '12 at 4:58

2 Answers 2

up vote 3 down vote accepted

I managed to answer my own question by hand creating an instance of my object array and then following the instructions in this article to write the array to xml: http://msdn.microsoft.com/en-us/library/ff604982.aspx

Looking at the output, it appears I needed a Type attribute on each Item node to specify the type. So my modified XML file looks like this:

<XnaContent>
    <Asset Type="MyBaseClass[]">
        <Item Type="MySubClass0"> 
            <MyBaseData>alpha</MyBaseData>
            <MySubData0>314</MySubData0>
        </Item>
        <Item Type="MySubClass1">
            <MyBaseData>bravo</MyBaseData>
            <MySubData1>true</MySubData1>
        </Item>
    </Asset>
</XnaContent>
share|improve this answer
1  
Please mark this as the correct answer when you are able to. –  Jonathan Hobbs Apr 3 '12 at 2:54

Are you writing your own XML importer/exporter, or using the built-in IntermediateSerializer? If you're writing your own, then you have the answer to your own question since you're the one implementing it.

Otherwise, I believe that the built-in IntermediateSerializer just knows to treat deserialized collections as collections and assumes that the named members of your top level objects that contain these collections are able to accept these objects as collection items, otherwise it'll bork. It'd be a pretty lame serializer if you couldn't instantiate subclasses to stick in a base class collection from XML.

I say "believe" because I haven't tried to examine the IL or anything like that. Just going based on my own futzing with it.

I put my first whack at a component system on github just recently, and this is what IntermediateSerializer (should be XNA 4, if not it's 3.1 and 4 likely exports similar data):

https://github.com/michaelbartnett/DaHooch_XnaComponentArchTest/blob/master/Project%20Da%20Hooch/xmlTest.xml

Here's a snippet:

<Level xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Entities>
    <Entity>
      <Enabled>true</Enabled>
      <UpdateOrder>0</UpdateOrder>
      <Name>PLATFORM1</Name>
      <Aspects>
        <Aspect xsi:type="RenderingAspect">
          <Name>renderer</Name>
          <DrawOrder>0</DrawOrder>
          <Visible>true</Visible>
          <CoordinateType>World</CoordinateType>
        </Aspect>
        <Aspect xsi:type="SpriteAspect">
          <Name>sprite</Name>
          <Scale>
            <X>1</X>
            <Y>1</Y>
          </Scale>
          <LayerDepth>0.2</LayerDepth>
          <ImageFile>Sprites/platform</ImageFile>
          <OrientationAspect>collider</OrientationAspect>
          <CoordinateType>World</CoordinateType>
          <DrawOrigin>
            <X>256</X>
            <Y>32</Y>
          </DrawOrigin>
          <Tint>
            <R>255</R>
            <G>255</G>
            <B>255</B>
            <A>255</A>
            <PackedValue>4294967295</PackedValue>
          </Tint>
          <FlipEffects>None</FlipEffects>
          <ImageFrame>
            <X>0</X>
            <Y>0</Y>
            <Width>512</Width>
            <Height>64</Height>
            <Location>
              <X>0</X>
              <Y>0</Y>
            </Location>
          </ImageFrame>
        </Aspect>
        <Aspect xsi:type="BoxCollisionAspect">
          <Name>collider</Name>
          <Position>
            <X>300</X>
            <Y>350</Y>
          </Position>
          <Rotation>0</Rotation>
          <Dimensions>
            <X>256</X>
            <Y>32</Y>
            <Width>512</Width>
            <Height>64</Height>
            <Location>
              <X>256</X>
              <Y>32</Y>
            </Location>
          </Dimensions>
          <Mass>100</Mass>
          <IsStatic>true</IsStatic>
          <CollisionGroup>0</CollisionGroup>
          <Enabled>false</Enabled>
          <UpdateOrder>0</UpdateOrder>
        </Aspect>
      </Aspects>
      <IsTemplate>false</IsTemplate>
    </Entity>
  </Entities
</Level>

You'll notice that IntermediateSerializer doesn't even bother specifying the type of <Entities> or <Aspects>, presumably the Content XML importer knows what to do when it sees a collection.

This is an XML file I generated using the IntermediateSerializer documented on Shawn Hargreaves' blog. The solution is split up into the Content project, and Engine project, a Game project and one Editor project. The only difference between Editor and Game is some #ifdefs that determine whether to export the current state of the scene as as an XML file, or to load an XML file from the Content project so I could see exactly how it was getting serialized and deserialized.

Here's the stripped down game class to illustrate what I'm talking about:

class Game1 : Microsoft.Xna.Framework.Game
{

    private Level level; // Level class contains Entities, which are built out of Aspects (component system)

    /// <summary>
    /// LoadContent will be called once per game and is the place to load
    /// all of your content.
    /// </summary>
    protected override void LoadContent()
    {
#if EDITOR
            FirstLevel.LoadFirstLevel(level, this); // Static class method that instantiates game objects
#else
        level = Content.Load<Level>("xnaXmlTest2");
#endif
        level.Load(this);
    }

    /// <summary>
    /// Allows the game to run logic such as updating the world,
    /// checking for collisions, gathering input, and playing audio.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Update(GameTime gameTime)
    {
        currentGameTime = gameTime;
        var keyboardState = Keyboard.GetState();

        if (keyboardState.IsKeyDown(Keys.Escape))
        {
#if EDITOR
            if (levelMode == SAVE_MODE)
            {
               Entity player = Entity.GetEntityByID(FirstLevel.PlayerID);
               string filePath = Content.RootDirectory + @"\..\..\..\..\Content\xnaXmlTest2.xml";
               FileStream file = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite);
               IntermediateSerializer.Serialize<Level>(XmlWriter.Create(file), level, filePath);
            }
#endif
            this.Exit();
        }

        level.Update(gameTime);

        base.Update(gameTime);
    }
}
share|improve this answer
1  
Thanks for the answer Micheal. I am indeed using the built in serializer. I managed to answer my own question by hand creating an instance of my object array and then following the instructions in this article to write the array to xml: msdn.microsoft.com/en-us/library/ff604982.aspx Looking at the output, it appears I needed a Type attribute on each Item node to specify the type. Like this: <Item Type="MySubClass0"> –  Mcguirk Apr 2 '12 at 6:21
    
Glad to hear you solved the problem, remember to post your edit as an answer and mark it. –  michael.bartnett Apr 2 '12 at 7:51

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.