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.