Hello and welcome to another Blightmare dev blog!  Things have been progressing slower than we like around here, but we’re getting closer and closer.  We’ve had all our mechanics built for some time now and that has allowed us to really experiment with combining them in ways that we didn’t originally consider.  In doing so, we’ve discovered behaviors that we wish worked or customizations that we want to make in some cases that were not anticipated originally.  All of this is very normal and is one of the most fun parts of the development process.  The problem we’ve run up against is that it’s become increasingly difficult to make additions or changes to the editor.  Recently I started down a path towards fixing this to pave the way for quick explorations of bosses and setting up the decorations that will give our levels life.  Today’s post will be a quick look at what the main problems are and what approach I’m taking to fix them.

 

There are 2 main problems that I set out to fix:

  1.  Adding a single editable property to a mechanic requires changing at least 5 places in code as well as adding some UI in a prefab.  This is only for simple properties that don’t need visualization in the editor.  For anything more complex, it’s even more work.  This cost is just far too high.
  2.  Getting the editor state to exactly match what is being serialized into the level is a difficult process which dramatically increases the amount of testing that needs to be done for any given change.  Without this testing, the editor may stop working in some cases or otherwise hinder the ability of our designers to work which is time that we cannot afford to lose.

 

There are 2 general strategies that I’ve used to address the problems mentioned above:

  • Centralized state
  • “Immediate Mode” UI

 

We introduced Immediate Mode User Interfaces previously so I won’t repeat that here, but the extension that I made is to create several utilities that work specifically in the grid system that we use for level design which allows me to think about the level preview itself in an “immediate mode” paradigm.  To show a simple example of how this works, consider the case of our mushroom mechanic.  This is a platform that alternates between 2 points (low and high) which are offset from a base.  The original code to implement this in the editor was rather complex and consisted of several classes that exposed events for when data should change and required coordination between one another to make sure all the changes were recorded properly to update the visual preview, the UI values, and the draggable grid handles.  The code that we now use to implement these features is in 2 classes: a preview class and a mushroom specific class.

 

The preview class is shared by nearly every other mechanic and boils down to a very simple implementation:

foreach (var item in items)
{
    Vector2Int location = EditorState.GetItemLocation(item);
    GridUtils.Image(location, Preview);
}

That’s it.  This shows the preview image that we want in the proper place on the grid.  We’re able to specify the place every frame which means that it is never out of sync with the underlying position data.  This ability to avoid duplicating state is incredibly powerful because of the sheer simplicity of code that it allows.

 

Similarly, the draggable grid handles are implemented like this:

foreach (var item in items)
{
    var data = EditorState.GetMushroomData(item);

    GridUtils.Draggable(GridUtils.CreateGridId(item, 1), ref data.LowPoint);
    GridUtils.Draggable(GridUtils.CreateGridId(item, 2), ref data.HighPoint);

    EditorState.SetMushroomData(item, data);
}

 

You can see here that the Draggable function is given a reference to the original data which is then modified if it should be, and we can then just always replace the state when we’re done.  This guarantees the data is always up to date, no matter what other system we may add later that changes it in ways we didn’t anticipate.

 

Both of these examples rely heavily on the other technique which is the centralization of state.  Our original editor design tried to keep state encapsulated into layers: items, decorations, scripts, etc.  Our tools were then organized to operate on layers.  This became problematic when we wanted to implement things that interacted with multiple layers, or tried to share tools across layers.  The state encapsulation combined with a retained mode UI meant that we needed to keep copies of data in all the places that needed it and then build significant coordination infrastructure to make sure it was all consistent.  The new model keeps all the state in a single place.  There are a bunch of lists of data in our EditorState class which is available always to anyone.  That doesn’t mean that we are actually changing data all the time in all the places, but it means that it is very easy to make a change when we want to.  This allows quick prototyping which can then be refactored into something more reusable later if we decide it’s something we want to keep.  Our undo/redo system from before became too much hassle to keep up with and some features just never worked well with it.  The new system that has a single source of truth will be very easy to build undo/redo into because the system can be built centrally and won’t require integration in all the places that we might want to use the data.

 

As I write this, the changes are still very much in progress, so I may come back and eat my words, but so far things are looking great and I’ve been able to remove many duplicated pieces of UI and code which is a good sign.  I’m sure there will be more posts about this topic however it ends up, so stay tuned!  Please head over to Steam and put Blightmare on your wishlist if you haven’t already and follow us on Twitter to get the latest updates about the blog and the game.  Have a good day and a great week!