I moved to a new URL! Check it out!

Game Jam Procedural Generation Part IV

Game Jam Procedural Generation Part IV
The final part of this series about procedural generation for Starforger II will conclude with the last of the level generation code. In the last episode I talked about some of the details in generating rooms, tunnels, and other details in the level. In this final part I'll wrap it up by talking about how I place enemies, breakable blocks, and some final touches on the treasure room.

Image


The next thing in the generation of the level is the breakable blocks. These are blocks that the player can blow up using their bombs. I added these sort of at the last minute of the jam just to give some more interaction with the world, and it felt kinda fun to forge your own path through a big section of breakable blocks.

// put breakable blocks in random places I dunno
for (var yy = 10; yy < grid.TileRows; yy++) {
for (var xx = 0; xx < grid.TileColumns; xx++) {
if (CheckRect(xx, yy, 2, 2)) {
continue;
}
if (Rand.Chance(config.BreakableChance)) {
if (breakables < breakablesMax) {
Scene.Add(new BreakableBlock(xx * 16, yy * 16));
gridBreakable.SetRect(xx, yy, 2, 2);
breakables++;
}
if (config.Width > 1500) {
xx++;
}
if (config.Width > 1000) {
xx++;
}
}
}
}

I actually use a separate grid "gridBreakable" to keep track of where I've already placed blocks. This is less expensive in Otter. The alternative would be to do a collision check against all other breakable blocks which would take longer and longer if there are more breakable blocks being added. Whenever a block is added I add a 2 x 2 rectangle to the gridBreakable grid, and the function CheckRect() will check against the breakable grid and the ground grid, so I can't accidentally place a breakable block in the ground, or overlapping another breakable block.

Image


I also add a limit to how many breakables I can add to a level. Since the breakable blocks are all entities I don't want to overload my level with thousands of breakable blocks. I also space out the distribution of the blocks if the level width is greater than 1000 pixels, and 1500 pixels.

Image


The next step is placing enemies. This is similar to the code I've been using so far. I'm just going to go through the whole level and see if I can put an enemy in a space, and if I can I check against a random chance to see if I put the enemy in the available space.
var creatures = 0;
var creaturesMax = 12;
for (var yy = 10; yy < grid.TileRows; yy++) {
for (var xx = 0; xx < grid.TileColumns; xx++) {
if (CheckRect(xx, yy, 2, 2)) {
continue;
}
if (Rand.Chance(config.CreatureChance) && creatures< creaturesMax) {
creatures++;
Scene.Add(new EnemyWalker(xx * 16, yy * 16));

xx += Rand.Int(2, 50);
yy += Rand.Int(0, 3);
}
}
}

I max out the amount of enemies to 12 just for the sake of not completely filling a planet full of enemies if all the random numbers align. If I find an empty space for an enemy I check to see if I can place it. If I'm successful in placing the enemy then I also advance the loops x and y values by a random amount. This was another attempt to try and space out enemies, and make their appearances incredibly rare, and also try to make two enemies appearing next to each other very rare. Overall I wanted seeing enemies in the game to be incredibly rare and only really happen if you're exploring planets in the outer most rim.

Image


The next step is to clear out a landing pathway for the ship. The ship is going to land somewhere near the center of the level, and I wanted the ship to always have to descend some distance down to the planet before landing. I discovered that after I made all the code to generate platforms and islands in the air sometimes the ship would just crash into a stray platform in the air instead of making it down to the ground level. To fix this I simply carve a huge landing zone out of the ground for the ship that extends from the top part of the level to the ground level.
// clear out center column for ship

var shipx = config.ShipStartOffset + config.Width / 2;
shipx = (int)Util.SnapToGrid(shipx, 16) / 16;

grid.SetRect(shipx - 6, 0, 12, grid.TileRows - 1 - groundLevel - 5, false);

This carves out a nice landing zone for the ship to fly down. Note that this only gets rid of the ground on the main grid. It doesn't get rid of any breakable blocks that could be in the way. At first this was a bug, but instead of removing breakable blocks in the way instead I just made it so the ship could crash through breakable blocks and that turned out to be way more fun.

Image


Now it's time to set up that room with the treasure in it. A long time ago I figured out where the treasure was supposed to go, and I made a room reserved for it, but at this point that room might be full of platforms or island formations, so it needs to be cleared again. After that the treasure needs to be on the ground, so some platform tiles need to be added just below it.
//carve out room for treasure
grid.SetRect(gx - 3, gy - 3, 7, 7, false);

//place platform under treasure
grid.SetRect(gx - 1, gy + 1, 3, 1, true);

The values gx and gy are the grid x and y values of the treasure. I manually carve out space around the treasure here to ensure an empty room. Once again the breakable blocks might be still in there, but it's okay since the player can just blow them up to ultimately find the treasure. I then place a 3 block wide platform directly under the treasure so it has something to rest on.

Image


The very last step of the generation is to ensure that the player can't fall out of the level either on the sides or the bottom. Adding some walls around the left, right, and bottom edges works just fine for that.
// place ground on very bottom
grid.SetRect(0, grid.TileRows - 1, grid.TileColumns, 1, true);

// place walls
grid.SetRect(0, 0, 1, grid.TileRows, true);
grid.SetRect(grid.TileColumns - 1, 0, 1, grid.TileRows, true);

I don't place any walls on the top of the level since the ship actually flies in from above the top of the level. To stop the player from flying off of the top of the level and going left or right I just clamp the player's position to 0 and the level width. This is a simple enough way to stop the player from using bomb jumps or crouch jumps to fly off of the level.

Image


Okay that covers my entire procedural generation code from Starforger II. To check out the other parts just take a look at the related posts section just below this post. This was mostly about making a very messy and quick procedural generation algorithm for a game jam game, but if you want to dig into some more advanced stuff there's all kinds of resources out there like /r/proceduralgeneration /r/gamedev and the procedural generation wiki.
new comment!

Post your comment!

Name
Email
Comment