Flat root layout replaced with a domain-based structure:
- autoload/ the two existing singleton scripts (InteractionManager,
InteractionUI) — grouped by role, matching project.godot's
[autoload] section
- world/ the flying island: terrain.gd, and world/poi/ for the POI
marker + gate scripts/scenes
- player/ everything about the player as a character: player.gd/.tscn,
camera_rig.gd (it only exists to follow the player)
- boot/ created empty for now, will hold the opening/menu screens
main.tscn renamed to world/island.tscn — "main" stops making sense once
the actual run/main_scene becomes a boot screen (next commit). Verified
via grep that main.tscn was referenced nowhere else by path (only by UID
elsewhere, which self-heals).
Every .gd got its .uid sidecar moved alongside it. Two ext_resource
entries had no UID (poi_gate.tscn's own script ref, and island.tscn's
ref to poi_gate.tscn) and needed their path= fixed by hand; everything
else is UID-based and resolved itself after a project reimport.
Also removed two untracked-looking but actually committed editor temp
files (preview_scene.tscn*.tmp, leftovers from the unused hterrain
addon, referenced nowhere).
Verified headless: all 8 affected scenes (island, poi, poi_gate,
player, interaction_ui, and the three debug prototypes that reference
moved files) load with exit code 0. No behavior change, pure move.
69 lines
2.0 KiB
GDScript
69 lines
2.0 KiB
GDScript
extends Area3D
|
|
|
|
@export var poi_name: String = "Tor zur Mitte"
|
|
@export var locked_name: String = "Verschlossen"
|
|
@export var marker_color: Color = Color(1.0, 0.9, 0.4)
|
|
@export var required_group: String = "region1_pois"
|
|
@export var target_position: Vector3 = Vector3.ZERO
|
|
|
|
@onready var sprite: AnimatedSprite3D = $AnimatedSprite3D
|
|
@onready var name_label: Label3D = $Billboard/NameLabel
|
|
@onready var prompt: Label3D = $Billboard/InteractionPrompt
|
|
|
|
var player_nearby: bool = false
|
|
|
|
func _ready() -> void:
|
|
prompt.visible = false
|
|
prompt.text = "[F] Interact"
|
|
_update_visuals()
|
|
body_entered.connect(_on_body_entered)
|
|
body_exited.connect(_on_body_exited)
|
|
|
|
func _all_required_visited() -> bool:
|
|
var pois := get_tree().get_nodes_in_group(required_group)
|
|
if pois.is_empty():
|
|
return false
|
|
for poi in pois:
|
|
if not poi.visited:
|
|
return false
|
|
return true
|
|
|
|
func _update_visuals() -> void:
|
|
if _all_required_visited():
|
|
sprite.modulate = marker_color
|
|
name_label.text = poi_name
|
|
else:
|
|
sprite.modulate = marker_color * Color(0.35, 0.35, 0.35, 1.0)
|
|
name_label.text = locked_name
|
|
|
|
func _on_body_entered(body: Node3D) -> void:
|
|
if body.is_in_group("player"):
|
|
player_nearby = true
|
|
_update_visuals()
|
|
# Verschlossen: nicht ansprechbar, kein Prompt
|
|
prompt.visible = _all_required_visited()
|
|
|
|
func _on_body_exited(body: Node3D) -> void:
|
|
if body.is_in_group("player"):
|
|
player_nearby = false
|
|
prompt.visible = false
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
if player_nearby and _all_required_visited() and Input.is_action_just_pressed("interact"):
|
|
InteractionManager.active = true
|
|
InteractionManager.current_poi = self
|
|
InteractionUI.show_menu("Durchgehen?", [
|
|
{ "label": "Ja", "action": "gate_yes" },
|
|
{ "label": "Nein", "action": "gate_no" },
|
|
])
|
|
|
|
func handle_interaction_action(action: String) -> void:
|
|
match action:
|
|
"gate_yes":
|
|
var player := get_tree().get_first_node_in_group("player")
|
|
if player:
|
|
player.global_position = target_position
|
|
InteractionManager.close()
|
|
"gate_no":
|
|
InteractionManager.close()
|