Over the past week most of my work on the game has been on editor-related things, along with some improvements to the collision detection code.

One of the changes I had to make was support for level geometry that’s made up of multiple polygons. This allows me to have geometry that isn’t convex, like an ellipse or a concave slope. Not too much work involved, really; I just needed to add some for loops in a few places and write a simple triangulation routine.

Getting curved surfaces to work was a bit of a challenge, but that was mostly just because I had to spend an hour or two figuring out the right formula to represent the slope of the surface. Most curves ended up being concave instead of convex, as well, which necessitated the work I mentioned above.

The main thing I worked on, though, was getting everything set up so that I could do loading and saving from the editor without issues. I had to split out my level into ‘design time’ and ‘run time’ data structures so that I could load a level, run around for a few minutes, and then save it without any of the contents of the file changing. Things like the position of a switch needed to be separated from the state of the switch in a straightforward manner.

I ended up going with a simple approach based on graph traversal. I treat the level as the root of a tree and each piece of geometry or tile in the level is one of the leaves. This allows me to handle serialization generally by writing out the entire graph as XML with some type annotations, which looks like this (various crap edited out for readability):

<?xml version="1.0" encoding="utf-8"?>
<level>
  <graph>
    <node key="1">
      <node key="2">
        <node key="3">
          <node key="4" />
          <node key="5" />
          <node key="6" />
          <node key="7" />
          <node key="8" />
          <node key="9" />
          ...
        </node>
      </node>
    </node>
  </graph>
  <nodes>
    <types>
      <type id="0" name="XnaGameTest.Level" />
      <type id="1" name="XnaGameTest.LevelLayerList" />
      <type id="2" name="XnaGameTest.LevelLayer" />
      ...
    </types>
    <values>
      <Level key="1" typeId="0" />
      <LevelLayerList key="2" typeId="1" />
      <LevelLayer key="3" typeId="2" />
      <LevelTile key="4" typeId="3">
        <Position>
          <X>944</X>
          <Y>-256</Y>
        </Position>
        <Tile>bgwall1</Tile>
      </LevelTile>
      <LevelTile key="5" typeId="3">
        <Position>
          <X>1136</X>
          <Y>-256</Y>
        </Position>
        <Tile>bgwall2</Tile>
      </LevelTile>
      <LevelTile key="6" typeId="3">
        <Position>
          <X>1328</X>
          <Y>-256</Y>
        </Position>
        <Tile>bgdoor3</Tile>
      </LevelTile>
      ...
    </values>
  </nodes>
</level>

Once I have one of these files it’s really simple to load – I just create a XmlGraphReader and point it at the XML and I get out an INode object that represents the root node of the graph. In this case, I know the root is going to be a Level, so I cast it to the right type and I’m basically done.

One thing worth noting is that the XML only contains the ‘design time’ objects for the level. So, once I’ve loaded a level, I need to do some work before it can actually be played. What I do is walk over the entire graph, and for every LevelGeometry object, I call a virtual method called CreateRuntimeGeometry and pass in a reference to the game. Every type of LevelGeometry knows how to create its runtime counterpart, so the code ends up looking like this:

    public class LevelRectangle : LevelGeometry {
        public Vector2 TopLeft;
        public Vector2 BottomRight;

        protected override IRuntimeGeometry CreateRuntimeGeometry (Game game) {
            return new RuntimeRectangle(game, this);
        }
    }

    public class RuntimeRectangle : RuntimeGeometry {
        public RuntimeRectangle (Game game, LevelRectangle rect)
            : base (game, rect) {

            var poly = new DrawablePolygon(
                rect.TopLeft,
                new Vector2(rect.BottomRight.X, rect.TopLeft.Y),
                rect.BottomRight,
                new Vector2(rect.TopLeft.X, rect.BottomRight.Y)
            );

            _DrawList.Add(poly);
            _CollisionList.Add(poly);
        }

There’s some more magic behind the scenes, but that’s basically it. By splitting the objects out into two structures I have an easy way to handle saving levels and saving games, without getting state mixed up – whether a door is currently open is stored in the runtime object, but the position of the door is stored in the design time object.

One of the main bits of work left for me to do is refactoring the code so that I can get rid of the Position property of level geometry. The property has the unfortunate attribute of being used by both the editor and objects like doors at runtime, which means I can’t save it in the level file or in saved games, because it is modified both at design time and at run time. Once I’m done I expect that the design time part of it will be removed entirely, with the editor operating on the actual coordinates of the objects, and the run time part will remain but no longer be used by the editor.

If you’re curious about how the graph serialization stuff works, you can find the source code here. It has a few unit tests as well.