Hello again! I’m back again with part 4 of our ongoing refactor series. It’s finally time to cover the first refactor. The last several weeks laid the groundwork for this post and I would encourage you to peruse them if you’re interested in the details. The quick summary is that the game has some basic mechanics implemented using the traditional MonoBehaviour based approach and it is now time to start adding more mechanics to flesh out the game.
As I was working through adding more mechanics to the game such as the Ladybug, and Caterpillars, it became increasingly difficult to manage the shared state between things. For example, while a boulder is being dragged it should track the player’s movement and the player should be in her dragging animation. If the boulder falls off a ledge however, it needs to become disconnected from the player and she should return to a neutral animation. Communicating the status of dragging between the boulder and the player is something that needs to be kept up to date and then checked in most places where either object is being moved to make sure the correct logic is being applied. Using MonoBehaviours for this quickly becomes cumbersome because only some of the logic and data are stored together – either in the boulder or the player – and trying to remember where it is as well as manage all the component lookups/references is distracting from the logic itself. Blightmare at this time was also being built in a way that was fairly general to allow new mechanics to be assembled out of simple attributes. This meant that there were a large number of components required for each gameplay element and the state ownership became problematic when you wanted to use just one half of a mechanic. Often this resulted in adding flags to seemingly unrelated components to disable various portions of logic, or refactoring working implementations so that the data was available to new mechanics. This led to bugs being introduced accidentally because the cognitive load of the code was high – that is, you really needed to know the whole system in order to make a local change. Being the small team that we are, and working part time on the game, we could not afford to test everything all the time or to spend the time to go remember how everything works. If I’ve got an hour to program on Blightmare, I just can’t spend half of that time remembering how things work enough to start making the change I need to make. The last straw for this problem for me was in an intense week just before a demo of the game. We were polishing up some levels and there was an excruciating game of whack-a-bug that really demonstrated just how brittle the whole system had become. The demo went alright, but there were certain places where the people coming to see the game consistently got stuck that we didn’t find in our own testing sessions. As a programmer, these instances are not only embarrassing because I happened to be standing right there presenting the game, but also incredibly frustrating because the problem is inevitably some little thing that just shouldn’t have been possible to do wrong. It was time for a change.
Entity Component Systems and Data-Oriented Technology Stack (DOTS)
The backdrop to all of this is of course the underlying technology (Unity) and what their roadmap looks like. We wanted to be sure to keep relatively up to date with Unity releases so that we wouldn’t end up on an old version that might be unsupported, or have other issues with no ability to fix them. At the time – and to a large extent this is still true – Unity was significantly hyping their new DOTS infrastructure which would eventually become the default way that projects were built. There are a lot of moving parts in DOTS, but the interesting piece for Blightmare was the Entities package which is an ECS implementation. I’ve mentioned an ECS before on this blog, but where it helps the problem I was having is that it expects logic and data to be separated. The structure of an ECS actually helps even more than that because of the way that logic is applied to component sets. Essentially the framework that allows the ECS to run should handle nearly all of the annoying and error-prone reference lookups, state ownership questions, and logic isolation that was really bogging down development. The several theoretical benefits to this paradigm along with the public direction that Unity was talking about everywhere pointed to this being a great way to go.
Rolling Up the Sleeves
A short time after the demo, I had some vacation time. Over the course of that week and a half, I decided to dig right in to figuring out how the Unity Entities API worked and began porting all the Blightmare features that we had at the time to it. It took a bit of fiddling to really get an understanding of how to make things work in the new framework, but after trying out some super simple things, I came up with some patterns that seemed to work out pretty well and then I applied that to everything. During this period, we were essentially supporting 2 copies of the game because there was still active design work happening that needed maintenance for bugs or new features here and there. All of that work needed to be duplicated in the ECS version to make sure that when the refactor was complete, things would continue to work. The single most significant benefit of this refactor was how nicely encapsulated each piece of logic was. A small piece of the game state is passed to each piece of logic and then the updated state is written out again, which has very nice predictable properties. There is no internal state to any of the logic functions, so it’s very predictable what will happen given some input and that means that logical dependencies between systems are now exclusively represented in data and only observed at the boundaries between functions which means knowledge of that function is all that’s required to make a change. All in all, I think the complete refactor took a couple of months which translates to about a week of full-time work. That’s not too bad for the benefits that seemed to be presenting themselves.
Obviously this is the tale of two refactors, so we’re not out of the woods yet, but at the start the grass definitely looked greener. Next week I’ll discuss some of the problems we faced with the ECS version of Blightmare. Don’t worry, the ride isn’t over!