Developing video games is often a matter of elegance, a struggle between limited resources and dreams that just won’t let us quit. Great game makers know how to infuse each element of their games with love, dread and heartbreak. They toil with benedictine care to use as little as possible to achieve moments that stay with you forever. Today we’re going to talk about the other kind of game development.
There is wisdom, great wisdom, in following the sage advice of the great developers: think simple, stay within the bounds of what your lowest common denominator can accomplish, try to be clever about reusing your resources.
This philosophy led to gaming jewels with universal appeal that enjoyed instant success: Crossy Road, FTL, Papers Please, and The Room series are spectacular examples of what can be accomplished with a self-contained, sparse environment that is used to the fullest to deliver a great gaming experience.
So, with that great wisdom in mind, and armed with Unity3D’s rapid prototyping superpowers, we set off to make an arcade action game with monstrously deep levels, thousands of moving parts in each of those levels which are, naturally, randomly assembled at runtime. Because what good are the lessons of the past if you don’t ignore them, now and then?
Salvage Heroes is a top-to-bottom vertical scroller set in a post-apocalyptic world where “Salvagers” brave the depths of dilapidated deathtrap buildings to rescue relics from a golden age past, for profit and glory. It’s developed in Unity3D, and features unlit 3D environments that we spruced up with a dynamic darkness system.
The levels contain up to 200 floors, which are a mixture of predesigned chunks and randomly generated connecting sections, and end up containing upwards of 8000 elements once you factor in tiles, moving gameplay elements and props. Our first challenge was to fit all of that into something that would run on a decent variety of mobile devices.
There was much profiling, and much wailing of CPUs. Cases ran hot, and processors screamed. The main issue was that, in a “Journey” level, the entire level has to be calculated at startup, because certain key components have to have guaranteed level placements. The game oscillated between slow runtime and enormous loading times as the levels were generated. Then, we got clever.
As is usual in a craft ruled by the strict tyranny of elegance, the solutions were simple, and had to be used in creative ways to obtain our desired results.
Use tile maps.
Yes, it’s 2016 and devices are powerful enough to run without tiling tricks and all that stuff.
We know, we used to think the same. Thing is, once you reduce your level to a tile map, you can get away with massive, 200×16 grid sizes without worrying about instancing a thing.
In our tests, generating a full level of up to five thousand floors deep could be accomplished in milliseconds even on modest devices. So yeah, forget all that modern talk, and tile map the heck out of your games. Even if your graphics end up being relatively tile-independent, having everything in a tile map simplifies much of the instancing and logic behind the game.
Then pool your pools. Then pool them too. Final pass? You got it, pool one more time.
Instantiating a prefab in Unity (and in any other engine) is relatively inexpensive, but does incur in a lot of first-time initialization penalties. Deleting an object is anathema: just don’t. Even if you’re feeling dangerous, try not to delete objects at runtime. Without getting too technical, removing an object makes the game reshuffle the entire memory heap, and that is what causes stuttering on your first mobile game.
Pooling means that you just instantiate the objects you need once, and then keep re-using them, never actually deleting them, but rather putting them away somewhere out of the way until you need them again.
Good news is that once your level is an abstract grid of integers as per the previous step, you can get clever and pool every single aspect of the game: tiles, props, interactive pieces. They all can be instanced at startup in the right measure, and used and reused constantly to only render a small part of your large level.
Pooling tiles especially had the most dramatic effect on performance. It essentially freed us from the level depth limit. If you’re only going to make your device render 30 floors at a time, then your actual tile map can be thousands of floors deep at nearly no cost after it’s generated. Pooling, in case it wasn’t clear, is your friend. On any device.
We went through many iterations of our level generation system. It started as a simple random generator. Then we went with loading entire designed levels. Then, we tried something in between. Then, we had to find the most efficient way to store and generate tiles at runtime.
At the moment, our level generation completes before the loading screen even fades in. This is largely because it no longer needs multiple passes of the level’s tile map to figure out the exact nature of each piece. The pieces themselves figure out which floor they’re on and use the correct material and configuration every time they’re called back from the pool. It’s a minor runtime delay (16 elements every second or so), and a massive reduction in level generation time.
This is, by the way, one area where Unity3D excels. By not having to wait for lengthy compile times, by being able to dynamically adjust key component configuration values live, in the inspector, while your game runs – those are things that even something as mighty as Source or Unreal can’t manage.
No, really. Without mercy.
Even with all the optimisation, there were certain gameplay elements that required multiple tile map iterations to configure correctly. Most notorious were impassable walls, which had to feature exits to the right and left on both of the floors they influenced. They also had to be placed where they would make sense, so far away from limit walls, and they couldn’t drop the player on deadly tiles.
In the end, the rule set for creating them ended up being quite heavy to generate at runtime because of the multiple passes required to ensure all conditions were being met, and we noticed that they ended up being placed in the same three or four places which best fit our conditions.
So we cheated, and removed them from the generator. Impassable walls now only feature in the game’s designed chunks, where our all too human minds can come up with all kinds of creative positioning that a reusable rule set would never have been able to implement. This last step is perhaps the least engine-dependent of the lot, because it requires an understanding and mastery of a host of tricks and techniques that you can’t learn anywhere else but in game development. But it’s so very, very worth it.
Learn to get the most out of your tools.
Object-oriented coding is great, right? All those components, nicely ticking along to themselves, so self-contained, uncoupled from the universe, so like a textbook.
And yeah, OO coding is great, except that sometimes you just have to apologize, rap it on the head, and do what you have to to make your game run fast.
There was one particular issue we had with the sheer number of pickups, deadly tiles, doors, and other elements that need to check player distance and position. No matter how light we made their Update code, just having them in the scene incurred in a massive (5ms) penalty. IL2CPP made things faster, but is way too unstable on Android and so we couldn’t rely on it. That’s when we read this blog on the Unity… eh, blog site: http://blogs.unity3d.com/2015/12/23/1k-update-calls/
There. Boom. Instant speed boost of roughly 200% in most cases – which of course allowed us to go in and add even more things to the game. Learn to use your tools, they will surprise you.
At the end of this, I have the same advice for you that the greats have, if not by wisdom by trying to avoid the pain of past mistakes: think small, compact. Craft your experience around simple rule sets that combine together for endless, fun gameplay permutations.
If you absolutely must bring to life your dream of rocket-powered post-apocalyptic archeology, don’t. If you still can’t shake that dream, then we hope to have provided some insights in how to get away with it and still remain as relatively sane as a game developer can be.
Salvage heroes is out now for both Android and iOS, and it’s free – actually no-microtransactions, no-scummery free. Head over here for more information and links to the relevant app stores.