feat: 3D blobber dungeon generator (PR 1)

Replaces the 2D-only demo pipeline with a 3D cell-based blobber
generator. Per-cell face walls, per-material mesh emission, and a
GDExtension binding that returns a Dictionary with ArrayMesh surfaces
the demo consumes directly.

- src/blobber/: cell3d_t data model, dungeon container, pipeline that
  wraps the 2D generator per level and materializes into cell3d
- src/mesh/: face-quad emitter with per-material groups + .obj dump
- src/genesis3d_main.c: new CLI driving the blobber + mesh
- godot/: BrogueGen.generate_dungeon(seed, num_levels, depth) binding
  with dungeon_to_dict packing cells + mesh surfaces
- demo/: demo_blobber.tscn + dungeon_builder.gd, func_godot addon for
  the .map export path, point/entity templates, TrenchBroom docs
- Retired: old arcade/FPS demo scenes and their scripts, unused meshlib

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
saarsena@gmail.com 2026-04-18 13:24:27 -04:00
parent 6ee49c3375
commit 7a6ae79d01
160 changed files with 7209 additions and 2072 deletions

View file

@ -1,127 +0,0 @@
[gd_scene format=3 uid="uid://jysbib77g851"]
[ext_resource type="Script" path="res://scripts/arcade/arcade_scene.gd" id="1_scene"]
[ext_resource type="Script" path="res://scripts/arcade/player_arcade.gd" id="2_player"]
[ext_resource type="Script" path="res://scripts/arcade/hud.gd" id="3_hud"]
[sub_resource type="Environment" id="Env1"]
background_mode = 1
background_color = Color(0.03, 0.03, 0.05, 1)
ambient_light_source = 2
ambient_light_color = Color(0.55, 0.55, 0.65, 1)
ambient_light_energy = 0.45
fog_enabled = false
[sub_resource type="CapsuleShape3D" id="PlayerCapsule"]
radius = 0.3
height = 1.7
[node name="Arcade" type="Node3D"]
script = ExtResource("1_scene")
base_seed = 25
start_depth = 1
total_depths = 10
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Env1")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.707107, -0.5, 0.5, 0, 0.707107, 0.707107, -0.707107, -0.5, 0.5, 0, 10, 0)
light_color = Color(1, 0.95, 0.85, 1)
light_energy = 0.9
shadow_enabled = true
[node name="World" type="Node3D" parent="."]
[node name="Player" type="CharacterBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 39, 1.1, 14)
script = ExtResource("2_player")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0)
shape = SubResource("PlayerCapsule")
[node name="Camera3D" type="Camera3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
current = true
near = 0.05
far = 500.0
[node name="HUD" type="CanvasLayer" parent="."]
script = ExtResource("3_hud")
[node name="HPBar" type="Control" parent="HUD"]
offset_left = 16.0
offset_top = 16.0
offset_right = 240.0
offset_bottom = 56.0
[node name="BG" type="ColorRect" parent="HUD/HPBar"]
offset_right = 200.0
offset_bottom = 20.0
color = Color(0.1, 0.1, 0.12, 0.75)
[node name="Fill" type="ColorRect" parent="HUD/HPBar"]
offset_right = 200.0
offset_bottom = 20.0
color = Color(0.32, 0.82, 0.36, 1)
[node name="Label" type="Label" parent="HUD/HPBar"]
offset_top = 22.0
offset_right = 240.0
offset_bottom = 40.0
theme_override_colors/font_color = Color(0.95, 0.95, 0.95, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
text = "HP 20 / 20"
[node name="InfoLabel" type="Label" parent="HUD"]
offset_left = 16.0
offset_top = 64.0
offset_right = 500.0
offset_bottom = 88.0
theme_override_colors/font_color = Color(0.95, 0.95, 0.95, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
text = "Depth 1 / 10"
[node name="EndOverlay" type="Control" parent="HUD"]
anchor_right = 1.0
anchor_bottom = 1.0
mouse_filter = 2
[node name="Dim" type="ColorRect" parent="HUD/EndOverlay"]
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0, 0, 0, 0.55)
[node name="Title" type="Label" parent="HUD/EndOverlay"]
anchor_left = 0.5
anchor_top = 0.4
anchor_right = 0.5
anchor_bottom = 0.4
offset_left = -200.0
offset_top = -40.0
offset_right = 200.0
offset_bottom = 20.0
theme_override_colors/font_color = Color(0.92, 0.28, 0.28, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 4
theme_override_font_sizes/font_size = 56
text = "YOU DIED"
horizontal_alignment = 1
[node name="Hint" type="Label" parent="HUD/EndOverlay"]
anchor_left = 0.5
anchor_top = 0.55
anchor_right = 0.5
anchor_bottom = 0.55
offset_left = -160.0
offset_top = 0.0
offset_right = 160.0
offset_bottom = 30.0
theme_override_colors/font_color = Color(0.9, 0.9, 0.9, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
text = "Press R to restart"
horizontal_alignment = 1

View file

@ -1,32 +0,0 @@
[gd_scene format=3 uid="uid://jysbic1s50to"]
[ext_resource type="Script" uid="uid://brsi02a7ei24j" path="res://scripts/demo_3d.gd" id="1_demo3d"]
[ext_resource type="Script" uid="uid://d0srrm35g1m0t" path="res://scripts/fly_camera.gd" id="2_flycam"]
[sub_resource type="Environment" id="Env1"]
background_mode = 1
background_color = Color(0.04, 0.04, 0.05, 1)
ambient_light_source = 2
ambient_light_color = Color(0.6, 0.6, 0.7, 1)
ambient_light_energy = 0.4
[node name="Demo3D" type="Node3D" unique_id=36819859]
script = ExtResource("1_demo3d")
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1412341693]
environment = SubResource("Env1")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=236128294]
transform = Transform3D(0.707107, -0.5, 0.5, 0, 0.707107, 0.707107, -0.707107, -0.5, 0.5, 0, 10, 0)
light_color = Color(1, 0.95, 0.85, 1)
light_energy = 0.9
shadow_enabled = true
[node name="FlyCamera" type="Camera3D" parent="." unique_id=658486835]
transform = Transform3D(1, 0, 0, 0, 0.819152, 0.573576, 0, -0.573576, 0.819152, 39, 14, 32)
current = true
near = 0.1
far = 500.0
script = ExtResource("2_flycam")
[node name="Levels" type="Node3D" parent="." unique_id=1476525758]

View file

@ -0,0 +1,33 @@
[gd_scene format=3 uid="uid://c8blbrbrbr001"]
[ext_resource type="Script" uid="uid://b2r6hnyvt7ef7" path="res://scripts/dungeon_builder.gd" id="1_builder"]
[ext_resource type="Script" uid="uid://d0srrm35g1m0t" path="res://scripts/fly_camera.gd" id="2_flycam"]
[sub_resource type="Environment" id="Environment_1"]
background_mode = 1
background_color = Color(0.08, 0.08, 0.1, 1)
ambient_light_source = 2
ambient_light_color = Color(0.55, 0.55, 0.6, 1)
ambient_light_energy = 0.5
[node name="DemoBlobber" type="Node3D" unique_id=201275322]
[node name="Dungeon" type="Node3D" parent="." unique_id=2089249369]
script = ExtResource("1_builder")
seed_value = 43
num_levels = 8
depth = 20
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1913401313]
transform = Transform3D(0.86602527, -0.35310844, 0.35399818, 0, 0.70799595, 0.70621645, -0.5000003, -0.6116013, 0.6131424, 80, 40, 80)
shadow_enabled = true
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1337946831]
environment = SubResource("Environment_1")
[node name="FlyCamera" type="Camera3D" parent="." unique_id=987300286]
transform = Transform3D(0.9, 0, 0, 0, 0.7, 0.7, 0, -0.7, 0.7, 120, 40, 80)
fov = 65.0
near = 0.1
far = 500.0
script = ExtResource("2_flycam")

View file

@ -1,58 +0,0 @@
[gd_scene format=3 uid="uid://c0brogue3dfps"]
[ext_resource type="Script" path="res://scripts/demo_fps.gd" id="1_demofps"]
[ext_resource type="Script" path="res://scripts/player.gd" id="2_player"]
[ext_resource type="Script" path="res://scripts/fps_overlay.gd" id="3_fpsov"]
[sub_resource type="Environment" id="Env1"]
background_mode = 1
background_color = Color(0.04, 0.04, 0.05, 1)
ambient_light_source = 2
ambient_light_color = Color(0.6, 0.6, 0.7, 1)
ambient_light_energy = 0.4
fog_enabled = false
[sub_resource type="CapsuleShape3D" id="Capsule1"]
radius = 0.3
height = 1.7
[node name="DemoFPS" type="Node3D"]
script = ExtResource("1_demofps")
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Env1")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.707107, -0.5, 0.5, 0, 0.707107, 0.707107, -0.707107, -0.5, 0.5, 0, 10, 0)
light_color = Color(1, 0.95, 0.85, 1)
light_energy = 0.9
shadow_enabled = true
[node name="Levels" type="Node3D" parent="."]
[node name="Player" type="CharacterBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 39, 1.1, 14)
script = ExtResource("2_player")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0)
shape = SubResource("Capsule1")
[node name="Camera3D" type="Camera3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
current = true
near = 0.05
far = 500.0
[node name="FPSOverlay" type="CanvasLayer" parent="."]
script = ExtResource("3_fpsov")
[node name="Label" type="Label" parent="FPSOverlay"]
offset_left = 12.0
offset_top = 12.0
offset_right = 800.0
offset_bottom = 80.0
theme_override_colors/font_color = Color(0.95, 0.95, 0.95, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
text = "FPS: --"

View file

@ -1,8 +1,152 @@
[gd_scene format=3 uid="uid://c0broguelarge"]
[gd_scene format=3 uid="uid://c2gdxshrhuv3t"]
[ext_resource type="Script" path="res://scripts/demo_fps.gd" id="1_demofps"]
[ext_resource type="Script" path="res://scripts/player.gd" id="2_player"]
[ext_resource type="Script" path="res://scripts/fps_overlay.gd" id="3_fpsov"]
[ext_resource type="Script" uid="uid://bj1fb7syiqys7" path="res://scripts/player.gd" id="2_player"]
[sub_resource type="GDScript" id="GDScript_aw1u5"]
script/source = "extends Node3D
# First-person demo. Generates a grid of chunks (each a 79×29 dungeon) times
# level_count stacked layers. Default is 1×1 chunks × 3 levels. Bump
# chunks_x / chunks_y to stress-test the renderer with larger worlds.
const T_NOTHING := 0
const T_FLOOR := 1
const T_WALL := 2
const T_DOOR := 3
const T_CORRIDOR := 4
const T_LIQUID := 5
const T_BRIDGE := 6
const T_STAIRS_UP := 7
const T_STAIRS_DOWN := 8
const L_WATER := 1
const L_LAVA := 2
const L_CHASM := 3
const L_BRIMSTONE := 4
const TILE_FLOOR := 0
const TILE_WALL := 1
const TILE_DOOR := 2
const TILE_CORRIDOR := 3
const TILE_WATER := 4
const TILE_LAVA := 5
const TILE_BRIMSTONE := 6
const TILE_BRIDGE := 7
const TILE_STAIRS_UP := 8
const TILE_STAIRS_DOWN := 9
const CHUNK_W := 79
const CHUNK_H := 29
@export var base_seed: int = 2028
@export var depth_start: int = 20
@export var level_count: int = 3
@export var level_spacing: float = 6.0
@export var chunks_x: int = 1
@export var chunks_y: int = 1
@export var player_spawn_height: float = 1.1
@onready var levels_root: Node3D = $Levels
@onready var player: CharacterBody3D = $Player
@onready var fps_overlay: Node = $FPSOverlay if has_node(\"FPSOverlay\") else null
var _mesh_library: MeshLibrary
func _ready() -> void:
var t0 := Time.get_ticks_msec()
_mesh_library = MeshLibraryBuilder.build(true)
var total_cells := 0
var total_chunks := 0
var spawn_world := Vector3.ZERO
var spawn_found := false
for level_index in range(level_count):
for cy in range(chunks_y):
for cx in range(chunks_x):
var seed := base_seed \\
+ level_index * 1000 \\
+ cy * chunks_x + cx
var depth := depth_start + level_index
var gen := BrogueGen.new()
var grid: Dictionary = gen.generate(seed, depth)
gen.free()
var grid_map := GridMap.new()
grid_map.name = \"L%d_C%d_%d\" % [level_index, cx, cy]
grid_map.mesh_library = _mesh_library
grid_map.cell_size = Vector3(1, 1, 1)
grid_map.position = Vector3(
cx * CHUNK_W,
-level_index * level_spacing,
cy * CHUNK_H
)
levels_root.add_child(grid_map)
var cells := _populate_level(grid_map, grid)
total_cells += cells
total_chunks += 1
# First up-stair on level 0, chunk (0,0) is the spawn point.
if not spawn_found and level_index == 0 and cx == 0 and cy == 0:
var up: Vector2i = grid[\"stairs_up\"] as Vector2i
if up.x >= 0:
spawn_world = Vector3(up.x, player_spawn_height, up.y)
spawn_found = true
if not spawn_found:
spawn_world = Vector3(CHUNK_W * 0.5, player_spawn_height, CHUNK_H * 0.5)
player.position = spawn_world
var build_ms := Time.get_ticks_msec() - t0
var info := \"%d chunks (%dx%d x %d lvls) %d placed cells build=%d ms\" % [
total_chunks, chunks_x, chunks_y, level_count, total_cells, build_ms,
]
print(info)
print(\"Player spawned at %s\" % spawn_world)
if fps_overlay and fps_overlay.has_method(\"set_subtitle\"):
fps_overlay.set_subtitle(info)
# Place every non-chasm cell into the GridMap. Returns the number of cells
# actually placed (excludes T_NOTHING and chasms).
func _populate_level(grid_map: GridMap, grid: Dictionary) -> int:
var w: int = grid[\"width\"]
var h: int = grid[\"height\"]
var terrain: PackedByteArray = grid[\"terrain\"]
var liquid: PackedByteArray = grid[\"liquid\"]
var placed := 0
for y in range(h):
for x in range(w):
var idx := y * w + x
var t: int = terrain[idx]
var liq: int = liquid[idx]
if t == T_LIQUID and liq == L_CHASM:
continue
var tile_id := _tile_for(t, liq)
if tile_id == -1:
continue
grid_map.set_cell_item(Vector3i(x, 0, y), tile_id)
placed += 1
return placed
func _tile_for(terrain: int, liquid: int) -> int:
match terrain:
T_FLOOR: return TILE_FLOOR
T_CORRIDOR: return TILE_CORRIDOR
T_DOOR: return TILE_DOOR
T_WALL: return TILE_WALL
T_BRIDGE: return TILE_BRIDGE
T_STAIRS_UP: return TILE_STAIRS_UP
T_STAIRS_DOWN: return TILE_STAIRS_DOWN
T_LIQUID:
match liquid:
L_WATER: return TILE_WATER
L_LAVA: return TILE_LAVA
L_BRIMSTONE: return TILE_BRIMSTONE
_: return TILE_WATER
_: return -1
"
[sub_resource type="Environment" id="Env1"]
background_mode = 1
@ -10,51 +154,65 @@ background_color = Color(0.02, 0.02, 0.03, 1)
ambient_light_source = 2
ambient_light_color = Color(0.6, 0.6, 0.7, 1)
ambient_light_energy = 0.35
fog_enabled = false
[sub_resource type="CapsuleShape3D" id="Capsule1"]
radius = 0.3
height = 1.7
[node name="DemoLarge" type="Node3D"]
script = ExtResource("1_demofps")
base_seed = 2028
depth_start = 20
level_count = 3
level_spacing = 6.0
[sub_resource type="GDScript" id="GDScript_5guhk"]
script/source = "extends CanvasLayer
# Cheap FPS + stats overlay. Attach as a child of any scene.
# The parent scene can call set_subtitle(text) to add a line below the FPS.
@onready var label: Label = $Label
var _subtitle := \"\"
func set_subtitle(text: String) -> void:
_subtitle = text
func _process(_delta: float) -> void:
var fps := Engine.get_frames_per_second()
var frame_ms := 0.0 if fps <= 0 else 1000.0 / fps
var txt := \"FPS: %d %.2f ms\" % [fps, frame_ms]
if _subtitle != \"\":
txt += \"\\n\" + _subtitle
label.text = txt
"
[node name="DemoLarge" type="Node3D" unique_id=1477096723]
script = SubResource("GDScript_aw1u5")
chunks_x = 5
chunks_y = 5
player_spawn_height = 1.1
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1826007557]
environment = SubResource("Env1")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=332057448]
transform = Transform3D(0.707107, -0.5, 0.5, 0, 0.707107, 0.707107, -0.707107, -0.5, 0.5, 0, 10, 0)
light_color = Color(1, 0.95, 0.85, 1)
light_energy = 0.9
shadow_enabled = true
[node name="Levels" type="Node3D" parent="."]
[node name="Levels" type="Node3D" parent="." unique_id=933491194]
[node name="Player" type="CharacterBody3D" parent="."]
[node name="Player" type="CharacterBody3D" parent="." unique_id=1414808402]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 39, 1.1, 14)
script = ExtResource("2_player")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player" unique_id=587919818]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0)
shape = SubResource("Capsule1")
[node name="Camera3D" type="Camera3D" parent="Player"]
[node name="Camera3D" type="Camera3D" parent="Player" unique_id=680522156]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
current = true
near = 0.05
far = 2000.0
[node name="FPSOverlay" type="CanvasLayer" parent="."]
script = ExtResource("3_fpsov")
[node name="FPSOverlay" type="CanvasLayer" parent="." unique_id=315989227]
script = SubResource("GDScript_5guhk")
[node name="Label" type="Label" parent="FPSOverlay"]
[node name="Label" type="Label" parent="FPSOverlay" unique_id=1568814951]
offset_left = 12.0
offset_top = 12.0
offset_right = 1000.0

View file

@ -0,0 +1,17 @@
[gd_scene format=3 uid="uid://bq41mttbygl2a"]
[ext_resource type="Script" uid="uid://cwu5cf7a0awcd" path="res://addons/func_godot/src/map/func_godot_map.gd" id="1_map_script"]
[ext_resource type="Resource" path="res://data/config/brogue_map_settings.tres" id="2_map_settings"]
[node name="DungeonFromMap" type="Node3D" unique_id=693515113]
[node name="FuncGodotMap" type="Node3D" parent="." unique_id=858233376]
script = ExtResource("1_map_script")
local_map_file = "res://maps/generated.map"
map_settings = ExtResource("2_map_settings")
[node name="Camera3D" type="Camera3D" parent="." unique_id=125559999]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 50, 100)
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=391021715]
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 10, 0)

File diff suppressed because one or more lines are too long

View file

@ -1,136 +0,0 @@
@tool
extends Node3D
class_name DungeonGenerator
const T_NOTHING := 0
const T_FLOOR := 1
const T_WALL := 2
const T_DOOR := 3
const T_CORRIDOR := 4
const T_LIQUID := 5
const T_BRIDGE := 6
const T_STAIRS_UP := 7
const T_STAIRS_DOWN := 8
const L_NONE := 0
const L_WATER := 1
const L_LAVA := 2
const L_CHASM := 3
const L_BRIMSTONE := 4
# Match the tile IDs exposed by MeshLibraryBuilder.
const TILE_FLOOR := 0
const TILE_WALL := 1
const TILE_DOOR := 2
const TILE_CORRIDOR := 3
const TILE_WATER := 4
const TILE_LAVA := 5
const TILE_BRIMSTONE := 6
const TILE_BRIDGE := 7
const TILE_STAIRS_UP := 8
const TILE_STAIRS_DOWN := 9
@export var base_seed: int = 2321
@export var depth_start: int = 20
@export var level_count: int = 10
@export var level_spacing: float = 6.0
@export var generate: bool = false:
set(value):
if value and Engine.is_editor_hint():
generate_dungeon()
generate = false
@export var clear_levels: bool = false:
set(value):
if value and Engine.is_editor_hint():
_clear_levels()
clear_levels = false
@onready var levels_root: Node3D = %Levels
var _mesh_library: MeshLibrary
# Populate one level's GridMap from the grid dict. Returns the count of
# chasm cells that were deliberately skipped (for reporting).
func _populate_level(grid_map: GridMap, grid: Dictionary) -> int:
var w: int = grid["width"]
var h: int = grid["height"]
var terrain: PackedByteArray = grid["terrain"]
var liquid: PackedByteArray = grid["liquid"]
var chasm_cells := 0
for y in range(h):
for x in range(w):
var idx := y * w + x
var t: int = terrain[idx]
var liq: int = liquid[idx]
# Chasm liquid renders as an actual see-through pit.
if t == T_LIQUID and liq == L_CHASM:
chasm_cells += 1
continue
var tile_id := _tile_for(t, liq)
if tile_id == -1:
continue # T_NOTHING or other empty — leave unrendered
# GridMap coords: (X, Y, Z). We want dungeon x → X, dungeon y → Z.
grid_map.set_cell_item(Vector3i(x, 0, y), tile_id)
return chasm_cells
func _tile_for(terrain: int, liquid: int) -> int:
match terrain:
T_FLOOR: return TILE_FLOOR
T_CORRIDOR: return TILE_CORRIDOR
T_DOOR: return TILE_DOOR
T_WALL: return TILE_WALL
T_BRIDGE: return TILE_BRIDGE
T_STAIRS_UP: return TILE_STAIRS_UP
T_STAIRS_DOWN: return TILE_STAIRS_DOWN
T_LIQUID:
match liquid:
L_WATER: return TILE_WATER
L_LAVA: return TILE_LAVA
L_BRIMSTONE: return TILE_BRIMSTONE
L_CHASM: return -1 # empty cell — see through to level below
_: return TILE_WATER
_: return -1 # T_NOTHING or unknown
func _clear_levels() -> void:
if levels_root == null:
levels_root = %Levels
for child in levels_root.get_children():
levels_root.remove_child(child)
child.queue_free()
func generate_dungeon():
if levels_root == null:
levels_root = %Levels
if levels_root.get_child_count() > 0:
push_warning("Levels already generated — toggle clear_levels first to regenerate.")
return
_mesh_library = MeshLibraryBuilder.build()
var edited_root: Node = get_tree().edited_scene_root if Engine.is_editor_hint() else null
var total_chasm_cells := 0
for level_index in range(level_count):
var level_seed := base_seed + level_index
var depth := depth_start + level_index
var gen := BrogueGen.new()
var grid: Dictionary = gen.generate(level_seed, depth)
gen.free()
var grid_map := GridMap.new()
grid_map.name = "Level%d" % level_index
grid_map.mesh_library = _mesh_library
grid_map.cell_size = Vector3(1, 1, 1)
grid_map.position.y = -level_index * level_spacing
levels_root.add_child(grid_map)
if edited_root:
grid_map.owner = edited_root
var chasm_cells := _populate_level(grid_map, grid)
total_chasm_cells += chasm_cells
print("Level %d: seed=%d depth=%d rooms=%d machines=%d chasms=%d"
% [level_index, level_seed, depth,
(grid["rooms"] as Array).size(),
(grid["machines"] as Array).size(),
chasm_cells])

View file

@ -1 +0,0 @@
uid://ox0s7xjdj3lw