In Part 1 of this article we discussed why we chose to use procedural level generation. Were also described how to generate a procedural level’s layout and connect multiple smaller level chunks together to form a fully coherent level. In part 2 of this article we discuss how we overcome lighting and NavMesh problems in Unity, and how tospawn NPCs based on a tempo.
With procedurally generated path, it is likely that the same chunk will be loaded multiple times for each level. The duplicate meshes within those chunks are stored in memory separately, but the lightmaps can be shared by adding code to our level loader to amalgamate all the lightmaps by scene name. Lightmap indexes are then reassigned as they load, saving memory instead of duplicating lightmaps for those chunks.
In order to ensure the level chunks line up properly with their baked lighting and shadows, need to bake each scene sharing a theme with the same lighting settings. It found that using an “ambient source” setting of “skybox” resulted in lighting that was slightly different in each chunk and showed obvious seams where the chunks connected. This was clearly not ideal and we experimented with different lighting setups to find the best option. Author found the best results by setting the “ambient source” to “gradient” which provided near perfect connections without visible seams. The following image shows the difference.
Unity’s NavMesh system was used for all purposes. When we designed this procedural system, Unity did not support stitching together multiple NavMeshes dynamically at runtime. Unity has since started adding this feature to the new beta in version 5.6, but at the time of this article it is still incomplete. So we had to solve the problem of loading multiple random level chunks and having each of them work together with a single NavMesh.
This system actually works quite well and barely affects the loading time of levels. The main restriction is that algorithm cannot have varied NavMesh elevations within level chunks because they only cut out of the single flat NavMesh that exists in the parent scene. There is no way to know what chunk will load where beforehand, so elevation changes can’t be builded into the NavMesh because each level layout is random and dynamic. So author upgrade system to support the stitching of multiple NavMeshes with varied elevations as him update to Unity 5.6 in the future.
After we loaded each chunk and laid out our paths, author wanted to add more variety to what was inside those chunks to ensure duplicate chunks loaded for a level would not look exactly the same. To achieve this Procedural Obstacle Volumes that generate random props and cover objects within each level chunk was created.
Procedural Obstacle Volumes
Procedural Obstacle Volumes represent a volume of space where randomly selected prefabs such as cover objects are spawned. They allows to add more variety within level chunks, even when duplicate chunks are loaded.
First, a component called “ProceduralObstacle” that is added to every prefab that can be loaded through the system was created. The component tracks the bounding box size of the prefab along with some tags and other settings. Anytime the prefab gets updated in the editor, it is automatically updated in a global manifest ScriptableObject.
Next,Procedural Obstacle Volume game objects were placed inside our level chunks. These volumes have settings that let the us pick the prefabs that are allowed to spawn in them based on their tags and direction facings. This is all based on the seed that is generating the level (so it’s deterministic and works across the network for COOP games). Then used a standard bin packing algorithm to position the prefabs into the volume as tight as we can with a buffer around them so that mercenaries can still path around them.
Over time we’ve added a fair number of additions to this system to make it easier for level designers. For instance, user can click an editor button to see what prefabs might load in a volume before the game is even running. This is a quick way to test what might get generated in the volume based on its settings.
NPCs and Tempo Curves
When creating each level chunk for M.E.R.C., there are need to strategically place NPC spawn points within each chunk; however, author don’t want them all to spawn when the game runs because that would just be mayhem. Instead we determined which spawn points will trigger when a procedural level loads based on a Tempo Curve.
A Tempo Curve is a simple concept that came up with to represent the “beats” of a level’s tempo. Using Unity animation curve graphs user can control how easy and hard the level gets as you progress through it. An example is shown below.
When a procedural mission is loading it has a base mission difficulty and we randomly pick a Tempo Curve from a list to use for its tempo. Evaluated that the Tempo Curve points relative to each other. This means that wherever points appear on the chart, our code finds the lowest one and considers this zero or “Don’t Spawn“. It then finds the highest point and consider that “Much Harder“. It averages out where to put the other challenge modifiers in equal increments between the lowest and highest points as follows:
- Don’t Spawn (lowest point)
- Much Easier (spawn much easier NPCs)
- Easier (spawn easier NPCs)
- Baseline (matches the mission difficulty)
- Harder (spawn harder NPCs)
- Much Harder (highest point)
For example, if the level’s main path ends up having 9 chunks, the above Tempo Curve points are mapped out along that path from start to end. The first chunk maps to “Don’t Spawn“, the 4th point (or 7th chunk) maps to “Much Harder“, and the last point (or last chunk) maps to “Don’t Spawn“. All the other chunks would map to their points on the curve and determine what tempo to use for spawning NPCs in those chunks. The result is a beat for how the level flows in terms of NPC difficulty. The above chart starts slowly and builds up to a big battle before the end on the main path. Almost every chunk in this example will spawn some NPCs, except for the first and last chunks, so there will be a constant NPC presence.