Posts Tagged editor

Event-driven audio

One of the older items on my to-do list was to give my sound designer a way to change the game’s audio without having to recompile the game in Visual Studio and start it up. Based on some of the improvements I made recently, I was finally able to knock that item off my to-do list.

Below, you can see a short annotated video walkthrough where I demonstrate the technique and show how it integrates with XACT.


There are a few key pieces necessary for this to work.

Read the rest of this entry »

Tags: , , , , , ,

Tiled map loader for XNA

Update: Stephen Belanger of Nerd Culture has made some great improvements to the following code, so I encourage you to check out the post about it on his blog.

Early in the development of my game, I used the free and open-source Tiled Map Editor to create levels. It was a big time-saver since it let me worry about more important things instead of investing effort into being able to place tiles down on a map. Later on I decided that the traditional approach to map construction wasn’t right for my project – but I was still glad I’d used Tiled.

Recently I realized that there aren’t very many easy ways for newbie XNA developers to get maps into their games, so I decided it was worth packaging up my Tiled map loader and sharing it with the world. So, I’ve created a simple example that shows how to load Tiled maps in your XNA game on Windows PCs and the XBox 360, and included the loader with it. It’s open-source and free for your use, no strings attached. I hope you find it helpful.

screenshot

Download source code and binaries

Note that it doesn’t have support for isometric tiles or embedded tilesets, because I had no use for either feature. Tiled’s file format is relatively simple, so if you need those features, it should be simple to add them.

And of course, this wouldn’t be possible without the generous contributions of the developers of Tiled, Adam Turk and Bjørn Lindeijer. If you’d like to try it out, you can download it from their website (note: requires Java).

Tags: , , , , , , , , ,

Object model cleanup

Been kind of short on spare time this week, since it’s Hack Week at work and I’m getting things prepared for my housemate moving out. I spent most of my time on cleaning up my object model for level geometry so that it makes more sense and doesn’t have issues with being loaded and saved.

One of the major changes was unifying the way I store object positions and sizes. Previously, LevelGeometry objects each had their own convention, based on type – Rectangles had TopLeft/BottomRight, Triangles had A/B/C, Switches merely had a position, and so on.

The new object model is more consistent: Every piece of geometry has an Anchor, which defines the general position of the geometry. Every other coordinate (if any) is defined relative to the Anchor, which allows you to use the Anchor as a way of moving objects generally. This also means that I can get rid of the Position property I previously had, which was only used for object repositioning in the editor, and for a couple objects that moved at runtime (like doors and platforms) – objects that move at runtime can have a Position property in their RuntimeGeometry object, while the editor can adjust the Anchor property to reposition geometry.

One other challenge is that previously, I converted expressions that I loaded from the level file into functions immediately at load time, and used those functions directly to get their values. The best example is OneWayRectangles – to allow changing the direction of a barrier at runtime, based on the state of a switch or pressure plate, I made their PassableAxis an expression. In the new model, the expression lives in the design-time LevelGeometry object, but the RuntimeGeometry object is the one that needs to evaluate the expression and has the necessary context to evaluate it (for example, a reference to the Game object so that it can find other named objects like switches). This means that while the expression itself can live in the LevelGeometry object, the necessary scaffolding for compiling and evaluating expressions needs to live in the RuntimeGeometry object.

The solution I’m using for expressions right now is a bit of a hack intended to make the game code easier to write: The LevelGeometry object has fields of type GameExpression<T>, which are serialized as a single string (the expression). At runtime, when a RuntimeGeometry object is constructed from a LevelGeometry object, it finds all the GameExpression<>s defined in the LevelGeometry and hands those expressions a reference to the Game. The expression then uses the game to compile a function and temporarily stores the function. This means that the rest of the game code is now able to look like this:

public override void Update () {
    var pa = LevelGeometry.PassableAxis.Evaluate();
    if (pa != _LastPassableAxis) {
        if (_LastPassableAxis.Length() > 0.0f)
            _Game.CameraLookAt(LevelGeometry.Anchor + _Center, 3.0f);
        _LastPassableAxis = pa;
        UpdateArrow();
    }
}

The need for a comparison against the previous passable axis is kind of unfortunate, but it is at least straightforward. The main advantage of this approach is that I don’t need to do any work to keep RuntimeGeometry objects in sync with the expressions defined in the corresponding LevelGeometry object – the necessary work is done at runtime to fix it up. The downside, of course, is that this means it’s not possible to use the same LevelGeometry object in multiple Game instances, and the solution also breaks some OO design principles.

Luckily, having multiple Game instances is pretty much guaranteed to be unnecessary in my case. Design principles tend to matter a little bit, so I spent some time trying to come up with a cleaner, more pure solution to the problem, but I couldn’t find one… so screw design principles. Eliminating duplication is key.

Getting these details of the object model worked out brings me closer to being able to build levels entirely in the integrated editor without having to edit XML files, which is essential if I want to start building real game content out of multiple level files with complex object interactions and geometry, and have the rest of my team be able to make edits and contributions directly without my help.

Tags: , , ,

Level editing III

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.

Tags: , , ,

Level editing II

Over the past few days I’ve done some more work on the level editor. The main things I tried to get working were macrotile editing and geometry editing. I did some work on making it easy to work with multiple layers of macrotiles, by building support for things like snapping the cursor to the edges of other tiles when placing tiles, so that it’s easy to create seamlessly tiled environments.

After I got that working, I started building basic support for editing level geometry. I managed to get a basic implementation of that working so that I can create rectangles and triangles, and reposition/delete existing geometry. I still have to come up with a good technique for saving out levels once they’ve been loaded, since up until this point I was building levels by hand in SciTE. Since lots of level geometry changes after being loaded, I need to come up with a straightforward way of storing the original, intended values, so that loading and immediately re-saving a level does not modify it.

One of the things I had to figure out was how to write the code for handling user input in the geometry editor, since it can take multiple clicks to define a shape like a triangle. The approach I ended up using is based on tasks, and it looks like this in practice:

protected IEnumerator<object> PlaceLevelTriangle () {
    var a = GetMousePosition(true);
    XnaPoint b, c;

    using (var f = CaptureMouse())
    while (!f.Completed) {
        b = GetMousePosition(true);
        _IncompleteGeometry = new LevelTriangle(SnapToGrid(a.ToVec2()), SnapToGrid(b.ToVec2()), SnapToGrid(b.ToVec2()));

        yield return new WaitForNextStep();
    }

    b = GetMousePosition(true);

    using (var f = CaptureMouse())
    while (!f.Completed) {
        c = GetMousePosition(true);
        _IncompleteGeometry = new LevelTriangle(SnapToGrid(a.ToVec2()), SnapToGrid(b.ToVec2()), SnapToGrid(c.ToVec2()));

        yield return new WaitForNextStep();
    }

    _IncompleteGeometry = null;
    c = GetMousePosition(true);
    var vA = SnapToGrid(a.ToVec2());
    var vB = SnapToGrid(b.ToVec2());
    var vC = SnapToGrid(c.ToVec2());

    if ((vB - vA).Length() > 0.0f && (vC - vA).Length() > 0.0f && (vC - vB).Length() > 0.0f)
        AddGeometry(new LevelTriangle(vA, vB, vC));
}

The CaptureMouse function attaches special input handlers to the three mouse buttons that eat all mouse input, and returns a future. When a mouse button is released, the handlers are removed and the future is completed. This allows me to yield a task until the mouse is released, or poll every frame to see whether the mouse has moved. This gives me a simple way to handle multi-click input and prevent clicks from accidentally setting off other code.

Tags: , , ,

luminance is Digg proof thanks to caching by WP Super Cache