The procedural level generation in Connected Caves

Posted by (twitter: @AlexanderBirke)
August 30th, 2014 10:54 am

For Ludum Dare 30 my friend Sam Chester and I decided we wanted to focus on creating something with procedural generation. From the theme we got the idea of having the player explore an infinite complex of connected caves, which also ended being the name of the game.  I thought it would be fun to share the method I came up with for creating the levels. The Unity project is also available on github for the brave and bold.

Mmmm fog, lots of fog

Step 1: Generating the base cave geometry

I designed the generation to take place in separate stages so it would be easier to make sure each part worked on it’s own, and so we would have something done at the end of the jam. For generating the numbers used to create the cave we used unity’s build in Random class. Before the level generation takes place, we seeds it with either a value put in by the player or the seed assigned to the portal the player just used to exit the last cave they visited. The first step is to generate the overall shape of the cave. This is done with a series of randomized Bezier curves that forms segments of the wall:

Red lines are Bezier curves. Green lines connect the primary control points

Red lines are Bezier curves. Green lines connect the primary control points

The dimensions of the cave is the most important feature, since it determines later on which objects it can be populated with. The mesh for the cave is created by sampling the Beziers. This was not that straightforward since each segment is defined by one horizontal Bezier and four vertical ones, 2 for the top half and 2 for the bottom part:

The mesh for one segment

The mesh for one segment

You can think of the mesh as being a cylinder being pressed into the shape of the Beziers. I started to write an explanation for how this is done but it got a bit long winded so if you want to know how that is done ask me in the comments 😉

Step 2 Generate locations for props and place them

With the shape of the cave done the next part is to find out where to place props inside it. This is handled by this component:


This component generates the positions for props to be placed

Random positions on the surface of the cave is found by generating a random index for a vertex of the cave and use its position. The next big issue is to make sure that things is not created on top of each other. Each type of prop is associated with a category, that determines how many props should be generated based on the size of the cave, how random that number turns out to be and the minimum distance to other props of its own type and other prop types. When all the points has been generated, instances of the ObjectPlacer component is notified, and is responsible for spawning the prop its associated with. In this case it is the crystals:

The component that populate the cave with crystals

The component that populate the cave with crystals

This component both supports that a randomized object is spawned, and can also vary the size and rotation of the object created. This is all to give a more varied visual look.

Step 4 Color randomization and more pretty Beziers!

To change the look of each cave even more, the colors of the cave itself, crystals, and the fog along with the strength of the fog is also changed based on the seed of the cave:

The initial seed is used to select a color scheme that's applied to the whole cave

The initial seed is used to select a color scheme that’s applied to the whole cave

Since I had written the code for generating the mesh for the cave as a component that took an arbitrary set of Beziers as input, I also quickly added a type of prop based on that:

Flowers? Mushrooms? Spikes? a bit of everything I suppose

Flowers? Mushrooms? Spikes? a bit of everything I suppose

Conclusion: What went right/wrong

I think that overall the solution is able to generate some quite interesting spaces, but I would have liked each cave to be even more unique. A system to create more varied typologies for the cave would be great, since it would have the biggest impact. The type of prop that was based on Beziers also showed great potential to give more variation. As you can see from the screenshots of the components above I also ended up using a lot of AnimationCurves to define various probability distributions. This worked out great so I can recommend that. In the end I really enjoyed working on this type of programming since you both get to have the joy of getting it to work but also not knowing exactly what it will result in. This is definitely not the last time I make something procedural.

Leave a Reply

You must be logged in to post a comment.

[cache: storing page]