Giter Site home page Giter Site logo

Dungeon Generator about ursina HOT 23 OPEN

pokepetter avatar pokepetter commented on July 17, 2024
Dungeon Generator

from ursina.

Comments (23)

Spritekin avatar Spritekin commented on July 17, 2024 3

Ok guys... got the final program and the tutorial on how to get there from scratch.

from ursina import *                    # this will import everything we need from ursina with just one line.

# Define some characters we want to use to represent the map
TEMPTY    = ' '
TFLOOR    = '.'
TCORRIDOR = '='
TDOOR     = '&'
TDEADEND  = '^'
TWALL     = '#'
TOBSTACLE = '%'
TCAVE     = '@'
TWATER    = '~'
TROAD     = '*'
TENTRY    = 'E'

CAMERA_SPEED = 10
SCALE = 2
texoffset = 0.0                         # define a variable that will keep the texture offset

def update():
    global texoffset                                 # Inform we are going to use the variable defined outside
    texoffset += time.dt * 0.05                       # Add a small number to this variable
    for tile in water:
        setattr(tile, "texture_offset", (texoffset, texoffset))  # Assign as a texture offset

# Move the camera to a new position but check if it is valid before moving
def moveto(newposition):
    mapposition = newposition / SCALE                       # get the map coords from that position
    if tilemap[int(mapposition[2])][int(mapposition[0])] != TWALL:        # if the new position doesn't have a wall
        camera.position = newposition
    else:
        print("There is a wall in that direction")

def input(key):
    if key == 'i': moveto(camera.position + camera.forward * SCALE)    # where will I move
    if key == 'k': moveto(camera.position + camera.back * SCALE)
    if key == 'j': moveto(camera.position + camera.left * SCALE)
    if key == 'l': moveto(camera.position + camera.right * SCALE)
    if key == 'u': camera.rotation_y -= 90              # rotate camera left
    if key == 'o': camera.rotation_y += 90              # rotate camera righa

app = Ursina()

window.title = 'Ursina Dungeons'                # The window title
window.borderless = False               # Show a border
window.fullscreen = False               # Go Fullscreen
window.exit_button.visible = False      # Show the in-game red X that loses the window
window.fps_counter.enabled = True       # Show the FPS (Frames per second) counter

# Open your dungeon map for read
tilemap=[]
with open('dungeon.map') as f:
   for line in f:                       # For each line
       tilemap.append(list(line[:-1]))      # append the line to the map but break it in individual characters first

water = []

for py, line in enumerate(tilemap):
    for px, tile in enumerate(line):
        if tile == TEMPTY:
            continue
        if tile == TFLOOR or tile == TENTRY:    
            if tilemap[px+1][py] == TWATER or tilemap[px-1][py] == TWATER or tilemap[px][py+1] == TWATER or tilemap[px][py-1] == TWATER:
                Entity(model='cube', color=color.white, texture="floor", collider="box", position=(px * SCALE, -0.5 * SCALE, py * SCALE), scale=(SCALE,SCALE,SCALE))
            else:
                Entity(model='plane', color=color.white, texture="floor", collider="box", position=(px * SCALE, 0, py * SCALE), scale=(SCALE,SCALE,SCALE))
            if tile == TENTRY: camera.position = (px*SCALE, 1, py*SCALE)
        if tile == TCORRIDOR:
            Entity(model='plane', color=color.white, texture="corridor", collider="box", position=(px * SCALE, 0, py * SCALE), scale=(SCALE,SCALE,SCALE))
        if tile == TDOOR:
            Entity(model='plane', color=color.white, texture="floor", collider="box", position=(px * SCALE, 0, py * SCALE), scale=(SCALE,SCALE,SCALE))
            Entity(model='cube', color=color.white, texture="door", collider="box", position=(px * SCALE, 0.5 * SCALE, py * SCALE), scale=(SCALE,SCALE,SCALE))
        if tile == TDEADEND:
            Entity(model='plane', color=color.white, texture="deadend", collider="box", position=(px * SCALE, 0, py * SCALE), scale=(SCALE,SCALE,SCALE))
        if tile == TWALL: 
            Entity(model='plane', color=color.white, texture="floor", collider="box", position=(px * SCALE, 0, py * SCALE), scale=(SCALE,SCALE,SCALE))
            Entity(model='cube', color=color.white, texture="wall", collider="box", position=(px * SCALE, 0.5 * SCALE, py * SCALE), scale=(SCALE,SCALE,SCALE))
        if tile == TOBSTACLE:
            Entity(model='plane', color=color.white, texture="obstacle", collider="box", position=(px * SCALE, 0, py * SCALE), scale=(SCALE,SCALE,SCALE))
        if tile == TCAVE:
            Entity(model='plane', color=color.white, texture="cave", collider="box", position=(px * SCALE, 0, py * SCALE), scale=(SCALE,SCALE,SCALE))
        if tile == TWATER:
            Entity(model='plane', color=color.white, texture="floor", collider="box", position=(px * SCALE, -1 * SCALE, py * SCALE), scale=(SCALE,SCALE,SCALE))
            water.append(Entity(model='plane', color=color.rgba(256,256,256,128), texture="water", collider="box", position=(px * SCALE, 0, py * SCALE), scale=(SCALE,SCALE,SCALE)))
        if tile == TROAD:
            Entity(model='plane', color=color.white, texture="road", collider="box", position=(px * SCALE, 0, py * SCALE), scale=(SCALE,SCALE,SCALE))

camera.fov = 110
camera.lens.setNearFar(0.6, 1000)

app.run()                               # opens a window and starts the game.
[Ursina Dungeons for Dummies.txt](https://github.com/pokepetter/ursina/files/4496622/Ursina.Dungeons.for.Dummies.txt)

from ursina.

Spritekin avatar Spritekin commented on July 17, 2024 2

Screen Shot 2020-04-18 at 2 35 03 am

Screen Shot 2020-04-18 at 2 36 42 am

from ursina.

ZeroCool940711 avatar ZeroCool940711 commented on July 17, 2024

Hi there, you said there is another Dungeon generation library you will be using, can you share a link for that one so I can check it and tell you if that one is better or the one I have?. Also, I think instead of a FPS camera you should use a top-down camera, its as simple as adding one line of code in Ursina, two if you want to set the FOV to something different from the default value.

camera.orthographic = True
camera.fov = 20

from ursina.

Spritekin avatar Spritekin commented on July 17, 2024

@ZeroCool940711 Don't worry, I went and tested a few, I ended with the one you suggested.
I have already built a small map and saved it into a file.

import dungeonGenerator

# define map size (x,y) max tiles to use in direction
levelSize = [64,64]

# create a generator
print('Generating map...')
dgen = dungeonGenerator.dungeonGenerator(levelSize[0], levelSize[1])
dgen.placeRandomRooms(7, 9, 2, 1, 1000)     # Generate rooms
dgen.generateCorridors('m')                 # Build corridors
dgen.connectAllRooms(10)                    # connect rooma to paths via door
dgen.mergeUnconnectedAreas()                # connect unconnected areas
dgen.pruneDeadends(10)                      # Remove corridors to nowhere
dgen.placeWalls()                           # Make the borders into walls
print('Map generated...')

# Define some characters we want to use to represent the map
TEMPTY    = ' '
TFLOOR    = '.'
TCORRIDOR = '='
TDOOR     = '&'
TDEADEND  = '^'
TWALL     = '#'
TOBSTACLE = '%'
TCAVE     = '@'
TWATER    = '~'
TROAD     = '*'

print('Saving the map...')
f = open('dungeon.map', 'w')
for y in range(dgen.height):
    for x in range(dgen.width):
        if dgen.grid[x][y] == dungeonGenerator.EMPTY:    print(TEMPTY, end = ''); f.write(TEMPTY)
        if dgen.grid[x][y] == dungeonGenerator.FLOOR:    print(TFLOOR, end = ''); f.write(TFLOOR)
        if dgen.grid[x][y] == dungeonGenerator.CORRIDOR: print(TCORRIDOR, end = ''); f.write(TCORRIDOR)
        if dgen.grid[x][y] == dungeonGenerator.DOOR:     print(TDOOR, end = ''); f.write(TDOOR)
        if dgen.grid[x][y] == dungeonGenerator.DEADEND:  print(TDEADEND, end = ''); f.write(TDEADEND)
        if dgen.grid[x][y] == dungeonGenerator.WALL:     print(TWALL, end = ''); f.write(TWALL)
        if dgen.grid[x][y] == dungeonGenerator.OBSTACLE: print(TOBSTACLE, end = ''); f.write(TOBSTACLE)
        if dgen.grid[x][y] == dungeonGenerator.CAVE:     print(TCAVE, end = ''); f.write(TCAVE)
        if dgen.grid[x][y] == dungeonGenerator.WATER:    print(TWATER, end = ''); f.write(TWATER)
        if dgen.grid[x][y] == dungeonGenerator.ROAD:     print(TROAD, end = ''); f.write(ROAD)
    print(''); f.write('\n')
f.close()

print("Map written to file")

from ursina.

ZeroCool940711 avatar ZeroCool940711 commented on July 17, 2024

@Spritekin I see, that one I suggested is pretty good, the best way to split the generation is in 2 parts, first the Structure generation which is the rooms, corridors, walls, floor,etc and the second part is the Overlays which are the stuff that you put over the structure like chests, monsters, entrances, exits, etc. As you can probably see if you checked the Demo.py and the dungeonGenerator.py you can add custom tiles and custom overlays to make almost anything, you can also use some of the functions inside the dungeonGenerator to find close tiles to the want where you want to place things so, in theory you could create Pack Based overlays, that means you can create a pack of monsters and place it inside a room or you can make sure there is only one chest in the room like for example if its a boss chest, its pretty customizable, in the repo you can find a lot of tilesets too if you want to test.

Rewarding the example you wrote above on the room generation part I recommend using decreasing the number of attempts to 500 instead of 1000, the more attempts the slower it's going to be and even with 500 that's more than enough to ensure there are a lot of rooms. also increase the margin between rooms to at least 2 or 3 so there is always enough space to place corridors or something, what that does is that it create empty tiles between rooms, that should avoid weird room generation, or you could also reduce it to put more rooms together if you want to create continuous rooms, something like this should be ok.
d.placeRandomRooms(5, 9, 1, 3, 500)

Here is the documentation of what thats doing in case you want to read it but its also inside the code as a docstring.

randomly places quads in the grid takes a brute force approach: randomly a generate quad in a random place -> check if fits -> reject if not Populates self.rooms
Args:
minRoomSize: integer, smallest size of the quad
maxRoomSize: integer, largest the quad can be
roomStep: integer, the amount the room size can grow by, so to get rooms of odd or even numbered sizes set roomSize to 2 and the minSize to odd/even number accordingly
margin: integer, space in grid cells the room needs to be away from other tiles
attempts: the amount of tries to place rooms, larger values will give denser room placements, but slower generation times
Returns:
none

from ursina.

ZeroCool940711 avatar ZeroCool940711 commented on July 17, 2024

Here are two images showing a result created with the Demo.py so there is some example of what it can be done with this Dungeon Generator, they show two dungeons with rooms and caves where caves are open areas, this could be used to create complex maps where there are indoor and outdoors areas or just open areas like a huge cave. There are also 5 types of overlays used, OVERLAY_ARTIFACT, OVERLAY_TREASURE, OVERLAY_TRAP, OVERLAY_FOE, this should simulate some content a normal dungeon in most games has. The first image was a dungeon with a size of 100x100 it took 0.3 seconds to generate, the second one is a huge dungeon that probably is not common to use, it can be used to create a contry size map or you could use it to procedurally generate a map like Minecraft, if you control where the generation starts and ends you can create a continuous grid, the size for the second one was 5000x5000 and took 20 seconds to generate, at least in my machine.

When checking the images make sure you zoom in, normally you would only see the basic structure from far but if you zoom in you can see the overlays and also more details on the structure.

screenshot
screenshot

from ursina.

Spritekin avatar Spritekin commented on July 17, 2024

@ZeroCool940711 I guess the idea is to demo how to build the maps only. If we try to build a big map we will have to go into advanced rendering technniques like BSP trees or level chunking... lets keep this simple for now.

Same for overlays, I understand what you mean but let me finish this first part, then I can make it more advanced.

Regards

from ursina.

ZeroCool940711 avatar ZeroCool940711 commented on July 17, 2024

@Spritekin I understand, sorry, I just wanted to let you know what can be done with it, of course in order to build big maps its necessary to use something like chunking and also use LOD and occlusion to optimize the map or otherwise will be hard to render such a huge map.

from ursina.

ZeroCool940711 avatar ZeroCool940711 commented on July 17, 2024

Another thing, when i tried using Ursina to render the dungeon created by the dungeon generator I noticed that when creating even a single Entity it made everything run really slow, I think the problem is probably related to the way Ursina load models and textures, seems like instead of having to specify a path to the model or texture it instead loads everything in memory or at least walks every directory inside the folder where the script is and when you have some tilesets there like the folder in the repository for the dungeon generator it takes forever to run the script, I found myself having to wait up to 10 minutes before the script even finished loading, if im right then I think something needs to be implemented in Ursina to handle better those cases, otherwise using Ursina for real game development will be impossible as a real game will have a lot of textures and models that will be loaded when the game starts.

from ursina.

Spritekin avatar Spritekin commented on July 17, 2024

Well I'm building in a single folder with a few textures and all goes fine. I'm using cubes to build the map and I'm rendering the 64x64 map and it still runs at 60FPS. I'm using a MacbookPro 15 inch so I don't think its the video card giving me an edge at all.

If it truly is a problem with the resource loader then yes it should be fixed.

Other things to fix are light management, UI standardisation, file formats, shaders... but hey it is version 0.1.

Even if it is used for learning, it is good.

from ursina.

ZeroCool940711 avatar ZeroCool940711 commented on July 17, 2024

Im not saying its not good, im just giving some feedback on what can be improved and a potential problem for those who might want to use Ursina for real stuff, there are not many Python game engine, at least not good ones so if Ursina becomes one of the good ones it would be nice. I have another Python game engine im using for serious stuff and its awesome but im stuck with something so if I can switch to use ursina until I can figure out how to do what I need with that other engine I will do it, if you are interested or you know someone who want to give me a hand with the game engine and the game im making just let me know, im currently looking for people with knowledge in Python but also need people with C++ knowledge as the game engine is mainly written in C++ and Python is just used for scripting, it has some nice features but its old so, not everyone might like it xD.

from ursina.

pokepetter avatar pokepetter commented on July 17, 2024

Entities are not really meant for big levels like that, but for a handful of dynamic intractable things.
For something like this you should take look at the Mesh class.

# read from a texture and place tiles based on the color
quad = load_model('quad')
dungeon = Entity(model=Mesh(), texture='brick')
model = dungeon.model

texture = load_texture('heightmap_1')
for y in range(texture.height):
    for x in range(texture.width):
        col = texture.get_pixel(x,y)
        # print(col)
        if col.v > .5: #
            model.vertices.extend([Vec3(x,y,0)+v for v in quad.vertices]) # add quad vertices, but offset.
            model.colors.extend((color.random_color(),) * len(quad.vertices)) # add vertex colors.

'''
the uvs are the same for all the squares in this case, but you could also have
them change based on the tile type so you can use a tilemap
'''
model.uvs = (quad.uvs) * (texture.width * texture.height)
model.generate() # call to create the mesh

This way I get ~400 fps even with 131 072 squares

Generating a vary big level can still take a few seconds though, so you might want chunk it into parts and make them as needed.

from ursina.

Spritekin avatar Spritekin commented on July 17, 2024

Screen Shot 2020-04-18 at 1 37 26 am

from ursina.

Spritekin avatar Spritekin commented on July 17, 2024

@pokepetter Nice trick with the mesh... maybe in a more advanced version as an optimisation.

from ursina.

ZeroCool940711 avatar ZeroCool940711 commented on July 17, 2024

@Spritekin I can see you made it work but why are your rooms so close to each other and the corridors so small? Is that intentional or you cant create long corridors?
Also, I have a question, how can you make it so things like walls and doors block the player or have collision but not the floors and other tiles?

from ursina.

ZeroCool940711 avatar ZeroCool940711 commented on July 17, 2024

Also I think you could rotate the wall tiles that are vertically so they look better.

from ursina.

Spritekin avatar Spritekin commented on July 17, 2024

@ZeroCool940711 If you are building a classic roguelike game, I don't think you need to worry about that, the logic that controls colissions is done on the map level not the scene level.

In other words if you are moving in one direction and your map says there is a wall, you can't move there. You don;t test on the quad that is on the scene.

If you are doing 3D (which I plan to do next) then collision is managed by the floor and the walls which are built on top of the floor, in that case yes you need collisions but mostly for some effects.

from ursina.

Spritekin avatar Spritekin commented on July 17, 2024

@ZeroCool940711 I just generated a dungeon with some parameters, adjust it anyway you prefer. Here is the code that builds the map that was generated with the generator.py I sent before.

from ursina import *                    # this will import everything we need from ursina with just one line.

# Define some characters we want to use to represent the map
TEMPTY    = ' '
TFLOOR    = '.'
TCORRIDOR = '='
TDOOR     = '&'
TDEADEND  = '^'
TWALL     = '#'
TOBSTACLE = '%'
TCAVE     = '@'
TWATER    = '~'
TROAD     = '*'

CAMERA_SPEED = 10

def update():
    if held_keys['w']:                                          # If q is pressed
        camera.position += (0, time.dt * CAMERA_SPEED, 0)       # move camera up
    if held_keys['s']:                                          # If a is pressed
        camera.position -= (0, time.dt * CAMERA_SPEED, 0)       # move camera down
    if held_keys['d']:                                          # If d is pressed
        camera.position += (time.dt * CAMERA_SPEED, 0, 0)       # move camera right
    if held_keys['a']:                                          # If a is pressed
        camera.position -= (time.dt * CAMERA_SPEED, 0, 0)       # move camera left
    if held_keys['q']:                                          # If d is pressed
        camera.position += (0, 0, time.dt * CAMERA_SPEED)       # move camera right
    if held_keys['z']:                                          # If a is pressed
        camera.position -= (0, 0, time.dt * CAMERA_SPEED)       # move camera left

app = Ursina()

window.title = 'Ursina Dungeons'                # The window title
window.borderless = False               # Show a border
window.fullscreen = False               # Go Fullscreen
window.exit_button.visible = False      # Show the in-game red X that loses the window
window.fps_counter.enabled = True       # Show the FPS (Frames per second) counter

# Open your dungeon map for read
map=[]
with open('dungeon.map') as f:
   for line in f:                       # For each line
       map.append(list(line[:-1]))      # append the line to the map but break it in individual characters first

for py, line in enumerate(map):
    for px, tile in enumerate(line):
        if tile == TEMPTY:
            continue
        if tile == TFLOOR:    
            Entity(model='quad', color=color.white, texture="floor", collider="box", position=(px, py, 0))
        if tile == TCORRIDOR:
            Entity(model='quad', color=color.white, texture="corridor", collider="box", position=(px, py, 0))
        if tile == TDOOR:
            Entity(model='quad', color=color.white, texture="door", collider="box", position=(px, py, 0))
        if tile == TDEADEND:
            Entity(model='quad', color=color.white, texture="deadend", collider="box", position=(px, py, 0))
        if tile == TWALL: 
            Entity(model='quad', color=color.white, texture="wall", collider="box", position=(px, py, 0))
        if tile == TOBSTACLE:
            Entity(model='quad', color=color.white, texture="obstacle", collider="box", position=(px, py, 0))
        if tile == TCAVE:
            Entity(model='quad', color=color.white, texture="cave", collider="box", position=(px, py, 0))
        if tile == TWATER:
            Entity(model='quad', color=color.white, texture="water", collider="box", position=(px, py, 0))
        if tile == TROAD:
            Entity(model='quad', color=color.white, texture="road", collider="box", position=(px, py, 0))

app.run()                               # opens a window and starts the game.

from ursina.

Spritekin avatar Spritekin commented on July 17, 2024

Notice the tutorial takes the reader rendering on 2D first, then on 3D. This program is the final part only. Also there are no textures for walls, doors, water, etc because I want the user to search his textures.

from ursina.

Spritekin avatar Spritekin commented on July 17, 2024

Screen Shot 2020-04-18 at 10 49 35 pm

from ursina.

Spritekin avatar Spritekin commented on July 17, 2024

Final view with camera properly setup and transparent water with simple animation.

from ursina.

bearney74 avatar bearney74 commented on July 17, 2024

Any way to get the source for this? It looks pretty cool.

from ursina.

bearney74 avatar bearney74 commented on July 17, 2024

I think I found what I need at https://github.com/pokepetter/ursina/files/4496622/Ursina.Dungeons.for.Dummies.txt

from ursina.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.