extends Node3D ## The reusable game shell: player, camera, HUD and the PS1 filter — everything that ## outlives a single level. On load it instances the active level's module (geometry, ## spawners, lighting, colliders) into LevelRoot. Switching levels frees the whole ## previous module first, so no old mesh or collider can survive into the next fight. const PhysicsLayers = preload("res://physics_layers.gd") @onready var _level_root: Node3D = $LevelRoot @onready var _camera: IsoCamera = $Player/Camera3D func _ready() -> void: Run.ensure_run() load_active_level() ## Tear down the current level module and build the run's active one. Freeing the whole ## subtree (not hiding it) is the point: a level's colliders and spawners cease to exist ## before the next level's are created. func load_active_level() -> void: for child: Node in _level_root.get_children(): _level_root.remove_child(child) child.queue_free() var level := Run.active_level() if level == null or level.level_scene == null: push_warning("game_shell: active level has no level_scene") return _camera.set_framing_scale(level.camera_zoom) var module := level.level_scene.instantiate() _tag_environment_for_corpses(module) _level_root.add_child(module) ## Ground and walls stay on WORLD so the bull and live matadors collide with them; here ## we add CORPSE too. A killed matador's ragdoll is masked to CORPSE alone, so it rests ## on the arena while the bull — which only collides with WORLD — passes through the body ## instead of spazzing out on it. Done at load so it also reaches colliders baked into ## the instanced GLB arenas, not just the level module's own floor box. func _tag_environment_for_corpses(module: Node) -> void: for body: StaticBody3D in module.find_children("*", "StaticBody3D", true, false): body.collision_layer |= PhysicsLayers.CORPSE