extends Node3D const ROOM_WIDTH := 20.0 const ROOM_DEPTH := 20.0 const ROOM_HEIGHT := 6.0 const WALL_THICKNESS := 0.5 const DOOR_GAP := 4.0 const ROOM_SPACING := ROOM_WIDTH + WALL_THICKNESS var player_ref: Node3D var room_data: Array = [] func _ready() -> void: randomize() _setup_lighting() _spawn_player() _generate_and_build() func _generate_and_build() -> void: var grid = DungeonGenerator.new().generate() for key in grid: var parts = key.split(",") _build_room(grid, int(parts[0]), int(parts[1]), grid[key]) func _has_valid_door(grid: Dictionary, doors: int, side: int, gx: int, gz: int) -> bool: if not ((doors >> side) & 1): return false var nkey = "%d,%d" % [gx + DungeonGenerator.SIDE_DX[side], gz + DungeonGenerator.SIDE_DZ[side]] if not (nkey in grid): return false var opp = (side + 2) % 4 return (grid[nkey]["doors"] >> opp) & 1 func _build_room(grid: Dictionary, gx: int, gz: int, rd: Dictionary) -> void: var offset := Vector3(gx * ROOM_SPACING, 0, gz * ROOM_SPACING) var room := Node3D.new() room.name = "Room_%d_%d" % [gx, gz] room.position = offset add_child(room) rd["node"] = room rd["enemies"] = [] room_data.append(rd) var body := StaticBody3D.new() body.name = "Collisions" room.add_child(body) var floor_mat := StandardMaterial3D.new() floor_mat.shading_mode = StandardMaterial3D.SHADING_MODE_PER_VERTEX match rd["type"]: "entrance": floor_mat.albedo_color = Color(0.25, 0.4, 0.25) "exit": floor_mat.albedo_color = Color(0.35, 0.2, 0.3) "treasure": floor_mat.albedo_color = Color(0.4, 0.35, 0.2) _: floor_mat.albedo_color = Color(0.35, 0.32, 0.28) var floor_mesh := MeshInstance3D.new() floor_mesh.mesh = BoxMesh.new() floor_mesh.mesh.size = Vector3(ROOM_WIDTH, 1.0, ROOM_DEPTH) floor_mesh.position = Vector3(0, -0.5, 0) floor_mesh.material_override = floor_mat body.add_child(floor_mesh) var floor_col := CollisionShape3D.new() floor_col.shape = BoxShape3D.new() floor_col.shape.size = Vector3(ROOM_WIDTH, 1.0, ROOM_DEPTH) floor_col.position = Vector3(0, -0.5, 0) body.add_child(floor_col) var wall_mat := StandardMaterial3D.new() wall_mat.shading_mode = StandardMaterial3D.SHADING_MODE_PER_VERTEX match rd["type"]: "entrance": wall_mat.albedo_color = Color(0.3, 0.5, 0.3) "exit": wall_mat.albedo_color = Color(0.5, 0.25, 0.4) "treasure": wall_mat.albedo_color = Color(0.55, 0.48, 0.25) _: wall_mat.albedo_color = Color(0.55, 0.52, 0.48) _build_walls(grid, body, rd["doors"], wall_mat, gx, gz) if rd["type"] not in ["entrance", "exit", "treasure"]: _spawn_room_enemies(room, rd, offset) if rd["type"] == "treasure": _spawn_treasure(room) func _build_walls(grid: Dictionary, body: Node, doors: int, wall_mat: StandardMaterial3D, gx: int, gz: int) -> void: var hw := ROOM_WIDTH / 2.0 var hd := ROOM_DEPTH / 2.0 var wt := WALL_THICKNESS var wt2 := wt / 2.0 var hh := ROOM_HEIGHT / 2.0 for side in 4: var has_door = _has_valid_door(grid, doors, side, gx, gz) match side: 0: if has_door: var seg := (ROOM_DEPTH - DOOR_GAP) / 2.0 body.add_child(_wall_piece(Vector3(-hw - wt2, hh, hd - seg / 2), Vector3(wt, ROOM_HEIGHT, seg), wall_mat, "W1")) body.add_child(_wall_piece(Vector3(-hw - wt2, hh, -hd + seg / 2), Vector3(wt, ROOM_HEIGHT, seg), wall_mat, "W2")) else: body.add_child(_wall_piece(Vector3(-hw - wt2, hh, 0), Vector3(wt, ROOM_HEIGHT, ROOM_DEPTH), wall_mat, "W")) 1: if has_door: var seg := (ROOM_WIDTH + wt * 2 - DOOR_GAP) / 2.0 body.add_child(_wall_piece(Vector3(hw + wt2 - seg / 2, hh, hd + wt2), Vector3(seg, ROOM_HEIGHT, wt), wall_mat, "S1")) body.add_child(_wall_piece(Vector3(-hw - wt2 + seg / 2, hh, hd + wt2), Vector3(seg, ROOM_HEIGHT, wt), wall_mat, "S2")) else: body.add_child(_wall_piece(Vector3(0, hh, hd + wt2), Vector3(ROOM_WIDTH + wt * 2, ROOM_HEIGHT, wt), wall_mat, "S")) 2: if has_door: var seg := (ROOM_DEPTH - DOOR_GAP) / 2.0 body.add_child(_wall_piece(Vector3(hw + wt2, hh, hd - seg / 2), Vector3(wt, ROOM_HEIGHT, seg), wall_mat, "E1")) body.add_child(_wall_piece(Vector3(hw + wt2, hh, -hd + seg / 2), Vector3(wt, ROOM_HEIGHT, seg), wall_mat, "E2")) else: body.add_child(_wall_piece(Vector3(hw + wt2, hh, 0), Vector3(wt, ROOM_HEIGHT, ROOM_DEPTH), wall_mat, "E")) 3: if has_door: var seg := (ROOM_WIDTH + wt * 2 - DOOR_GAP) / 2.0 body.add_child(_wall_piece(Vector3(hw + wt2 - seg / 2, hh, -hd - wt2), Vector3(seg, ROOM_HEIGHT, wt), wall_mat, "N1")) body.add_child(_wall_piece(Vector3(-hw - wt2 + seg / 2, hh, -hd - wt2), Vector3(seg, ROOM_HEIGHT, wt), wall_mat, "N2")) else: body.add_child(_wall_piece(Vector3(0, hh, -hd - wt2), Vector3(ROOM_WIDTH + wt * 2, ROOM_HEIGHT, wt), wall_mat, "N")) func _wall_piece(pos: Vector3, size: Vector3, mat: StandardMaterial3D, name: String) -> StaticBody3D: var body := StaticBody3D.new() body.name = name var mesh := MeshInstance3D.new() mesh.mesh = BoxMesh.new() mesh.mesh.size = size mesh.position = pos mesh.material_override = mat.duplicate() body.add_child(mesh) var col := CollisionShape3D.new() col.shape = BoxShape3D.new() col.shape.size = size col.position = pos body.add_child(col) return body func _spawn_room_enemies(room: Node3D, rd: Dictionary, _offset: Vector3) -> void: var enemy_scene := preload("res://scenes/enemy.tscn") var count := 2 + randi() % 2 for i in count: var angle := (i / float(count)) * TAU + randf() * 0.5 var dist := randf_range(3.0, 7.0) var pos := Vector3(cos(angle) * dist, 1.0, sin(angle) * dist) var enemy := enemy_scene.instantiate() enemy.position = pos room.add_child(enemy) enemy.spawn_position = enemy.global_position enemy.set_target(player_ref) rd["enemies"].append(enemy) func _spawn_treasure(room: Node3D) -> void: var chest := MeshInstance3D.new() chest.mesh = BoxMesh.new() chest.mesh.size = Vector3(1.5, 1.0, 1.0) chest.position = Vector3(0, 1.0, 0) var mat := StandardMaterial3D.new() mat.albedo_color = Color(1.0, 0.84, 0.0) mat.emission = Color(1.0, 0.84, 0.0) mat.emission_energy = 2.0 mat.shading_mode = StandardMaterial3D.SHADING_MODE_PER_VERTEX chest.material_override = mat room.add_child(chest) func _process(_delta: float) -> void: if not player_ref: return var best_name: StringName = "" var best_dist := INF for rd in room_data: var dist = (player_ref.global_position - rd["node"].global_position).length_squared() if dist < best_dist: best_dist = dist best_name = rd["node"].name for rd in room_data: var active = rd["node"].name == best_name for enemy in rd["enemies"]: if enemy and enemy.is_inside_tree(): enemy.chasing = active func _spawn_player() -> void: var player_scene := preload("res://scenes/player.tscn") var player := player_scene.instantiate() player.name = "Player" player.position = Vector3(0, 2.0, 0) add_child(player) player_ref = player func _setup_lighting() -> void: var dir_light := DirectionalLight3D.new() dir_light.position = Vector3(5, 10, 5) dir_light.rotation_degrees = Vector3(-45, 45, 0) dir_light.shadow_enabled = true dir_light.name = "Sun" add_child(dir_light) var world_env := WorldEnvironment.new() var env := Environment.new() env.ambient_light_color = Color(0.6, 0.7, 1.0) env.ambient_light_energy = 0.5 world_env.environment = env world_env.name = "WorldEnvironment" add_child(world_env)