Search This Blog

Monday, February 26, 2018

7DRL: Turncoat Tomb --About those dungeons

2018 7DRL only DAYS AWAY.   I'm so excited I just can't hide it.

Since it is within the rules to use commercial or 3rd party graphics, build on pre-made game engines like the T engine, or even build on previous projects, I'm using some of the tech I developed when experimenting for my book Let's Make a RogueLike with Construct 2.

The dungeon is a grid of tiles that are 32x32.  I made several generic dungeon generators for the book in hopes of explaining different ways to do PCG levels. 

My first example of a "procedural level" was with a "pillar and wall" maze generator similar to how Berzerk mazes are made.  I explained this in my "Roguelike maze" tutorial on Scirra's site:

This is a very easy and super fast kinda maze algorithm.  It can give you labyrinthine hallways and sort of room like areas. If you want a pcg level in a hurry it's a good way to go.

This was meant to be short, easy to understand, and maybe help someone get started with really basic PCG in Construct 2.

At the time I was very naive and not as sophisticated in my approach to making pcg levels.

In the tutorial (and in subsequent ridiculously convoluted attempts) I made levels by placing wall and floor sprites on the layout, then checking for where these sprites are with "feeler" objects checking for collisions, then adjusting things accordingly.

There are a couple of big problems with this approach:

If you put a sprite down, say a wall, then check to see if there's a wall there, Construct will think there is no wall there.

 I didn't know it at the time, but Construct 2 waits until all the events are done and a tick happens and the screen is updated before the sprites you just created are really "there" for collision checks.   You can say "wait 0" and THAT EVENT ONLY will wait a tick so anything in THAT EVENT ONLY will then see your new floor or wall or whatever if its checking for collisions. 

Notice how I keep saying THAT EVENT ONLY in rage caps?  To take advantage of the tick that happens after a wait action, you have to have your checks in events that are in that same event tree further down.

Other events don't give a crap about your wait command and will go on their merry way like nothing happened... so in those events, your collision checks won't work.

This led me to make some ridiculous chains of "make a floor, wait, check for floor, do something if there is one, something else if there's not, make a wall or something, wait, check for walls and floor do something else make another floor wait again..."  It started to get really ugly because all these waits have to be in the same event.  God forbid I call a function or have a loop or something.

 The fact that wait works this way is actually really awesome.  It's a good way to have it set up for most situations, but it's no fun for trying to build a level.

Furthermore if you have a big level with hundreds of sprite tiles and you have to do a bunch of nested collision checks and stuff, things start to get really slow.  Building a  very simple"Rogue" sized level can take almost a minute which is hours in waiting to load time.  And that's without any game objects.

Also it tended to make redundant floors and walls over each other which happens when you're throwing stuff around random -willy-nilly. It required some clean-up which was more collision checks in nested waits or with timed triggers or something.

Digger method:

Intuitively I thought a good way to make levels was with what I later learned was called an "agent based" or "digger" method.  I have a little guy pop up rooms, turn a random cardinal direction, move a random amount (while making a hall) then pop up another room.  When our little room digger gets too close to an edge of the boundary, I have it turn back.

The idea was good, but the execution was a mess.

The solution is just to add an extra step and make your level in a two dimensional array, then once the array is done building, have the game draw the level with sprites.  No sprite collision checks needed.  All the thinkin' is done on the array.

1 build your level in an array
2 have a generic array-to-level draw routine.

This way no matter how you build your array, your "draw dungeon from array" routine should work the same.

Also it's fast as sh@$.

I returned to the tried and true method, similar to the one used in the original Rogue.

It uses a tic-tac-toe grid and in random squares of that grid it makes random rooms... then makes halls between them.  You end up with the same looking dungeons as the digger method, but there's more control.

It's easy to see what's going on here.  The problem is it's kinda boring.  The dungeons are similar and you just have rectangular rooms.  With the digger method you have more unpredictable shapes because the thing digs rooms out on top of other rooms.

So I just added a loop to make a number of random rooms on top of the initial random rooms.

With some tweaks in the parameters, I can easily set my dungeons to "Extra Chunky (tm)"   Extra chunky = extra fun!   All the control of the boring dungeon, but none of the boring.

This is how fast it can generate complex dungeons in real time.  (The top is slowed down a lot).

Mmmmm. Chunky.

The "chunky" ones look  like playable levels in a more modernerish game.  Yes "modernerish" is a word.  It is now.  You're welcome, colloquial English.

Instead of taking 30 seconds to make a level you can make the same level 10 times a second.  At least.  Turns out it's a LOT faster to check numbers in an array than it is to check sprite geometry collisions on a layout.

Also there's less clean up because one value in an array can't be 2 overlapping things (unless you want it to --which I'll explain below).

A bit about my generic level array:

If you want more than just one thing on a given square --for example maybe you want a floor and a door or a wall and  a torch sticking out of it...   you will need to have more than one value in the same "area" in your level.  To do this you might want a 3 dimensional array instead of just x and y so you can stack values in each spot.

This seems like over-kill since most squares in say a 200x200 or 40,000 square dungeon  will just have a wall, a floor or nothing. Every step in the third dimension is another 40,000 values of mostly thin air.   That's a whole lotta nothin' going on.

What I did was keep just a 2 dimensional array, but use bit values. Bit 1 is a floor or not, bit 2 is a wall or not, bit 3 is a door or not...and so on.  This gives me the freedom to have any combination of 8  different things with a single number from 0 to 255.

To be continued:

No comments:

Post a Comment