Fixes room spawning and adds physical doors and indicator lights

This commit is contained in:
2026-03-10 11:16:48 -04:00
parent 7559114623
commit b709b64a52
6 changed files with 172 additions and 5 deletions
+113
View File
@@ -0,0 +1,113 @@
extends Node2D
class_name RoomDoor
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
const DOOR_THICKNESS := 12.0
const INDICATOR_SIZE := 10.0
const INDICATOR_OFFSET := 20.0 # distance above door
# Indicator light colors (brighter versions of room floor colors)
const INDICATOR_COLORS := {
RoomData.RoomType.START: Color(0.3, 0.3, 0.5), # blue-gray
RoomData.RoomType.COMBAT: Color(0.35, 0.35, 0.55), # blue-gray
RoomData.RoomType.ITEM: Color(0.2, 0.5, 0.2), # green
RoomData.RoomType.SHOP: Color(0.6, 0.45, 0.2), # gold/amber
RoomData.RoomType.BOSS: Color(0.7, 0.15, 0.15), # red
}
# ---------------------------------------------------------------------------
# State
# ---------------------------------------------------------------------------
var direction: String = "" # "up", "down", "left", "right"
var door_size: Vector2 = Vector2.ZERO
var adjacent_room_type: RoomData.RoomType = RoomData.RoomType.COMBAT
var _barrier: StaticBody2D = null
var _barrier_collision: CollisionShape2D = null
var _barrier_visual: Polygon2D = null
var _indicator: Polygon2D = null
var _is_locked: bool = false
# ---------------------------------------------------------------------------
# Setup
# ---------------------------------------------------------------------------
func setup(dir: String, size: Vector2, adj_type: RoomData.RoomType) -> void:
direction = dir
door_size = size
adjacent_room_type = adj_type
_build_barrier()
_build_indicator()
# Start unlocked (open)
unlock()
func _build_barrier() -> void:
_barrier = StaticBody2D.new()
_barrier.collision_layer = 1 # world layer - blocks player
_barrier.collision_mask = 0
_barrier_collision = CollisionShape2D.new()
var shape := RectangleShape2D.new()
shape.size = door_size
_barrier_collision.shape = shape
_barrier.add_child(_barrier_collision)
# Visual - solid rectangle
_barrier_visual = Polygon2D.new()
var hx := door_size.x / 2.0
var hy := door_size.y / 2.0
_barrier_visual.polygon = PackedVector2Array([
Vector2(-hx, -hy), Vector2(hx, -hy),
Vector2(hx, hy), Vector2(-hx, hy),
])
_barrier_visual.color = Color(0.4, 0.35, 0.3) # metallic brown
_barrier.add_child(_barrier_visual)
add_child(_barrier)
func _build_indicator() -> void:
_indicator = Polygon2D.new()
# Small diamond shape for indicator
var s := INDICATOR_SIZE
_indicator.polygon = PackedVector2Array([
Vector2(0, -s), Vector2(s, 0),
Vector2(0, s), Vector2(-s, 0),
])
# Position based on door direction
var offset := Vector2.ZERO
match direction:
"up": offset = Vector2(0, -door_size.y / 2.0 - INDICATOR_OFFSET)
"down": offset = Vector2(0, door_size.y / 2.0 + INDICATOR_OFFSET)
"left": offset = Vector2(-door_size.x / 2.0 - INDICATOR_OFFSET, 0)
"right": offset = Vector2(door_size.x / 2.0 + INDICATOR_OFFSET, 0)
_indicator.position = offset
_indicator.color = INDICATOR_COLORS.get(adjacent_room_type, INDICATOR_COLORS[RoomData.RoomType.COMBAT])
add_child(_indicator)
# ---------------------------------------------------------------------------
# Lock/Unlock
# ---------------------------------------------------------------------------
func lock() -> void:
_is_locked = true
_barrier_collision.disabled = false
_barrier_visual.visible = true
func unlock() -> void:
_is_locked = false
_barrier_collision.disabled = true
_barrier_visual.visible = false
func is_locked() -> bool:
return _is_locked
+8
View File
@@ -90,3 +90,11 @@ func _entry_offset(from_direction: String) -> Vector2:
# ---------------------------------------------------------------------------
func _grid_to_world(grid_pos: Vector2i) -> Vector2:
return Vector2(grid_pos.x * ROOM_W, grid_pos.y * ROOM_H)
# Returns the RoomData.RoomType for the room at the given grid position.
# Returns -1 if no room exists at that position.
func get_room_type_at(grid_pos: Vector2i) -> int:
if _rooms.has(grid_pos):
return _rooms[grid_pos].data.type
return -1
+5 -3
View File
@@ -3,9 +3,9 @@ class_name FloorGenerator
const GRID_START := Vector2i(5, 5)
const FLOOR_ROOM_COUNTS := {
1: {"min": 5, "max": 6}, # 4-5 combat + start + boss
2: {"min": 6, "max": 7}, # 5-6 combat + start + boss
3: {"min": 3, "max": 4}, # 2-3 combat + start + boss
1: {"min": 8, "max": 9}, # 4-5 combat + start + boss + item + shop
2: {"min": 9, "max": 10}, # 5-6 combat + start + boss + item + shop
3: {"min": 6, "max": 7}, # 2-3 combat + start + boss + item + shop
}
const DIRS := {
@@ -29,6 +29,7 @@ static func generate(floor_number: int) -> Array:
var start := RoomData.new()
start.type = RoomData.RoomType.START
start.grid_pos = GRID_START
start.id = "%d_%d" % [GRID_START.x, GRID_START.y]
rooms[GRID_START] = start
var queue: Array = [GRID_START]
@@ -59,6 +60,7 @@ static func generate(floor_number: int) -> Array:
var r := RoomData.new()
r.type = RoomData.RoomType.COMBAT
r.grid_pos = next
r.id = "%d_%d" % [next.x, next.y]
rooms[next] = r
queue.append(next)
+43 -2
View File
@@ -55,6 +55,7 @@ var data: RoomData = null
var _floor: Node = null # reference to floor.gd node
var _enemy_count: int = 0
var _doors: Dictionary = {} # direction -> Area2D trigger
var _physical_doors: Dictionary = {} # direction -> RoomDoor
# ---------------------------------------------------------------------------
# Node refs (set by room_base.tscn)
@@ -62,6 +63,7 @@ var _doors: Dictionary = {} # direction -> Area2D trigger
@onready var floor_poly: Polygon2D = $Floor
@onready var walls_node: Node2D = $Walls
@onready var door_triggers: Node2D = $DoorTriggers
@onready var doors_node: Node2D = $Doors
@onready var contents: Node2D = $Contents
# ---------------------------------------------------------------------------
@@ -73,12 +75,13 @@ func setup(room_data: RoomData, floor_ref: Node) -> void:
_apply_floor_color()
_build_walls()
_build_door_triggers()
_build_physical_doors()
# ---------------------------------------------------------------------------
# Activate — called on first (and repeat) entry
# ---------------------------------------------------------------------------
func activate() -> void:
EventBus.room_entered.emit(data.id if "id" in data else "")
EventBus.room_entered.emit(data.id)
if data.visited:
_update_door_locks()
return
@@ -227,6 +230,35 @@ func _on_door_entered(body: Node, direction: String) -> void:
"transition_to", data.connections[direction], direction
)
# ---------------------------------------------------------------------------
# Physical doors with lock barriers and indicator lights
# ---------------------------------------------------------------------------
func _build_physical_doors() -> void:
const DOOR_THICKNESS := 12.0
var hw := ROOM_W / 2.0
var hh := ROOM_H / 2.0
# Door positions and sizes per direction
var configs := {
"up": {"pos": Vector2(0, -hh + WALL_T / 2.0), "size": Vector2(DOOR_W, DOOR_THICKNESS)},
"down": {"pos": Vector2(0, hh - WALL_T / 2.0), "size": Vector2(DOOR_W, DOOR_THICKNESS)},
"left": {"pos": Vector2(-hw + WALL_T / 2.0, 0), "size": Vector2(DOOR_THICKNESS, DOOR_H)},
"right": {"pos": Vector2(hw - WALL_T / 2.0, 0), "size": Vector2(DOOR_THICKNESS, DOOR_H)},
}
for dir in data.connections.keys():
var cfg = configs[dir]
var adj_grid: Vector2i = data.connections[dir]
var adj_type: int = _floor.get_room_type_at(adj_grid)
if adj_type < 0:
adj_type = RoomData.RoomType.COMBAT # fallback
var door := RoomDoor.new()
door.position = cfg["pos"]
door.setup(dir, cfg["size"], adj_type as RoomData.RoomType)
doors_node.add_child(door)
_physical_doors[dir] = door
# ---------------------------------------------------------------------------
# Enemy spawning
# ---------------------------------------------------------------------------
@@ -284,7 +316,7 @@ func _on_enemy_died() -> void:
_spawn_body_part_drop()
_spawn_floor_exit()
room_cleared.emit()
EventBus.room_cleared.emit(data.id if "id" in data else "")
EventBus.room_cleared.emit(data.id)
_update_door_locks()
@@ -512,5 +544,14 @@ func _update_door_locks() -> void:
var is_combat := data.type == RoomData.RoomType.COMBAT \
or data.type == RoomData.RoomType.BOSS
var locked := data.visited and not data.cleared and is_combat
# Update door triggers (for transitions)
for area in _doors.values():
(area as Area2D).monitoring = not locked
# Update physical door barriers
for door in _physical_doors.values():
if locked:
door.lock()
else:
door.unlock()
+1
View File
@@ -2,6 +2,7 @@ class_name RoomData
enum RoomType { START, COMBAT, ITEM, SHOP, BOSS }
var id: String = "" # unique identifier (e.g., "5_5" from grid pos)
var type: RoomType = RoomType.COMBAT
var grid_pos: Vector2i = Vector2i.ZERO
var connections: Dictionary = {} # "up"/"down"/"left"/"right" -> Vector2i