Python dungeon/level generator

Why don’t you save the dungeon to a notepad file.You can do that in python.

That is possible, but storing a seed would be about 100 times less data.

I can put together another example .blend at the weekend if needed.

@BPR: you’re right. It’s perfectly possible to seed it (since it’s based of python’s random module), then you only need to save the seed and ensure the generator functions are called in the same order. Call random.seed() and passing the seed before calling any generator functions (see herefor an example of seed setting). You can also use random.getState() and .setState() to save and load the random generator states.

a simple example, will produce the same level each time (I used this for bug testing)


import random
import dungeonGenerator

random.seed(100)
d = dungeonGenerator.dungeonGenerator(51, 51)
# and so forth

At present I’m playing with number pairing functions to turn a player’s x,y position into a unique number and use that as the basis for the seed for python’s random module. This way you can have a completely endless random level that will always allow you to return to the same point, without having to save any data.

I have it working on a seed, and using a mix of two walls and setting textures, but can you help with a method to have corners VS walls? (so I don’t have the back faces)

what about a 3 layer thick maze? (to facilitate closing off the ceiling but not as a cube)


 _______
/               \
|               |


I am making a 3rd person game :smiley:

I also need a way to dependably make a freeway :smiley:

What do you mean by corners?Could you provide a picture?It would help.

I think I have a method, but I don’t know exactly how I will line it up but I have a idea

check to see how many paths are touching each wall, and then spawn a piece depending on that value,

then spawn the right piece and line it up,

A runner = a wall touching 1 path

A corner = a wall touching 2 paths

A isthmus = a wall touching 3 paths

A island = a wall touching 4 paths

Wicked! That’s looking awesome!

You wall tiles can be as high as you like. With regards to ceilings, it’s just a case of adding a plane above the floor tile/object rather than the wall objects.

I’ve put together an example .blend of 3 different ways to control wall placement, there’s other approaches, but it should give you some ideas to get started.

controllingWallPlacement.blend (625 KB)

As for a freeway, I’d use the .placeRoom() function to place a long, thin section to function as a freeway. And since it’s deterministic rather than random you’re guaranteed to get one in your level.

Battery it’s perfect :smiley:

Now I just need to see what I can get done with it :smiley:

Happy to help man! Keep me posted with how you get one.

old version - :smiley:

New

Just tweeked the geometry and added a flycam

Thanks Jay, I thought about making exactly the same type of script, I even have D&D related literature, however I never actually had the energy.

Also thanks to Blueprint random for sharing the WIP. Good game :slight_smile:

Ok, So, now, that example you gave me, can I store a list of all objects in a radius of a tile?( in a grid/array or something?)

I noticed find neighbors, but what if I need all neighbors in a radius?

to add physics colliders and remove them, and potentially suspend dynamics, and re-engage etc. of any objects inside the bound of the tile…

I was looking at -


def findNeighboursDirect(self, x, y):
        """
        finds all neighbours of a cell that directly touch it (up, down, left, right) in a 2D grid
         
        Args:
            x and y: integer, indicies for the cell to search around
             
        Returns:
            returns a generator object with the x,y indicies of cell neighbours
        """
        xi = (0, -1, 1) if 0 < x < self.width - 1 else ((0, -1) if x > 0 else (0, 1))
        yi = (0, -1, 1) if 0 < y < self.height - 1 else ((0, -1) if y > 0 else (0, 1))
        for a in xi:
            for b in yi:
                if abs(a) == abs(b):
                    continue
                yield (x+a, y+b)


but I don’t really understand how to use it to operate on the neighbors, (add or remove physics and place lamps/put them on a unused list)

its very very slick btw.

edit: looks like I will build the maze -> store maze data as KDTree

Delete all maze except closest X units

Use KDTree to add/remove objects and also build a list of lamp hooks :smiley:

here is my method based on .sorted()

I will try and have the KDTree method done tonight :smiley:

Attachments

controllingWallPhysicsObjectsPlacementAndLamps.blend (872 KB)

I DID it!
KD tree based physics collider addition/subtraction :smiley:
Also built in a dynamic lighting manager :smiley:

This is using 11 point lights for the whole map :smiley:

now we need A atlas made kit , with LOD + UPBGE instancing build :smiley:

Attachments

KDTReePhyscicsLODandLampLOD4.blend (1.11 MB)

This is cool!

@Nerai: you’re welcome. I read a lot of D&D and rpg level generation literature when developing this. Really helpful stuff.

That’s amazing! The size of the levels is massive!

There are 2 methods of getting neighbouring tiles. There’s findNeighboursDirect(), which returns the north, south east and west tiles (ie. those directly touching) and there’s findNeighbours() which returns all surrounding tiles. They both take the x,y coords of a tile and spit out the x,y coords of neighbouring tiles.

Both are generator functions, so they don’t return a list. The reasoning behind this was that you more likely to want to iterate over a tiles neighbours than store a list of neighbours. Additionally, since when looping over them often you need to exit early once a condition is met there wasn’t much need to construct a full list. So you use them like this:


d = dungeonGenerator.dungeonGenerator(51, 51)

# blah, blah, blah

for nx, ny, in d.findNeighbours(x, y):
    if d.grid[nx, ny] == dungeonGenerator.FLOOR:
        # do something
        # etc

You can read more about generators here. And you could convert the generator output to a list:


neighbours = list(d.getNeighbours(x,y)

Which would give you a list of x,y tuples of the neighbours.

With regards to storing physics and rendering information you got 2 options I can think of. The first is to create you’re own gird of the same dimensions as the dungeon generator grid, then you can store KX_GameObjects in that. This way all your dungeonGenerator.grid references would be valid for the grid you’ve created.

The alternative is to replace the tile constants with a tile class, that way you can store all kinds of information in the grid and monkey patch your own functions into the tile class. I have a version that does this, (it requires very little code change). But not one that’s ready for release (it’s not tested with every generator function yet). My current tile class looks like this:


<b>class </b><b>Tile</b>:
    <i>"""
</i><i>    simple container for tile information
</i><i>    supports comparisons between tiles and comparisons between integers, so tile constants can be compared
</i><i>    Args:
</i><i>        x: integer, x indice in tile map
</i><i>        y: integer, y indice in tile map
</i><i>    """
</i><b>def </b>__init__(self, x, y, tile):
        self.x = x
        self.y = y
        self.tileType = tile

        self.passable = <b>True
</b><b>    def </b>__lt__(self, other):
        <b>if </b>isinstance(other, int):
            <b>return </b>self.tileType &lt; other
        <b>return </b>self.tileType &lt; other.tileType

    <b>def </b>__gt__(self, other):
        <b>if </b>isinstance(other, int):
            <b>return </b>self.tileType &gt; other
        <b>return </b>self.tileType &gt; other.tileType

    <b>def </b>__ne__(self, other):
        <b>if </b>isinstance(other, int):
            <b>return </b>self.tileType != other
        <b>return </b>self.tileType != other

    <b>def </b>__eq__(self, other):
        <b>if </b>isinstance(other, int):
            <b>return </b>self.tileType == other
        <b>return </b>self.tileType == other.tileType

It supports comparisons between integer constants, so if tile == 1 will work. The main changes to the dungeonGenerator script are when a new tile is assigned, so references like:


self.grid[x][y] = Floor

becomes:


self.grid[x][y] = Tile(x, y, FLOOR)

There’s a few other changes, but most of them are easy to spot. I’m working on an updated version, but I’ve been side tracked by developing a procedural terrain/environment generator. A little off topic but here’s a quick screen shot:


Not off topic at all! (procedural generation of game maps!)

I am thinking of using terrain -> Roads -> buildings

and the find neighbors is nice, however the KD tree is where it’s at for raw speed, this + data stored in each tile and it will be smoking fast :smiley:

Now with true geometry instancing (I have a build of the branch on my pc (UPBGE_GI_Branch))

we can have levels as big and detailed as fallout :yes:

(stored as 10kb of seeds!)

now how do I make caves? buildings?
I noticed some caves in some of your dungeon documentation.

Yeah, there’s a cave generator, that creates more organic shapes using cellular automata. If I’m honest, I haven’t played with it much. Usage is fairly simple


generateCaves(p = 45, smoothing = 4)

Where p is the probability that a cell become a cave tile (values around 35-45 work best), and smoothing controls the amount of smoothing iterations (values over 4 seem to have no effect). Depending on when you call it will also affect the type of caves generated (especially before and after rooms/corridors).

With regards to buildings, the idea I had in mind was to use the generated rooms as buildings and the corridors as connecting roads (.generateCorridors(‘f’) will produce straighter connecting paths)

Ok, I have caves working,

next question, is there any way I can have most paths be at least 2 tiles wide, or wider?

(less claustrophobic)

Glad to hear it.

I really wanted to be able to specify the path width at generation, but couldn’t come up with a decent solution. So the short answer is no. However…

It is possible to widen paths by abusing the wall and flood fill. Basically, generate the path, prune it back, generate walls and then flood fill everything to corridor tiles. Then you place rooms and generate the rest of the level. This does work to some degree but can produce ugly levels. There’s an example of this towards the end of the blog post.

Alternatively, increase the overall tile size then the generated corridors will be wider. This can then be offset by generating rooms with a smaller size (otherwise they’d end up ridiculously big.

If anyone can come up with any other solutions to widening paths, or improve the existing script I’d love to see it.