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.


