Coordinate, combine, and conquer in this high-action co-op game for two to four friends. Reclaim your planet from swarms of alien RoboBugs using the only weapon you have left, your own life-force. Grab a seat, find some friends, and give it your all in the ultimate cooperative experience. This is our development blog where you can follow our progress as we work on the game.
So it’s been a while since I’ve written a dev blog post, and we’ve been showing a lot of art previews on twitter and here in the past few months. Code isn’t nearly as pretty to look at, but I figured I should talk about some of the tech stuff for those interested in our latest stuff under the hood. We recently just fixed a huge issue with our level creation pipeline and I wanted to write about it so here we go!
Core Tech within Unity
The worlds in Tetrapulse are created procedurally. We design individual level chunks and combine them randomly to create the unique level layouts, giving the game more variance and hopefully keeping the experience fresh.
A level chunk, with characters for size reference
Up until now our pipeline has been pretty tedious for iteration based on two Unity default features- Prefabs and Lightmaps. For those unfamiliar with Unity I’ll briefly explain both:
What are Prefabs?: Think of a sandwich. You construct a sandwich with different ingredients and condiments in a particular layout. If you wanted to reconstruct the same exact sandwich, you would have to save information about where every single ingredient was inside of your sandwich, how much of each condiment, etc. Prefabs are Unity’s way of letting you save information about complex objects and allowing you to create copies of them as needed. This is how we save level chunks!
What are Lightmaps?: Usually 3D games are comprised of complex lighting. Lights are pretty taxing on your machine to compute, so the more complicated and numerous your lights, the slower your game will potentially run. One workaround is to save information about lights that don’t move. This information is stored inside of lightmaps. Lightmaps are generated inside of Unity or whatever engine/lighting system they use during development and when the game is running, instead of manually computing the dynamic lights every frame, it can use lightmaps as a shortcut to get a very similar if not higher quality of lighting (though this does take up memory).
Problems
So we use both of these features pretty extensively. Each system has its quirks, and two quirks in particular were giving us headaches and causing us a lot of time:
Problem with Prefabs: Unity’s implementation of prefabs has a limitation where it doesn’t allow automatic updating of objects more than 2 layers deep. Our level chunks sometimes go 5 or 6 layers deep, so this is a problem for us! The most obvious example is if we update a model in the project, the prefabs don’t automatically update the model if it exists inside of them. This means we’d have to go in and manually replace the broken links to the new model in every level chunk every time we updated assets. This was really stupid.
I eventually made an editor script that let you define a new model along with the name of the old model to replace. It would perform a name search on all objects in a level chunk and swap the new object out for the old broken link. This was a decent workaround, but still a huge time sink and not very efficient.
Problem with Lightmaps: Because our levels are randomly generated, the level geometry can be created potentially anywhere in the game world. This technically goes against that part of lightmaps where you bake information about lights that don’t move, but we actually discovered that lightmaps are generated relative to where a light is placed. This means that as long as the geometry stayed consistent relative to the light positions, lightmaps would still work!
There was still a problem though- Unity only generates one set of lightmaps in a scene, which means that if we tried to bake lightmaps for a single level chunk it would delete the ones we created for the OTHER level chunks. This is because there is a single array of lightmaps that Unity references when using the built in lightmap system. For Unity folks- This array can be found in LightmapSettings.lightmaps (and can be modified at runtime! More on this soon).
Our workaround for this was to generate lightmaps in a scene with every level chunk present. We would stack all of the level chunks 1000 units apart or so and tell the lighting system to bake everything in the scene. This takes a long time. Our minimum number of level chunks for a given environment is 13, but even for just 13 chunks it takes about an hour to bake lights! And if there’s any errors that occur, we have to rebake all of the chunks AGAIN! Seriously this was the worst.
Fun fact: Our character artist David Finseth actually made the level chunk stacker while I was busy with other things a while back. It’s nice having artists who can occasionally code <3
Anyway, these problems were seriously causing some hindrance for iteration. Any time we wanted to update art assets or tweak lighting, everything would come to a grinding halt. We would try to get other work done so we could just stay away from this part of the project unless we really had to. Our designer Jorge would often lose a lot of work because of asset updating issues or just have to throw away work because we’d want to put in new assets just as he finished tweaking any layouts he was working on.
As you can probably guess, pipeline issues slow down development and lower moral. This is all a huge problem because that means design iteration also suffered, not just art! It should be exciting to create new level chunks and iterate on the look of things, not dreadful.
Killing Two Birds with One Stone:
We needed a solution that ultimately allowed for the following behaviors:
Bake lights on a single level chunk without influencing others
Have models update automatically in all level chunks when overwriting old ones.
Our solution ended up being accomplished through one tool and hardly any additional logic to get things working properly, and it only took an afternoon to get it working! Here’s what it took to make these features possible:
Offline/In-Engine Tool:
Instead of creating level chunks as prefabs, create them as scenes. Scenes can have as deep of a hierarchy as you’d like and still properly maintain references to assets and prefabs.
Automatically select all desired mesh renderers in a level chunk and have Unity bake lights for the scene. (ref images below)
Move the generated lightmaps into a resources folder and properly name & index them according to their level chunk and relative light map index within that level chunk.
Create a code snippet that generates a level chunk prefab from a scene. That way the existing level generation system can still be used and all the work is done offline instead of any special additive scene loading logic at runtime.
Ground Renderers Selected:
Environment Renderers Selected:
Runtime Logic:
At runtime, when a level chunk prefab is loaded: populate the LightmapSettings.lightmap array with the associated level chunk lightmaps and go through all renderers within a level chunk and offset their lightmap index value (For devs, any Renderer object has a lightmapIndex value that tells a renderer which lightmap in the array it should use).
Hooray!
That’s it! Lightmapping and random level generation can finally live in perfect harmony in our game. Hopefully this is helpful for those using lightmaps in any semi-dynamic environments. If you have any other questions or feedback about our solution you can get ahold of me on twitter @david_laskey or tweet at our studio @amiablegames!
We've added a new character to Tetrapulse! Meet Mooshi the Mushroom Knight. She is a lovable character that packs a punch. With her three-headed carnivorous staff she fearlessly leads the team into battle, sending Robobugs flying to and fro.
Check out her gameplay video here
Ready for some exciting news!? After five long months of hard work and development the game is finally ready for early beta access. This is where the fun begins and where the game really starts to take shape. Find out more in out latest Kickstarter Update