Then-Versus-Now: How I tackled the issues of Unity’s Spaghetti-prone Architecture, and What Went Wrong Before?

A forewarning: This is a bit of a technical post. I’m going to try and distill things down and keep as close to English as I can, but I wanted to provide some insights as to “What went Wrong?” with the programming side of the last version of the game, and how we’re addressing it. I also want to share some of our processes, and maybe give a peek behind the curtain for anyone interested! Game development is hard, but I love the challenges and I would love to answer any questions, or explain any concepts I can to folks who are interested.

So what went wrong? That’s a tough question… the short answer is, “It’s complicated…” From a programming perspective it boils down to the fact that Unity, for all it’s strengths, is very prone to “Spaghettification,” or encouraging all the code to become incredibly inter-connected. This means even small changes to a piece of code, even a piece of code that seems to be totally disconnected from the rest of the game code, can have rippling changes that break everything else, which makes the team less willing to make major changes even when they need to happen.

  A very simplified version of the difference between the two architectures. See how in the new architecture, changing the Audio Engine only breaks a single connection, while in the old architecture, it breaks four connections. This is very simplified, some of the old systems touched as much as 20 other systems at a time, and there were several floating tangled islands like this that would sometimes break huge swaths of the game even when small changes were made, resulting in the old game code becoming totally unmanageable. Not to mention, some changes would ripple out and compound; changes in one class led to changes in the next, which broke the next 20 connections… Small changes required massive overhauls.

A very simplified version of the difference between the two architectures. See how in the new architecture, changing the Audio Engine only breaks a single connection, while in the old architecture, it breaks four connections. This is very simplified, some of the old systems touched as much as 20 other systems at a time, and there were several floating tangled islands like this that would sometimes break huge swaths of the game even when small changes were made, resulting in the old game code becoming totally unmanageable. Not to mention, some changes would ripple out and compound; changes in one class led to changes in the next, which broke the next 20 connections… Small changes required massive overhauls.

There’s a lot of ways to combat this problem and everyone has their own favorite way to solve it. For me, implementing a “Message Bus” using elements of the Command Pattern and Observer Pattern resulted in the best bang for my development buck. What this means is, every discrete system connects to a central messaging bus with a series of “Commands.” Objects can send commands (or data) to one another without directly referencing each other. It also has the pleasant side effect of allowing the human behind the keyboard to use a Debug Window while testing the game, which greatly speeds up debugging time by an astounding factor, and means we can pass more testing off to non-programmers.

How does this fix things? Well… We’re not unwilling to make large (or small) changes to the game when problems are found. Indeed: Changes are now pretty quick and painless. What does this mean for development? It’s happening a lot faster, and without any of the pain. Interacting with systems is simple. Adding new systems is simple. Removing old systems is simple.

Anyway, that’s a brief look under the hood. Next time we’ll go over how we’ve overhauled our development processes!