Now About Social Code
summaryrefslogtreecommitdiff
path: root/scripts/bsp_level_generator.gd
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/bsp_level_generator.gd')
-rw-r--r--scripts/bsp_level_generator.gd256
1 files changed, 230 insertions, 26 deletions
diff --git a/scripts/bsp_level_generator.gd b/scripts/bsp_level_generator.gd
index af34e0b..543eb1d 100644
--- a/scripts/bsp_level_generator.gd
+++ b/scripts/bsp_level_generator.gd
@@ -1,20 +1,38 @@
-extends Node2D
+extends Node3D
+@export_category("Level Generator")
@export var width: int = 40
@export var height: int = 40
@export var min_dim: int = 5
+@export_category("Geometry Generator")
+## Geometry generation config
+@export var wall_thickness: float = 1
+
+var wall_inst = preload("res://models/wall.blend")
+var locker_inst = preload("res://prefabs/locker.tscn")
+var level_geo: Node3D
+
+enum Direction {LEFT, RIGHT}
+
+signal grid_generated(grid: Array[Tile], p_grid_width: int, p_grid_height: int)
+
var min_room_size: int = min_dim * min_dim
-var rects: Array[Vector4i] = []
+var grid_width = width + 1
+var grid_height = height + 1
+
+var rooms: Array[Rect2i] = []
class BSPNode:
+ var axis: int
var min_dims: Vector2i
var max_dims: Vector2i
var left: BSPNode
var right: BSPNode
- func _init(p_min_dims: Vector2i, p_max_dims: Vector2i, p_left: BSPNode, p_right: BSPNode):
+ func _init(p_axis: int, p_min_dims: Vector2i, p_max_dims: Vector2i, p_left: BSPNode, p_right: BSPNode):
+ axis = p_axis
min_dims = p_min_dims
max_dims = p_max_dims
left = p_left
@@ -22,24 +40,19 @@ class BSPNode:
func generate_level(axis: int, min_space: Vector2i, max_space: Vector2i, depth: int = 0) -> BSPNode:
var dims = max_space - min_space
- if dims[axis] / 2 < min_dim:
- #rects.append(Vector4i(min_space.x, min_space.y, max_space.x, max_space.y))
- return null
-
var new_axis = (axis + 1) % 2
# 10% we stop here and just create a big room
if (depth > 2 and randi_range(0, 9) == 0) \
or dims.x * dims.y <= min_dim * min_dim \
or dims[new_axis] / 2 < min_dim:
- rects.append(Vector4i(min_space.x, min_space.y, max_space.x, max_space.y))
- return BSPNode.new(min_space, max_space, null, null)
+ return BSPNode.new(new_axis, min_space, max_space, null, null)
# Calculate min and max ranges so that a split
# doesn't create a room that violates min dimensions
var min_value = min_space[axis] + min_dim
var max_value = max_space[axis] - min_dim
var split = randi_range(min_value, max_value)
- print("Spliting axis ", axis, " at ", split)
+ #print("Spliting axis ", axis, " at ", split)
var left_min_space = min_space
var left_max_space = max_space
@@ -51,28 +64,219 @@ func generate_level(axis: int, min_space: Vector2i, max_space: Vector2i, depth:
var right_max_space = max_space
var right = generate_level(new_axis, right_min_space, right_max_space, depth + 1)
- assert((left == null and right == null) or (left != null and right != null))
+ return BSPNode.new(axis, min_space, max_space, left, right)
+
+func is_leaf(node: BSPNode) -> bool:
+ return node.left == null and node.right == null
+
+func check_door(grid: Array[Tile.Tile], axis: int, door_pos: Vector2i) -> bool:
+ var room_left = door_pos
+ room_left[axis] -= 1
+
+ var room_right = door_pos
+ room_right[axis] += 1
+
+ if door_pos.x >= grid_width or door_pos.y >= grid_height:
+ return false
+ if room_left.x >= grid_width or room_left.y >= grid_height:
+ return false
+ if room_right.x >= grid_width or room_right.y >= grid_height:
+ return false
+
+ # Check if there are two spaces to connect
+ # And ensure no door already exists in the space
+ return grid[room_left.y * grid_width + room_left.x] == Tile.Tile.FLOOR \
+ and grid[room_right.y * grid_width + room_right.x] == Tile.Tile.FLOOR \
+ and grid[door_pos.y * grid_width + door_pos.x] == Tile.Tile.WALL
+
+func generate_grid(map: BSPNode, grid: Array[Tile.Tile]) -> void:
+ if is_leaf(map):
+ for y in range(map.min_dims.y, map.max_dims.y - 1):
+ for x in range(map.min_dims.x, map.max_dims.x - 1):
+ grid[(y+1) * grid_width + (x+1)] = Tile.Tile.FLOOR
+
+ # TODO double check room dimensions are correct here
+ var room = Rect2i(map.min_dims + Vector2i.ONE, map.max_dims - map.min_dims)
+ rooms.append(room)
+ else:
+ generate_grid(map.left, grid)
+ generate_grid(map.right, grid)
+
+ # Look for space on dividing wall to place door
+ var other_axis = (map.axis + 1) % 2
+ var split_axis = map.left.max_dims[map.axis]
+ var have_door = false
+ var tries = 0
+
+ while not have_door:
+ var test_door = randi_range(map.min_dims[other_axis], map.max_dims[other_axis] - 1)
+ var door_pos = Vector2i.ZERO
+ door_pos[map.axis] = split_axis
+ door_pos[other_axis] = test_door + 1
+ var door_pos2 = door_pos
+ door_pos2[other_axis] += 1
+
+ if check_door(grid, map.axis, door_pos) and check_door(grid, map.axis, door_pos2):
+ have_door = true
+
+ # Place grid for mesh
+ grid[door_pos.y * grid_width + door_pos.x] = Tile.Tile.DOOR
+ grid[door_pos2.y * grid_width + door_pos2.x] = Tile.Tile.DOOR
+ #door_pos[map.axis] -= 1
+ #grid[door_pos.y * grid_width + door_pos.x] = Tile.Tile.FLOOR
+
+ tries += 1
+ if tries > 1000:
+ print("Took too many attempts to generate a door")
+ get_tree().quit()
+ return
+
+func get_tile(grid: Array[Tile.Tile], pos: Vector2i) -> Tile.Tile:
+ return grid[pos.y * grid_width + pos.x]
+
+func place_lockers(grid: Array[Tile.Tile]) -> void:
+ for room in rooms:
+ var num_lockers = randi_range(0, 2)
+ print("Generating ", num_lockers, "Lockers")
+ for i in range(num_lockers):
+ var found = false
+ while not found:
+ var pos = Vector2i(randi_range(room.position.x, room.end.x - 1), randi_range(room.position.y, room.end.y - 1))
+ if grid[pos.y * grid_width + pos.x] == Tile.Tile.FLOOR \
+ and (get_tile(grid, pos + Vector2i(1, 0)) == Tile.Tile.WALL \
+ or get_tile(grid, pos + Vector2i(-1, 0)) == Tile.Tile.WALL \
+ or get_tile(grid, pos + Vector2i(0, 1)) == Tile.Tile.WALL \
+ or get_tile(grid, pos + Vector2i(0, -1)) == Tile.Tile.WALL) \
+ and get_tile(grid, pos + Vector2i(1, 0)) != Tile.Tile.DOOR \
+ and get_tile(grid, pos + Vector2i(-1, 0)) != Tile.Tile.DOOR \
+ and get_tile(grid, pos + Vector2i(0, 1)) != Tile.Tile.DOOR \
+ and get_tile(grid, pos + Vector2i(0, -1)) != Tile.Tile.DOOR:
+ grid[pos.y * grid_width + pos.x] = Tile.Tile.LOCKER
+ found = true
+
+func place_player(grid: Array[Tile.Tile]) -> Rect2i:
+ var player_room: Rect2i = rooms.pick_random()
+ var player_pos = Vector2i(player_room.position.x + player_room.size.x / 2, player_room.position.y + player_room.size.y / 2)
+
+ grid[player_pos.y * grid_width + player_pos.x] = Tile.Tile.PLAYER
+ return player_room
+
+func place_enemies(grid: Array[Tile.Tile], player_room: Rect2i) -> void:
+ var num_enemies = randi_range(4, 10)
+
+ for i in range(num_enemies):
+ var found_pos = false
+ while not found_pos:
+ var room: Rect2i = rooms.pick_random()
+ if room == player_room:
+ continue
+
+ var pos = Vector2i(randi_range(room.position.x, room.end.x - 1), randi_range(room.position.y, room.end.y - 1))
+ if grid[pos.y * grid_width + pos.x] == Tile.Tile.FLOOR:
+ grid[pos.y * grid_width + pos.x] = Tile.Tile.ENEMY
+ found_pos = true
+
+func place_exit(grid: Array[Tile.Tile], player_room: Rect2i) -> void:
+ var found_pos = false
+
+ while not found_pos:
+ var room: Rect2i = rooms.pick_random()
+ if room == player_room:
+ continue
+
+ var pos = Vector2i(randi_range(room.position.x, room.end.x - 1), randi_range(room.position.y, room.end.y - 1))
+ var left = pos + Vector2i.LEFT
+ var right = pos + Vector2i.RIGHT
+ var up = pos + Vector2i.UP
+ var down = pos + Vector2i.DOWN
+ if grid[pos.y * grid_width + pos.x] == Tile.Tile.FLOOR \
+ and grid[left.y * grid_width + left.x] == Tile.Tile.FLOOR \
+ and grid[right.y * grid_width + right.x] == Tile.Tile.FLOOR \
+ and grid[up.y * grid_width + up.x] == Tile.Tile.FLOOR \
+ and grid[down.y * grid_width + down.x] == Tile.Tile.FLOOR:
+ grid[pos.y * grid_width + pos.x] = Tile.Tile.PORTAL
+ found_pos = true
+
+func populate_grid(grid: Array[Tile.Tile]) -> void:
+ place_lockers(grid)
+ var player_room = place_player(grid)
+ place_exit(grid, player_room)
+ place_enemies(grid, player_room)
+
+func generate_plane(array: Array, pos: Vector3, dim: Array[Vector3], normal: Vector3) -> void:
+ var index = len(array[Mesh.ARRAY_VERTEX])
+ array[Mesh.ARRAY_VERTEX].append(wall_thickness*pos)
+ array[Mesh.ARRAY_VERTEX].append(wall_thickness*(pos+dim[0]))
+ array[Mesh.ARRAY_VERTEX].append(wall_thickness*(pos+dim[0]+dim[1]))
+ array[Mesh.ARRAY_VERTEX].append(wall_thickness*(pos+dim[1]))
- if left == null and right == null:
- rects.append(Vector4i(min_space.x, min_space.y, max_space.x, max_space.y))
+ array[Mesh.ARRAY_NORMAL].append(normal)
+ array[Mesh.ARRAY_NORMAL].append(normal)
+ array[Mesh.ARRAY_NORMAL].append(normal)
+ array[Mesh.ARRAY_NORMAL].append(normal)
- return BSPNode.new(min_space, max_space, left, right)
+ array[Mesh.ARRAY_INDEX].append(index + 0)
+ array[Mesh.ARRAY_INDEX].append(index + 1)
+ array[Mesh.ARRAY_INDEX].append(index + 2)
+ array[Mesh.ARRAY_INDEX].append(index + 0)
+ array[Mesh.ARRAY_INDEX].append(index + 2)
+ array[Mesh.ARRAY_INDEX].append(index + 3)
+
+func generate_geo(grid: Array[Tile.Tile], array: Array) -> void:
+ for y in range(grid_height):
+ for x in range(grid_width):
+ var tile = grid[y * grid_width + x]
+
+ if tile == Tile.Tile.WALL:
+ generate_plane(array, Vector3(x, 2, y), [Vector3(0, 0, 1), Vector3(0, -2, 0)], Vector3.LEFT)
+ generate_plane(array, Vector3(x, 2, y+1), [Vector3(1, 0, 0), Vector3(0, -2, 0)], Vector3.BACK)
+ generate_plane(array, Vector3(x+1, 2, y+1), [Vector3(0, 0, -1), Vector3(0, -2, 0)], Vector3.RIGHT)
+ generate_plane(array, Vector3(x+1, 2, y), [Vector3(-1, 0, 0), Vector3(0, -2, 0)], Vector3.FORWARD)
+ var wall: Node3D = level_geo.get_node("./Wall").duplicate()
+ wall.position = Vector3(x, 0, y)
+ add_child(wall)
+ else:
+ generate_plane(array, Vector3(x, 0, y), [Vector3(1, 0, 0), Vector3(0, 0, 1)], Vector3.UP)
+ var floor_tile: Node3D = level_geo.get_node("./Floor").duplicate()
+ floor_tile.position = Vector3(x, 0, y)
+ add_child(floor_tile)
+
+ var ciel_tile: Node3D = level_geo.get_node("./Floor").duplicate()
+ ciel_tile.rotate_z(deg_to_rad(180))
+ ciel_tile.position = Vector3(x+1, 2, y)
+ add_child(ciel_tile)
+
+ if tile == Tile.Tile.LOCKER:
+ var locker: Node3D = locker_inst.instantiate()
+ locker.position = Vector3(x, 0, y)
+ add_child(locker)
func _ready() -> void:
var starting_axis = randi_range(0, 1)
var min_space = Vector2i(0, 0)
var max_space = Vector2i(width, height)
var map = generate_level(starting_axis, min_space, max_space)
+ var grid: Array[Tile.Tile] = []
+ grid.resize(grid_width * grid_height)
+ grid.fill(Tile.Tile.WALL)
+
+ var surface_array = []
+ surface_array.resize(Mesh.ARRAY_MAX)
+
+ surface_array[Mesh.ARRAY_VERTEX] = PackedVector3Array()
+ surface_array[Mesh.ARRAY_INDEX] = PackedInt32Array()
+ surface_array[Mesh.ARRAY_NORMAL] = PackedVector3Array()
+
+ generate_grid(map, grid)
+ populate_grid(grid)
+ grid_generated.emit(grid, grid_width, grid_height)
+ level_geo = wall_inst.instantiate()
+ generate_geo(grid, surface_array)
-func _draw():
- var mult = 1
- for rect in rects:
- var pos1 = 5*Vector2i(rect.x, rect.y)
- var pos2 = 5*Vector2i(rect.z, rect.w)
- var dims = pos2 - pos1
- draw_rect(Rect2(pos1, dims), mult*Color(0.01, 0.01, 0.01))
- draw_line(pos1, Vector2i(pos1.x, pos2.y), Color(0, 1, 0))
- draw_line(Vector2i(pos1.x, pos2.y), pos2, Color(0, 1, 0))
- draw_line(pos2, Vector2i(pos2.x, pos1.y), Color(0, 1, 0))
- draw_line(Vector2i(pos2.x, pos1.y), pos1, Color(0, 1, 0))
- mult += 2
+ var mesh = ArrayMesh.new()
+ mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array)
+ var tri_mesh = mesh.create_trimesh_shape()
+ $NavigationRegion3D/StaticBody3D/CollisionShape3D.shape = tri_mesh
+ #$NavigationRegion3D.navigation_mesh = NavigationMesh.new()
+ #$NavigationRegion3D.navigation_mesh.radius
+ $NavigationRegion3D.bake_navigation_mesh(false)