Implement multiple dungeon levels

master
GreenXenith 2020-04-30 00:09:03 -07:00
parent 0be0efeb7f
commit 6c0124ac6c
8 changed files with 124 additions and 64 deletions

BIN
assets/stair_down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/stair_up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -27,7 +27,7 @@ for filename in os.listdir(os.path.join(os.path.dirname(os.path.realpath(sys.arg
# Map
map = Map(METER)
# map.load("map.json")
map.generate()
map.generate(0)
# Player
player = Player()
@ -35,8 +35,7 @@ player.sprite.texture = spritesheet.SpriteSheet(assets.get("character.png"), 32,
player.sprite.set_rect((8, 32, 16, 16))
# TODO: Use asset loader for spritesheets
player.sprite.texture.set_animation(0, 0, 0)
player.set_pos(1.1, 1.1)
player.set_pos(map.generator.rooms[0].x + 2, map.generator.rooms[0].y + 2)
player.set_pos(map.generators[0].rooms[0].x + 2, map.generators[0].rooms[0].y + 2)
CENTER = [winsize[0] / 2, winsize[1] / 2]
BGCOLOR = pygame.Color("#3e1202")
@ -67,43 +66,39 @@ while 1:
player_rendered = False
for z in range(len(map.map)):
for y in range(len(map.map[z])):
for x in range(len(map.map[z][y])):
tile = map.get_tile(x, y, z)
if tile:
# NOTE: Rotations are clockwise due to Y+ down rendering
# Will this break if there is no texture for the given rotation? Yes.
# Do I care? No.
texture = assets.get(tile.textures[tile.get_rotation()])
for z in range(player.z - 1, player.z + 1):
if z < len(map.map):
for y in range(len(map.map[z])):
for x in range(len(map.map[z][y])):
tile = map.get_tile(x, y, z)
if tile:
# NOTE: Rotations are clockwise due to Y+ down rendering
# Will this break if there is no texture for the given rotation? Yes.
# Do I care? No.
texture = assets.get(tile.textures[tile.get_rotation()])
tilesize = texture.get_rect().size
scaledsize = [tilesize[0] * SCALE, tilesize[1] * SCALE]
tilesize = texture.get_rect().size
scaledsize = [tilesize[0] * SCALE, tilesize[1] * SCALE]
tilex = (x * METER) - ((tilesize[0] / 2) - (METER / 2))
tiley = (y * METER) - (tilesize[1] - METER)
tilex = (x * METER) - ((tilesize[0] / 2) - (METER / 2))
tiley = (y * METER) - (tilesize[1] - METER)
pos = [
camera[0] + round((tilex - (player.pos.x * METER)) * SCALE),
camera[1] + round((tiley - (player.pos.y * METER)) * SCALE)
]
pos = [
camera[0] + round((tilex - (player.pos.x * METER)) * SCALE),
camera[1] + round((tiley - (player.pos.y * METER)) * SCALE)
]
# Only render tile if on-screen
if pos[0] + scaledsize[0] >= 0 and pos[0] <= winsize[0] and \
pos[1] + scaledsize[1] >= 0 and pos[1] <= winsize[1]:
tile.on_step(dtime, map, player)
screen.blit(pygame.transform.scale(texture, [round(scaledsize[0]) + 1, round(scaledsize[1]) + 1]), pos)
# Only render tile if on-screen
if pos[0] + scaledsize[0] >= 0 and pos[0] <= winsize[0] and \
pos[1] + scaledsize[1] >= 0 and pos[1] <= winsize[1]:
tile.on_step(dtime, map, player)
screen.blit(pygame.transform.scale(texture, [round(scaledsize[0]) + 1, round(scaledsize[1]) + 1]), pos)
# DEBUG
# text = arial.render(str(int(x)) + ", " + str(int(y)), False, (255, 255, 255))
# screen.blit(text, [x * 64 - (player.pos.x * round(SCALE * METER)) + camera[0], y * 64 - (player.pos.y * round(SCALE * METER)) + camera[1]])
if not player_rendered and z == 1 and y == math.ceil(player.pos.y + 1 + player.sprite.get_rect()[3] / METER):
# Draw player
screen.blit(pygame.transform.scale(player.sprite.texture.frame, [round(SCALE * player.sprite.texture.width), round(SCALE * player.sprite.texture.height)]), camera)
player_rendered = True
if not player_rendered and z == player.z and y == math.ceil(player.pos.y + 1 + player.sprite.get_rect()[3] / METER):
# Draw player
screen.blit(pygame.transform.scale(player.sprite.texture.frame, [round(SCALE * player.sprite.texture.width), round(SCALE * player.sprite.texture.height)]), camera)
player_rendered = True
player.hud.render(screen, SCALE)
pygame.display.update()
# pygame.display.flip()

36
src/level.py Normal file
View File

@ -0,0 +1,36 @@
from .rand import rand
import math
def populate(map, generator, z):
# Place stairways
if z != 0:
mroom = generator.rooms[int(math.ceil(len(generator.rooms) / 2))] # Middle room
enterx = mroom.x + rand(2, mroom.width - 2)
entery = mroom.y + rand(2, mroom.height - 3)
map.set_tile(enterx, entery, z + 1, "map:stair_up")
generator.enterance = (enterx, entery)
lroom = generator.rooms[0]#len(generator.rooms) - 1] # Last room
exitx = lroom.x + rand(2, lroom.width - 2)
exity = lroom.y + rand(2, lroom.height - 3)
map.set_tile(exitx, exity, z + 1, "map:stair_down")
generator.exit = (exitx, exity)
# Place loot
loot = [
["loot:coins", 10],
["loot:pile", 50],
]
for room in generator.rooms:
if rand(0, 2) != 0: # 1 in 3 chance of no loot
for y in range(1, room.height - 2):
for x in range(1, room.width - 1):
if map.get_tile(x, y, z) == None:
placed = False
for l in loot:
if not placed and rand(1, l[1]) == 1: # 1 in n chance of placing
map.set_tile(room.x + x, room.y + y, z + 1, l[0])
placed = True

View File

@ -1,18 +0,0 @@
from .rand import rand
def place_loot(map, z):
loot = [
["loot:coins", 10],
["loot:pile", 50],
]
for room in map.generator.rooms:
if rand(0, 2) != 0: # 1 in 3 chance of no loot
for y in range(1, room.height - 2):
for x in range(1, room.width - 1):
placed = False
for l in loot:
if not placed and rand(1, l[1]) == 1: # 1 in n chance of placing
map.set_tile(room.x + x, room.y + y, 1, l[0])
placed = True

View File

@ -1,11 +1,12 @@
import pygame
import json, math
from . import assets, dungeon, loot, tiles
from . import assets, dungeon, level, tiles
from .tiles import Tile
from .vector import Vector
class Map:
map = []
generators = {}
def __init__(self, meter):
self.METER = meter # Pixels per 1 meter
@ -14,12 +15,14 @@ class Map:
# with open(filename) as file:
# self.map = json.load(file)
def generate(self):
self.generator = dungeon.Generator(80)
self.generator.generate(self, 0)
loot.place_loot(self, 0)
def generate(self, z):
generator = dungeon.Generator(80)
generator.generate(self, z)
self.generators[z] = generator
def collides(self, pos, rect):
level.populate(self, generator, z)
def collides(self, pos, z, rect):
# Player position handling really needs to be reworked ...
METER = self.METER
cx = pos.x + (rect[0] / METER)
@ -30,11 +33,12 @@ class Map:
ch = cy + (rect[3] / METER)
for y in range(py - 1, py + 1 + math.ceil(rect[3] / METER)):
for x in range(px - 1, px + 2):
if y >= 0 and y < len(self.map[1]) and x >= 0 and x < len(self.map[1][y]):
tile = self.map[1][y][x]
if y >= 0 and y < len(self.map[z]) and x >= 0 and x < len(self.map[z][y]):
tile = self.map[z][y][x]
if tile and tile.is_solid():
if cw >= x and cx <= (x + 1) and ch >= y and cy <= (y + 1):
return True
return False
def set_tile(self, x, y, z, name):

View File

@ -7,6 +7,8 @@ from .sprite import Sprite
class Player:
pos = Vector(0, 0)
z = 1
last_level_change = 0
hp = 100
coins = 0
@ -59,13 +61,13 @@ class Player:
oldx = self.pos.x
self.set_pos(self.pos.x + self.vel.x * self.speed * dtime, self.pos.y)
if map.collides(self.pos, self.sprite.rect):
if map.collides(self.pos, self.z, self.sprite.rect):
self.set_pos(oldx, self.pos.y)
oldy = self.pos.y
self.set_pos(self.pos.x, self.pos.y + self.vel.y * self.speed * dtime)
if map.collides(self.pos, self.sprite.rect):
if map.collides(self.pos, self.z, self.sprite.rect):
self.set_pos(self.pos.x, oldy)
if controller.is_down("up") or controller.is_down("down") or \

View File

@ -1,7 +1,10 @@
import math
import random
from . import tiles
import time
from . import controller, tiles
from .vector import Vector
# Structure tiles
tiles.register_tile("map:floor", {
"textures": ["floor.png"],
})
@ -33,7 +36,45 @@ tiles.register_tile("map:wall_corner_outer", {
],
})
def pick_up(self, dtime, map, player):
# Stairs
def prev_level(self, _, map, player):
# Lazy distance check
if math.hypot(player.pos.x - self.pos[0], player.pos.y + 1 - self.pos[1]) <= 2 \
and player.pos.y + 0.5 > self.pos[1] and controller.is_down("shift") \
and time.time() - player.last_level_change > 0.5: # Prevent multiple shifts
player.z = max(1, player.z - 2)
generator = map.generators[player.z - 1]
player.set_pos(generator.exit[0], generator.exit[1] - 1)
player.dir = 2
player.last_level_change = time.time()
tiles.register_tile("map:stair_up", {
"textures": ["stair_up.png"],
"solid": True,
"on_step": prev_level,
})
def next_level(self, _, map, player):
if math.hypot(player.pos.x - self.pos[0], player.pos.y + 1 - self.pos[1]) <= 1 \
and controller.is_down("shift") and time.time() - player.last_level_change > 0.5:
player.z += 2
if not player.z - 1 in map.generators:
map.generate(player.z - 1)
generator = map.generators[player.z - 1]
player.set_pos(generator.enterance[0], generator.enterance[1] + 0.1)
player.dir = 0
player.last_level_change = time.time()
tiles.register_tile("map:stair_down", {
"textures": ["stair_down.png"],
"solid": False,
"on_step": next_level,
})
# Loot
def pick_up(self, _, map, player):
METER = map.METER
rect = player.sprite.get_rect()
cx = player.pos.x + (rect[0] / METER)