Building Mini-Engines in Unity
Published 2 years ago
Streamlining Rapid Content Implementation
Before I started working on Super Wall Crash, a lot of the games I developed in Unity consisted of a pretty standard workflow that I think a lot of Unity developers go through.  Create a scene, drop in some assets, create some prefabs, write your code, and test until happy.  This workflow is probably where most Unity developers start and for good reason, it's incredibly easy to get up and running with realtime results working exactly the way you want them to.  Want to tweak that building model?  Update it and drag in a new copy.
However, when our team started designing Super Wall Crash we knew it was going to be a procedurally generated world with randomized elements.  That means we build small chunks of the world (such as a ground tile, tree, or house) and have a set of scripts run to piece it all together (like a big puzzle).  Games like Minecraft and Crossy Road both are procedurally generated in this manner with varying algorithms that 'put the puzzle pieces together'.  This flips the workflow described above onto it's head because rather than having complete control over every spot of our scene we wrote pieces of generation logic that would do it for us, giving us an infinitely generating world at the expense of less control over the specifics of the scene.
Let's look at a few examples where we lifted away the burden of hand-placing specific assets and content, and let our 'mini-engines' do the work for us.
Developing the algorithm for procedural generation the way we wanted it wasn't an easy task, especially considering none of us had ever worked with procedural generation code or even understood how it worked.  This caused a lot of up-front development time being spent on script coding as we would place in a few basic art assets, run the generation, and rewrite sections that didn't work the way we wanted.  In essence, the algorithm was written to do a few basic steps:
  1. Determine where to place a new chunk of ground (a Tile)
  2. Generate the tile at the target location in the targeted Biome
  3. Potentially generate a hazard for the player on top of the new Tile (Debris)
The important step in regards to this writing is Step 2: Generate the tile at the target location in the targeted Biome.  That is important because we need to take into consideration a different Biome (i.e. "level").  Since each Biome is basically it's own self-contained unique level with it's own art style the Biome is extremely important when determining what art assets to use.  Remember above where we talked about dragging and dropping in art assets within a scene?  Since our entire scene is generated randomly at runtime by a script, what we needed was a solution to load in art assets dynamically and generate them as needed in the algorithm.
Any Unity developer worth his or her salt knows the power of Resources.Load() and it's variants.  We combined the use of a simple JSON parser with Resources.Load() to read through the JSON list of Biomes (including details like Tile prefab names, Debris prefab names, and other generation-specific properties) and pass those parsed results through Resources.Load() which would then load all our Biomes including their generation properties and all art.
The Biome Mini-Engine
It's important to realize at this point that essentially what we wrote is our own 'mini-engine' powered by Unity.  Why is this so important to game development?  While we spent more time up-front writing all this mini-engine functionality we are left with a self-contained system that significantly reduces the implementation time of future game content.  If we want to add a new Biome, all we do is create the art, build the prefabs, and save the specifics into the JSON file; the engine does everything else.
One other spot we did this in Super Wall Crash is the character select screen.  After the sceen was functional (basic UI navigation, selecting a runner) we focused on creating a reusable set of development steps that would help us get characters into the game as fast as possible.  Again using Resources.Load(), this time without a JSON backing file, we simply load every character prefab in a specific character folder and display them on that screen.  Giving them similar components and animations, only varying character properties (name, purchase cost, display order, etc) where applicable, means once a character is modeled and animated all we need to do is create/save a prefab (which takes about 4-5 minutes).  Again the mini-engine does everything else for us.
Why Does This Matter?
There's a particular complaint with many games, often indie games, that shows up more often these days: This game needs more content.  Unfortunately, in most cases this is an accurate criticism of the game.  We can't all be massive game studios employing hundreds of people dedicated only to creating new content.  We knew as we developed Super Wall Crash (being 3 designers and only 1 full developer) that art creation was our biggest bottleneck and that we would be spending an extremely large amount of time on this game before launching it with the amount of content we wanted in the final product.  However after implementing the above 'mini-engine' strategies we significantly helped reduce the strain on that bottleneck, allowing us to release our game with a base amount of content and consistently update it with new content in a short amount of time thanks to an extendable codebase built on top of what Unity already provided us.
The mobile market is always updating, mobile games are always getting new content in all kinds of forms.  But I want to stress that even though Super Wall Crash and a lot of this article is about a procedurally generated mobile game, that you and other developers should strive to find ways to build your own 'mini-engines' that allow for rapid development once they're completed.  Imagine placing your game on the mobile market, Kickstarter, Steam Greenlight, or any other market and being able to push out content in an evolving codebase to keep your players happy and engaged over time.  Oh, what a great procedurally generated world that would be!