Right-size POIs and fix interaction prompt visibility + terrain snap

Three compounding issues, found via user testing:

1. Sprite was 2.56x2.56 units (raw 256px checkerboard at default
   pixel_size) — 5x wider than the player, and collision shapes sized
   to roughly match. Gave AnimatedSprite3D an explicit pixel_size
   (0.004) for a ~1 unit sprite, shrunk the solid CylinderShape3D to
   radius 0.4/height 1.0 and the Area3D trigger sphere to radius 0.6
   accordingly (still >= the solid radius, so touching = interactable
   holds).

2. InteractionPrompt had zero vertical offset, sitting dead center of
   the (huge) sprite — rendering behind/inside it, so the "[F]
   Interact" hint never appeared even though proximity detection and
   the keypress itself worked (confirmed by the user: pressing F did
   work, the hint just never showed telling them to). Gave both
   NameLabel and InteractionPrompt proper offsets above the sprite.

3. Once the collision shapes shrank, a pre-existing ~0.7 unit gap
   between each POI's hand-placed Y (computed with the same
   radius-only height approximation that's only exact along a
   facet's straight edges, not its interior — same class of error as
   the earlier Ort6 out-of-bounds bug) and the true terrain surface
   became consequential: the small collider mostly floated above the
   real ground, giving wildly direction-dependent, sometimes entirely
   missing contact/interaction depending on approach angle. Root-fixed
   rather than re-deriving 36+ positions' exact heights by hand again:
   poi.gd/poi_gate.gd now raycast straight down against the terrain
   in _ready() (after one physics_frame, so the terrain's collision
   shape has had time to register) and snap themselves onto the real
   surface plus a small clearance.

Verified headless: approaching a POI from 4 different directions now
gives consistent ~0.56 unit contact distance and player_nearby=true
in all four (previously: two directions barely touched at ~0.05 units
with interaction never triggering, the other two behaved correctly —
direction-dependent failure, now gone). Re-ran the full 6-gate chain
(still passes) and gate-to-border-prop clearance across 3 randomized
layouts (still 1.3-1.9 units clear, no regression).
This commit is contained in:
Jonas Reith 2026-08-27 10:05:11 +02:00
parent 3d1933399e
commit 6eb6758216
4 changed files with 43 additions and 8 deletions

View File

@ -16,6 +16,23 @@ func _ready() -> void:
_update_visuals() _update_visuals()
body_entered.connect(_on_body_entered) body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited) body_exited.connect(_on_body_exited)
# Terrain-Kollision braucht mind. einen Physik-Tick um registriert zu sein
await get_tree().physics_frame
_snap_to_terrain()
# Handplatzierte Y-Werte treffen die geneigten Facetten nicht exakt (die Höhe
# ist keine reine Funktion des Radius, siehe terrain.gd). Statt jede Position
# neu von Hand zu berechnen: per Raycast auf die echte Terrain-Oberfläche
# schnappen, damit Sprite/Hitbox garantiert am Boden sitzen.
func _snap_to_terrain() -> void:
var space_state := get_world_3d().direct_space_state
var from := global_position + Vector3(0, 20, 0)
var to := global_position + Vector3(0, -20, 0)
var query := PhysicsRayQueryParameters3D.create(from, to)
query.exclude = [get_rid(), $StaticBody3D.get_rid()]
var result := space_state.intersect_ray(query)
if result:
global_position.y = result.position.y + 0.5
func _update_visuals() -> void: func _update_visuals() -> void:
if visited: if visited:

View File

@ -4,7 +4,7 @@
[ext_resource type="Texture2D" uid="uid://c3ajv7vb8c4fb" path="res://assets/debug/checkerboard.png" id="2_yof6k"] [ext_resource type="Texture2D" uid="uid://c3ajv7vb8c4fb" path="res://assets/debug/checkerboard.png" id="2_yof6k"]
[sub_resource type="SphereShape3D" id="SphereShape3D_gwpk6"] [sub_resource type="SphereShape3D" id="SphereShape3D_gwpk6"]
radius = 1.3 radius = 0.6
[sub_resource type="SpriteFrames" id="SpriteFrames_0surh"] [sub_resource type="SpriteFrames" id="SpriteFrames_0surh"]
animations = [{ animations = [{
@ -18,8 +18,8 @@ animations = [{
}] }]
[sub_resource type="CylinderShape3D" id="CylinderShape3D_58c6p"] [sub_resource type="CylinderShape3D" id="CylinderShape3D_58c6p"]
radius = 1.0 radius = 0.4
height = 2.4 height = 1.0
[node name="POI" type="Area3D" unique_id=1770000468] [node name="POI" type="Area3D" unique_id=1770000468]
script = ExtResource("1_gwpk6") script = ExtResource("1_gwpk6")
@ -29,6 +29,7 @@ shape = SubResource("SphereShape3D_gwpk6")
[node name="AnimatedSprite3D" type="AnimatedSprite3D" parent="." unique_id=1205831120] [node name="AnimatedSprite3D" type="AnimatedSprite3D" parent="." unique_id=1205831120]
billboard = 2 billboard = 2
pixel_size = 0.004
sprite_frames = SubResource("SpriteFrames_0surh") sprite_frames = SubResource("SpriteFrames_0surh")
[node name="StaticBody3D" type="StaticBody3D" parent="." unique_id=1488376384] [node name="StaticBody3D" type="StaticBody3D" parent="." unique_id=1488376384]
@ -40,12 +41,13 @@ shape = SubResource("CylinderShape3D_58c6p")
transform = Transform3D(6, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0) transform = Transform3D(6, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0)
[node name="NameLabel" type="Label3D" parent="Billboard" unique_id=1419647885] [node name="NameLabel" type="Label3D" parent="Billboard" unique_id=1419647885]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.018201571, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.13, 0)
billboard = 1 billboard = 1
text = "???" text = "???"
font_size = 16 font_size = 16
[node name="InteractionPrompt" type="Label3D" parent="Billboard" unique_id=2059219662] [node name="InteractionPrompt" type="Label3D" parent="Billboard" unique_id=2059219662]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.09, 0)
visible = false visible = false
billboard = 1 billboard = 1
font_size = 12 font_size = 12

View File

@ -18,6 +18,21 @@ func _ready() -> void:
_update_visuals() _update_visuals()
body_entered.connect(_on_body_entered) body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited) body_exited.connect(_on_body_exited)
# Terrain-Kollision braucht mind. einen Physik-Tick um registriert zu sein
await get_tree().physics_frame
_snap_to_terrain()
# Siehe poi.gd: handplatzierte Y-Werte treffen die geneigten Facetten nicht
# exakt, per Raycast auf die echte Terrain-Oberfläche schnappen.
func _snap_to_terrain() -> void:
var space_state := get_world_3d().direct_space_state
var from := global_position + Vector3(0, 20, 0)
var to := global_position + Vector3(0, -20, 0)
var query := PhysicsRayQueryParameters3D.create(from, to)
query.exclude = [get_rid(), $StaticBody3D.get_rid()]
var result := space_state.intersect_ray(query)
if result:
global_position.y = result.position.y + 0.3
func _all_required_visited() -> bool: func _all_required_visited() -> bool:
var pois := get_tree().get_nodes_in_group(required_group) var pois := get_tree().get_nodes_in_group(required_group)

View File

@ -4,7 +4,7 @@
[ext_resource type="Texture2D" uid="uid://c3ajv7vb8c4fb" path="res://assets/debug/checkerboard.png" id="2_gate"] [ext_resource type="Texture2D" uid="uid://c3ajv7vb8c4fb" path="res://assets/debug/checkerboard.png" id="2_gate"]
[sub_resource type="SphereShape3D" id="SphereShape3D_gate"] [sub_resource type="SphereShape3D" id="SphereShape3D_gate"]
radius = 1.3 radius = 0.6
[sub_resource type="SpriteFrames" id="SpriteFrames_gate"] [sub_resource type="SpriteFrames" id="SpriteFrames_gate"]
animations = [{ animations = [{
@ -18,8 +18,8 @@ animations = [{
}] }]
[sub_resource type="CylinderShape3D" id="CylinderShape3D_gate"] [sub_resource type="CylinderShape3D" id="CylinderShape3D_gate"]
radius = 1.0 radius = 0.4
height = 2.4 height = 1.0
[node name="POIGate" type="Area3D"] [node name="POIGate" type="Area3D"]
script = ExtResource("1_gate") script = ExtResource("1_gate")
@ -29,6 +29,7 @@ shape = SubResource("SphereShape3D_gate")
[node name="AnimatedSprite3D" type="AnimatedSprite3D" parent="."] [node name="AnimatedSprite3D" type="AnimatedSprite3D" parent="."]
billboard = 2 billboard = 2
pixel_size = 0.004
sprite_frames = SubResource("SpriteFrames_gate") sprite_frames = SubResource("SpriteFrames_gate")
[node name="StaticBody3D" type="StaticBody3D" parent="."] [node name="StaticBody3D" type="StaticBody3D" parent="."]
@ -37,7 +38,7 @@ sprite_frames = SubResource("SpriteFrames_gate")
shape = SubResource("CylinderShape3D_gate") shape = SubResource("CylinderShape3D_gate")
[node name="Billboard" type="Node3D" parent="."] [node name="Billboard" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
[node name="NameLabel" type="Label3D" parent="Billboard"] [node name="NameLabel" type="Label3D" parent="Billboard"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0)