Copod is a roguelite where you have to survive as a small creature in a strange world. All you want is to find a flower to impress your mate so you can continue to the next generation, but there are many other creatures that are in your way. Find eggs to hatch fellow Copods to help you out or sacrifice them for more health. Kill and scavenge for varied powers that you give you a temporary abilities.
I originally imagined the levels of Copod to be a lot more open than they are now. I thought it would be fun to have long paths that twist and turn. I also thought it would be cool to make the terrain have a more natural shape rather than a rigid grid based one (like in Spelunky for example). To generate these caves, I made a little program that would export procedurally generated caves that my game would then load at runtime. However, as I continued with the development of Copod, it became clear that the combat played a much bigger role than exploration, so I sized down the levels to make the combat tighter and the level layout less distracting and confusing. This meant I had to repurpose the existing cave generation to generate the caves that you find in Copod today.
The program (creatively named CopodWorldTool) goes through a number of steps to generate a cave. Some of these are independent from each other so can be processed in parallel to get a nice little speed up.
By starting off with a random seed, a “bubble map” is formed. This is merely a group of circles laid out and connected in way that you desire, randomised by a given seed and your specification. ArenaBubbleMap is the class used to generate most of the caves found in Copod. It simply starts with a bubble of a random size within a given threshold and surrounds it with one to four bubbles at each cardinal point. Each of these surrounding bubbles have a random size and are all rotated slightly off from their cardinal direction.
The bubble map is then given to the path map stage that then creates a path that links the bubbles together, like stations in a tube map. This can be used to make a random paths by ignoring some bubbles, but arenas just need all the bubbles linked together to look good, so nothing fancy is required here.
With a path laid out, an outline of the new cave can be made. The program takes each branch of the path (a prerequisite is that no loops in the path can exist) and creates a small polygon that wrap rounds it. You can see the four loops of each of the four branches below.
These are then merged together, the insides striped away. To do this, each poly is triangulated so it is easy to test whether a point on is inside a poly. Then program then marches along a polygon’s outline starting at any point that is outside of any other poly. When it detects a line intersecting with another line (a polygons outline simply being made out of line segments), it creates a new point where they intersect. It then and continues marching along the poly outline it collides with, in the direction away from the intersecting point. Each new and visited point is pushed onto a stack, building up a completely merged outline. This process is repeated until the program is back where it started. We now have an outline that looks like this:
By triangulating this composite outline, we can get a navigation mesh. This is used for pathfinding in the game, allowing creatures to move around without getting stuck behind walls (in theory at least). I also use the navigation mesh to make some floors that you can see in some caves.
Here in this screenshot, you can see the floor that is generated using a navigation mesh in the background.
The physics mesh represents the boundaries of the cave and is used by the physics engine to stop you from phasing through walls. This is made in the way that navigation mesh is created, but the negative space is triangulated instead.
Contours are used to make the mesh of what you see in the game. Each one is at a different height and is scaled based on the normal of each point.
Below is an example of this scaling. The blue and red lines represent the normals at each point and the solid black line represents part of an outline. You can see the left outline being scaled up along the normals to meet the dotted line, the result of the scaling is on the right.
Cracks and slight variations are also applied in this stage. You can see this in the image below. Red lines are closer while yellow are further away.
With these contours, a visual mesh can be created. First the code finds each matching point between two contours. This is trivial since each contour is just a resized version of another. From this, a list of ordered lines can be created, which in turn can create a list of quads between these lines. Triangulate this quads to get the visual mesh. The order of the points in these triangles is important since their order determines which face is visible when rendering.
In the diagram below, the two red lines are two contours being used to create the triangle mesh between them.
Above is a preview of the visual mesh that is created at this stage.
The Regions and Entities stages are responsible for placing gameplay objects and spawning positions. The circles outlines represent each gameplay region, green for whirlpool, a place where enemies should not spawn, and white for a nonspecific region where enemies can spawn. The solid circles represent entity placement locations. White circles are wall entities such as plants, green for places where eggs and flowers can spawn, lilac for whirlpool placement and their ejection direction and finally pink for enemy spawn positions.
And finally the colour scheme. A colour ramp is made in photoshop and then is assigned and applied to a cave at runtime. Each layer is assigned a different colour based on its height.
And there you have it, how procedural cave generation works in Copod. A bit complicated for what it is, but its done now and I’m happy with the end result. I also use the same process to generate other cave types, but they are just different configurations of some stages.
Bonus! Here is a gif of all the stages.
Stay tuned for more updates and don’t forget to vote of Copod on Steam Greenlight.
Latest tweets from @here_be_ben
DAE play spot the bootstrap.css whilst browsing?
Jun 10 2014, 4:12pm
Jun 8 2014, 9:33am
Just making a simple animation system that uses bézier curves for interpolation. T.co
Jun 7 2014, 8:40am
@Case_Portman Sonic 2's level design starts to go downhill after Mystic Cave Zone imo. I can never be bothered to play past that level.
Jun 5 2014, 7:37am
@GingerDragonYT Man, I loved making Mars Assault as hard as possible back in TS3. Such a fun map.
Jun 3 2014, 10:20am