Python dungeon/level generator

Hi Everyone,

It’s been a while since I’ve done anything Blender or Python related so over Christmas I developed a Python procedural dungeon/level generator.

It’s renderer independent so can be used in any Python based set up and allows for the procedural generation of rooms, caves, corridors and mazes (and any mix of the above). It also provides a load of helper functions and path finding.

You can find the article on how to use it here, the python module can be found in the resources section at the end. There’s a Blender specific usage example in the rendering section.

Here’s some screen shots of a procedurally generated temple created with it in Blender:




Let me know if you encounter any problems or any ideas for further development

Does this end the pieces of the dungeon far away from the player and add the pieces of the dungeon nearest player?

No, it’s renderer independent (so it can be used in other python projects). Adding that code would make it BGE specific.

It’s fairly straightforward to implement though for your chosen renderer. Just check where the player is on dungeonGenerator.grid and render the tiles surrounding the player. I think the best way to do this is to modify the module to work with a custom tile class (don’t need to change much), then the tile class can have a flag if it needs to be rendered or not.

The easiest way in Blender would be to use the built in LoD if you didn’t want to do it with python.

Do you need to run the dungeon generator script in module mode?

No, it’s renderer/engine/set up independent. To work with Blender’s module mode it would need Blender specific code. You import it into your BGE scripts (which can run in module mode) and go from there. Read the article for how to use it, and there’s a Blender specific example in the rendering section.

I’ve attached a .blend if you’re having trouble following the examples in my article.

simpleDungeonDemo.blend (559 KB)

This is really a good system.But now all it needs is some good ai to make it work.

Really nice.
5 stars!

Thanks guys! :slight_smile:

Sadly, I’m not going to add AI. It works fine without it. As it’s a procedural level generator it wouldn’t be within the purpose or function of the module to provide AI. Also, AI is something that it is very game and engine specific so wouldn’t fit with a module that’s meant to be generic. And because AI is so application specific it would be impossible to design an AI that would meet most peoples needs.

How would i randomly spawn enemies into open area of this maze?

How would i randomly spawn enemies into open area of this maze?

It’s renderer independent so can be used in any Python based set up and allows for the procedural generation of rooms, caves, corridors and mazes (and any mix of the above). It also provides a load of helper functions and path finding.

I was unable to find docs on the helper functions, (skimming through)

edit:
Finally, we have the path finding functions.
[TABLE=“width: 497”]

1
2

dungeonGenerator.constructNavGraph()
dungeonGenerator.findPath(startX, startY, endX, endY)

[/TABLE]

Before you can find a path a navigation graph needs to be constructed by calling constructNavGraph, which catalogues how the tiles link together. If you make any changes to the level you will need to re-construct the navigation graph by calling this function again.
Then you can find a path between two points on the dungeonGenerator.grid using findPath() and passing the start X,Y and end X,Y co-ordinates. This function returns a list of X,Y tuples forming a path between the two points. It’s important to note that all co-ordinates are dungeonGenerator.grid co-ordinates. Therefore in game applications you’ll need to convert your game world co-ordinates into grid co-ordinates and vice-versa. This is easily accomplished by using integer division:

[TABLE="width: 497"]

1
2

startX = objectGameWorldXPos // gameTileSize
startX = objectGameWorldYPos // gameTileSize



[/TABLE]



This isn’t entirely accurate though. How you choose to round the game object’s world position prior to division will affect which grid tile the object is considered to be on. This will produce better results:
[TABLE=“width: 497”]

1
2
3
4
5
6

def roundNearestInt(i, base = 2):
    """Returns nearest multiple of base"""
    return int(base * round(i/base))
 
startX = roundNearestInt(objectGameWorldXPos) // gameTileSize
startX = roundNearestInt(objectGameWorldYPos) // gameTileSize



[/TABLE]

Converting tile grid co-ordinates to game world co-ordinates is as simple as multiplying the co-ordinates by the game tile size

Spawning enemies is just a case of picking a tile that’s either a floor or a corridor. As BRP highlighted: How you go about this is completely up to you and will depend on your game.

So, to spawn 10 enemies in any open space (corridors and rooms) you could:


import dungeonGenerator
from random import randint

# generate your level here, then

enemiesSpawned = 0

while enemiesSpawned < 10:
    # where levelSize is the size of the dungeon you've generated
    x = randint(0, levelSize)
    y = randint(0, levelSize)
    if dungeon.grid[x][y] == dungeonGenerator.FLOOR or dungeon.grid[x][y] == dungeonGenerator.CORRIDOR:
        enemiesSpawned += 1
        # spawn your enemy however you like, 
        # set their world positions by multiplying x and y by the level tile size

Or you could pick a room at random then pick a tile within the room:


from random import randint, choice

room = choice(dungeon.rooms)
x = randint(room.x, room.x + room.width)
y = randint(room.y, room.y + room.height)

ob = scene.addObject('Enemy')
ob.worldPosition.x = x * tileSize
ob.worldPosition.y = y * tileSize

Or you could scatter them randomly across the entire level based on some probability:


from random import randint

# generate your level

for x, y, tile in dungeon:
    if tile == dungeonGenerator.FLOOR or tile == dungeonGenerator.CORRIDOR:
        if randint(0, 100) < 35: # or whatever probability you want to use
            # spawn you enemy
            # set their world position by multiplying the x, y values by the tile size

(all python untested)

Or a host of many other methods, like using the findNeighbour() and findNeighbourDirect() functions to place them away from things, or picking random tiles and then using the pathFinding() functions to make sure they’re not too close to the player, or using modulo on the x and y co-ordinates to make a pattern of enemy placements. And so forth…

@BRP: Thanks for your feedback :). In the module the path finding functions are right at the end and have doc strings (Google style). In the article they’re covered (albeit briefly) at the end of the ‘Helper functions and everything else’ section. There’s no pictures to show for them, so it’s easy to miss. It also covers converting world co-ordinates to grid ones and vice-versa. If those bits (or any other bits) don’t make sense or aren’t clear enough let me know and I’ll make some edits.

How would i get the level tile size?Those codes do not work.

Of course they wont work, most of the variables in them are not defined because they’re things you have to define (think of them as snippets from the middle of a script). The examples don’t generate any dungeons, because you need to call that and half of the code is just comments telling you where to put your spawn code. You can’t just copy and paste things in and expect it to work. The examples I gave are there just to give you some ideas as to how to implement a particular function. The specifics of the script you’ve got to do.

tile size is the size of the tiles you are using. I can’t tell you what size that is it because it depends on how you model your tiles in blender. If you look in the example .blend I attached above you will see that the tiles are 2 blender units by 2 blender units and tileSize in the script is set to 2.

With no. blend, error messages or a description of what you’re trying to do its hard to give guidance.

Replying to favorite this. Is it open source? We might be able to use it in out kids computer camp. They would love it. Ill let you know if we use it

Thanks! Yeah it’s open, so use it as you wish. I’d love to see what gets made at kids computer camp with it. And if you make any improvements to the script let me know and I can update the module.

Wow thanks man that’s really useful I have been searching for this for a while now! :slight_smile:

Cheers man. Glad it’s useful to you.

Any word on a .blend to reverse engineer?

The blend is on post 5.

Ok, it is very very useful, but is there a way to make it so with a seed it will always make the same dungeon ?

both are nice - (true random vs seeded) but seeded is more useful to me (I need to always generate the same levels).

this way I can store a table of seeds, and have all the maps in the game, and then use a table to place enemies and doors that are not random but designed, as the equations to use puzzles and gates… I don’t know where to start.