From db44a1a318766e29a786a2d7f7199a751e29566e Mon Sep 17 00:00:00 2001 From: Mike McGhen Date: Tue, 10 Mar 2026 11:24:36 -0400 Subject: [PATCH] Implements mini map and updates door lights --- scripts/event_bus.gd | 2 + scripts/rooms/door.gd | 23 ++++-- scripts/rooms/floor.gd | 7 ++ scripts/ui/hud.gd | 10 +++ scripts/ui/mini_map.gd | 171 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 scripts/ui/mini_map.gd diff --git a/scripts/event_bus.gd b/scripts/event_bus.gd index 7f6d804..d7b486c 100644 --- a/scripts/event_bus.gd +++ b/scripts/event_bus.gd @@ -15,6 +15,8 @@ signal floor_cleared(floor_number: int) signal room_entered(room_id: String) signal room_cleared(room_id: String) signal room_revealed +signal room_entered_at(grid_pos: Vector2i) +signal floor_map_ready(rooms_data: Dictionary) # --------------------------------------------------------------------------- # Items & Loot diff --git a/scripts/rooms/door.gd b/scripts/rooms/door.gd index e81ee76..01c9345 100644 --- a/scripts/rooms/door.gd +++ b/scripts/rooms/door.gd @@ -8,15 +8,20 @@ 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) +# Indicator light colors - only for special rooms, matching floor colors but brighter 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 + RoomData.RoomType.ITEM: Color(0.2, 0.45, 0.2), # green (brighter version of 0.10, 0.16, 0.10) + RoomData.RoomType.SHOP: Color(0.9, 0.75, 0.1), # yellow + RoomData.RoomType.BOSS: Color(0.7, 0.15, 0.15), # red (brighter version of 0.20, 0.08, 0.08) } +# Room types that should show indicator lights +const SPECIAL_ROOM_TYPES := [ + RoomData.RoomType.ITEM, + RoomData.RoomType.SHOP, + RoomData.RoomType.BOSS, +] + # --------------------------------------------------------------------------- # State # --------------------------------------------------------------------------- @@ -71,6 +76,10 @@ func _build_barrier() -> void: func _build_indicator() -> void: + # Only show indicators for special rooms (ITEM, SHOP, BOSS) + if adjacent_room_type not in SPECIAL_ROOM_TYPES: + return + _indicator = Polygon2D.new() # Small diamond shape for indicator @@ -89,7 +98,7 @@ func _build_indicator() -> void: "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]) + _indicator.color = INDICATOR_COLORS[adjacent_room_type] add_child(_indicator) diff --git a/scripts/rooms/floor.gd b/scripts/rooms/floor.gd index 4c19d56..87e00e4 100644 --- a/scripts/rooms/floor.gd +++ b/scripts/rooms/floor.gd @@ -20,6 +20,7 @@ const ENTRY_INSET := 60.0 # how far from wall edge the player appears # State # --------------------------------------------------------------------------- var _rooms: Dictionary = {} # Vector2i -> Room +var _rooms_data: Dictionary = {} # Vector2i -> RoomData (for mini map) var _current_grid: Vector2i var _transitioning: bool = false @@ -40,6 +41,7 @@ func _build_floor(floor_data: Array) -> void: rooms_container.add_child(room_node) room_node.setup(room_data, self) _rooms[room_data.grid_pos] = room_node + _rooms_data[room_data.grid_pos] = room_data if room_data.type == RoomData.RoomType.START: start_pos = room_data.grid_pos @@ -51,6 +53,10 @@ func _build_floor(floor_data: Array) -> void: _current_grid = start_pos _rooms[start_pos].activate() + # Emit signals for mini map + EventBus.floor_map_ready.emit(_rooms_data) + EventBus.room_entered_at.emit(start_pos) + # --------------------------------------------------------------------------- # Transitions # --------------------------------------------------------------------------- @@ -59,6 +65,7 @@ func transition_to(target_grid: Vector2i, from_direction: String) -> void: return _transitioning = true _current_grid = target_grid + EventBus.room_entered_at.emit(target_grid) var new_world := _grid_to_world(target_grid) diff --git a/scripts/ui/hud.gd b/scripts/ui/hud.gd index c886cac..d543088 100644 --- a/scripts/ui/hud.gd +++ b/scripts/ui/hud.gd @@ -1,5 +1,7 @@ extends CanvasLayer +const MiniMapScript = preload("res://scripts/ui/mini_map.gd") + # --------------------------------------------------------------------------- # Heart geometry — two polygons per slot: background (dark) + fill (red) # --------------------------------------------------------------------------- @@ -39,6 +41,7 @@ const ORIGIN := Vector2(32, 32) # center of first heart (screen px) # Each entry: { "bg": Polygon2D, "fill": Polygon2D } var _slots: Array = [] var _credits_label: Label +var _mini_map: Control # DEPRECATED — Boss bar nodes (commented out) #var _boss_bar_root: Node2D = null @@ -56,6 +59,7 @@ func _ready() -> void: #EventBus.boss_health_changed.connect(_on_boss_health_changed) #EventBus.boss_died.connect(_on_boss_died) _build_credits_label() + _build_mini_map() # Initialise once the scene is fully set up call_deferred("_init_from_player") @@ -126,6 +130,12 @@ func _build_credits_label() -> void: _credits_label.text = "SCRAP: %d" % amount add_child(_credits_label) + +func _build_mini_map() -> void: + _mini_map = Control.new() + _mini_map.set_script(MiniMapScript) + add_child(_mini_map) + func _on_credits_changed(new_total: int) -> void: _credits_label.text = "SCRAP: %d" % new_total diff --git a/scripts/ui/mini_map.gd b/scripts/ui/mini_map.gd new file mode 100644 index 0000000..df8124b --- /dev/null +++ b/scripts/ui/mini_map.gd @@ -0,0 +1,171 @@ +extends Control + +# --------------------------------------------------------------------------- +# Layout constants +# --------------------------------------------------------------------------- +const MAP_SIZE := Vector2(150, 150) +const ROOM_SIZE := Vector2(18, 10) # 16:9 aspect ratio like actual rooms +const ROOM_GAP := 4.0 +const PADDING := 10.0 +const CONNECTION_WIDTH := 2.0 + +# --------------------------------------------------------------------------- +# Colors (brightened versions of room.gd floor colors) +# --------------------------------------------------------------------------- +const ROOM_COLORS := { + RoomData.RoomType.START: Color(0.24, 0.24, 0.36), # blue-grey + RoomData.RoomType.COMBAT: Color(0.24, 0.24, 0.36), # blue-grey + RoomData.RoomType.ITEM: Color(0.20, 0.32, 0.20), # green + RoomData.RoomType.SHOP: Color(0.32, 0.24, 0.16), # brown + RoomData.RoomType.BOSS: Color(0.40, 0.16, 0.16), # red +} +const CURRENT_OUTLINE := Color(1.0, 1.0, 0.4) # bright yellow +const CONNECTION_COLOR := Color(0.5, 0.5, 0.6) +const UNVISITED_ALPHA := 0.35 +const BACKGROUND_COLOR := Color(0.08, 0.08, 0.10, 0.85) + +# --------------------------------------------------------------------------- +# State +# --------------------------------------------------------------------------- +var _rooms_data: Dictionary = {} # Vector2i -> RoomData +var _current_pos: Vector2i = Vector2i.ZERO +var _grid_min: Vector2i = Vector2i.ZERO +var _grid_max: Vector2i = Vector2i.ZERO + +# --------------------------------------------------------------------------- +# Setup +# --------------------------------------------------------------------------- +func _ready() -> void: + # Position in top-right corner + anchor_left = 1.0 + anchor_right = 1.0 + anchor_top = 0.0 + anchor_bottom = 0.0 + offset_left = -MAP_SIZE.x - PADDING + offset_right = -PADDING + offset_top = PADDING + offset_bottom = MAP_SIZE.y + PADDING + custom_minimum_size = MAP_SIZE + + # Connect signals + EventBus.floor_map_ready.connect(_on_floor_map_ready) + EventBus.room_entered_at.connect(_on_room_entered) + EventBus.run_ended.connect(_on_run_ended) + +# --------------------------------------------------------------------------- +# Signal handlers +# --------------------------------------------------------------------------- +func _on_floor_map_ready(rooms_data: Dictionary) -> void: + _rooms_data = rooms_data + _compute_grid_bounds() + queue_redraw() + + +func _on_room_entered(grid_pos: Vector2i) -> void: + _current_pos = grid_pos + queue_redraw() + + +func _on_run_ended(_reached_hub: bool) -> void: + _rooms_data.clear() + queue_redraw() + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +func _compute_grid_bounds() -> void: + if _rooms_data.is_empty(): + _grid_min = Vector2i.ZERO + _grid_max = Vector2i.ZERO + return + + var first := true + for pos in _rooms_data.keys(): + if first: + _grid_min = pos + _grid_max = pos + first = false + else: + _grid_min.x = mini(_grid_min.x, pos.x) + _grid_min.y = mini(_grid_min.y, pos.y) + _grid_max.x = maxi(_grid_max.x, pos.x) + _grid_max.y = maxi(_grid_max.y, pos.y) + + +func _get_visible_rooms() -> Dictionary: + # Returns {Vector2i -> {"data": RoomData, "visited": bool}} + var visible := {} + var known_positions: Array[Vector2i] = [] + + # First pass: collect visited rooms and their adjacent positions + for pos in _rooms_data.keys(): + var room: RoomData = _rooms_data[pos] + if room.visited: + visible[pos] = {"data": room, "visited": true} + for conn_pos in room.connections.values(): + if conn_pos not in known_positions: + known_positions.append(conn_pos) + + # Second pass: add adjacent (known but unvisited) rooms + for pos in known_positions: + if pos not in visible and _rooms_data.has(pos): + visible[pos] = {"data": _rooms_data[pos], "visited": false} + + return visible + + +func _grid_to_map_pos(grid_pos: Vector2i) -> Vector2: + # Convert grid position to local drawing coordinates + var cell_size := ROOM_SIZE + Vector2(ROOM_GAP, ROOM_GAP) + var grid_size := Vector2i(_grid_max.x - _grid_min.x + 1, _grid_max.y - _grid_min.y + 1) + var total_size := Vector2(grid_size.x, grid_size.y) * cell_size - Vector2(ROOM_GAP, ROOM_GAP) + + # Center the map within the control + var centering := (MAP_SIZE - total_size) / 2.0 + var offset := Vector2( + (grid_pos.x - _grid_min.x) * cell_size.x, + (grid_pos.y - _grid_min.y) * cell_size.y + ) + return centering + offset + +# --------------------------------------------------------------------------- +# Drawing +# --------------------------------------------------------------------------- +func _draw() -> void: + if _rooms_data.is_empty(): + return + + # Background panel + draw_rect(Rect2(Vector2.ZERO, MAP_SIZE), BACKGROUND_COLOR) + + var visible := _get_visible_rooms() + + # Draw connections first (under rooms) + for pos in visible.keys(): + var room: RoomData = visible[pos]["data"] + var room_center := _grid_to_map_pos(pos) + ROOM_SIZE / 2.0 + for dir in room.connections.keys(): + var conn_pos: Vector2i = room.connections[dir] + if visible.has(conn_pos): + var conn_center := _grid_to_map_pos(conn_pos) + ROOM_SIZE / 2.0 + # Only draw each connection once (from lower to higher grid pos) + if pos.x < conn_pos.x or (pos.x == conn_pos.x and pos.y < conn_pos.y): + draw_line(room_center, conn_center, CONNECTION_COLOR, CONNECTION_WIDTH) + + # Draw rooms + for pos in visible.keys(): + var info: Dictionary = visible[pos] + var room: RoomData = info["data"] + var is_visited: bool = info["visited"] + var map_pos := _grid_to_map_pos(pos) + var rect := Rect2(map_pos, ROOM_SIZE) + + # Room color with alpha based on visited state + var color: Color = ROOM_COLORS.get(room.type, ROOM_COLORS[RoomData.RoomType.COMBAT]) + if not is_visited: + color.a = UNVISITED_ALPHA + draw_rect(rect, color) + + # Current room indicator - bright outline + if pos == _current_pos: + draw_rect(rect, CURRENT_OUTLINE, false, 2.0)