Skip to content

Commit df42d84

Browse files
committed
Move interact verb/action to HUD
Instead of showing text over the object in the world, show it on the HUD at the bottom of the screen, in place of the "Interact" label. Show a bouncing red arrow above the currently-targetted interactable entity. Show nothing above interactable entities that are not currently targetted, but define an "idle" animation for this case so that we could in theory change this later just by adjusting the animation. Define the arrow position with a Marker2D attached to the InteractArea. Migrate all existing interact_label_position values to a new Marker2D node on each - these will not all be correct, but some of them might be. Manually adjust some, including the tricky case of the Stella tortoise. (Doing this by moving a Marker2D around in the editor is so much easier than when I did it by editing Vector2 values by hand!) Make all InteractArea nodes visible. (In a number of cases we previously hid them to reduce the visual clutter, but this would hide the arrow.) At runtime, if no Marker2D is provided, place one 64px above the InteractArea node - better than nothing. Add a sound effect that plays when the interaction target changes. Adjust the logic in character_sight.gd to only emit interact_area_changed if it actually does change, to avoid annoying extra clicks when the player moves in such a way that a second object is in range but the best object hasn't changed. Resolves #2114
1 parent 754e68e commit df42d84

29 files changed

Lines changed: 383 additions & 179 deletions

File tree

scenes/game_elements/characters/npcs/talker/talker.tscn

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,21 @@ flip_h = true
2828
rotation = -1.5708
2929
shape = SubResource("CapsuleShape2D_3vyb7")
3030

31-
[node name="InteractArea" type="Area2D" parent="." unique_id=1202156030]
31+
[node name="InteractArea" type="Area2D" parent="." unique_id=1202156030 node_paths=PackedStringArray("marker")]
3232
unique_name_in_owner = true
3333
collision_layer = 32
3434
collision_mask = 0
3535
script = ExtResource("3_c0xhn")
36-
interact_label_position = Vector2(0, -100)
36+
marker = NodePath("Marker")
3737

3838
[node name="CollisionShape2D" type="CollisionShape2D" parent="InteractArea" unique_id=792948505]
3939
position = Vector2(0, -29)
4040
shape = SubResource("RectangleShape2D_3eksq")
4141
debug_color = Color(0.600391, 0.54335, 0, 0.42)
4242

43+
[node name="Marker" type="Marker2D" parent="InteractArea" unique_id=609991529]
44+
position = Vector2(0, -100)
45+
4346
[node name="TalkBehavior" type="Node" parent="." unique_id=131616442 node_paths=PackedStringArray("interact_area")]
4447
unique_name_in_owner = true
4548
script = ExtResource("4_t2xbo")

scenes/game_elements/characters/player/components/player_interaction.gd

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
class_name PlayerInteraction
44
extends Node2D
55

6-
## Emitted when [method can_interact] will return a different value.
7-
signal can_interact_changed
6+
## Emitted when the entity the player is able to interact with changes (possibly
7+
## to nothing).
8+
signal interact_action_changed
89

910
## The character that gains interaction.
1011
## [br][br]
@@ -14,8 +15,6 @@ signal can_interact_changed
1415
set = _set_character
1516

1617
@onready var character_sight: CharacterSight = %CharacterSight
17-
@onready var interact_marker: Marker2D = %InteractMarker
18-
@onready var interact_label: FixedSizeLabel = %InteractLabel
1918

2019
@onready var player: Player = self.owner as Player
2120

@@ -25,10 +24,15 @@ func _set_character(new_character: CharacterBody2D) -> void:
2524
update_configuration_warnings()
2625

2726

28-
## Returns [code]true[/code] if the character is currently able to interact with
29-
## something in the environment.
30-
func can_interact() -> bool:
31-
return character_sight.interact_area != null
27+
## Returns the human-readable text of [member character]'s current interact
28+
## action (e.g. [code]"Talk"[/code]), or [code]""[/code] (empty string) if
29+
## [member character] cannot currently interact with anything. Use [signal
30+
## interact_action_changed] to monitor for changes.
31+
func get_interact_action() -> String:
32+
if character_sight.interact_area != null:
33+
var action := character_sight.interact_area.action
34+
return action if action else tr("Interact")
35+
return ""
3236

3337

3438
func _is_interacting() -> bool:
@@ -47,14 +51,6 @@ func _ready() -> void:
4751
_on_character_sight_interact_area_changed()
4852

4953

50-
func _process(_delta: float) -> void:
51-
if not character_sight.interact_area:
52-
return
53-
interact_marker.global_position = (
54-
character_sight.interact_area.get_global_interact_label_position()
55-
)
56-
57-
5854
func _unhandled_input(_event: InputEvent) -> void:
5955
if _is_interacting():
6056
return
@@ -68,7 +64,6 @@ func _unhandled_input(_event: InputEvent) -> void:
6864

6965
get_viewport().set_input_as_handled()
7066
character_sight.monitoring = false
71-
interact_label.visible = false
7267
interact_area.interaction_ended.connect(_on_interaction_ended, CONNECT_ONE_SHOT)
7368
interact_area.start_interaction(player, character_sight.is_looking_from_right)
7469

@@ -81,13 +76,4 @@ func _on_interaction_ended() -> void:
8176

8277

8378
func _on_character_sight_interact_area_changed() -> void:
84-
if _is_interacting():
85-
return
86-
87-
if not character_sight.interact_area:
88-
interact_label.visible = false
89-
else:
90-
interact_label.visible = true
91-
interact_label.label_text = character_sight.interact_area.action
92-
93-
can_interact_changed.emit()
79+
interact_action_changed.emit()

scenes/game_elements/characters/player/player.tscn

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
[ext_resource type="Script" uid="uid://bpu6jo4kvehlg" path="res://scenes/game_elements/characters/player/components/player_interaction.gd" id="3_dqkch"]
99
[ext_resource type="Script" uid="uid://qro4uo83ba8f" path="res://scenes/game_elements/characters/player/components/player_sprite.gd" id="3_qlg0r"]
1010
[ext_resource type="Script" uid="uid://necvar42rnih" path="res://scenes/game_elements/props/character_sight/character_sight.gd" id="6_3in67"]
11-
[ext_resource type="PackedScene" uid="uid://yfpfno276rol" path="res://scenes/game_elements/props/fixed_size_label/fixed_size_label.tscn" id="6_h17s1"]
1211
[ext_resource type="Script" uid="uid://e78f8iq448e1" path="res://scenes/game_elements/characters/player/components/animation_player.gd" id="7_0owmy"]
1312
[ext_resource type="AudioStream" uid="uid://cx6jv2cflrmqu" path="res://assets/third_party/sounds/characters/player/Foot.ogg" id="11_blfj0"]
1413
[ext_resource type="Texture2D" uid="uid://dxaq5piwxqnht" path="res://scenes/game_elements/characters/player/components/dust.png" id="12_3in67"]
@@ -420,22 +419,6 @@ position = Vector2(47, -30)
420419
shape = SubResource("RectangleShape2D_blfj0")
421420
debug_color = Color(0.600391, 0.54335, 0, 0.42)
422421

423-
[node name="InteractMarker" type="Marker2D" parent="PlayerInteraction" unique_id=439387414]
424-
unique_name_in_owner = true
425-
position = Vector2(-1, -116)
426-
427-
[node name="InteractLabel" parent="PlayerInteraction/InteractMarker" unique_id=554498577 instance=ExtResource("6_h17s1")]
428-
unique_name_in_owner = true
429-
anchors_preset = 15
430-
anchor_right = 1.0
431-
anchor_bottom = 1.0
432-
offset_left = 1.0
433-
offset_top = 11.0
434-
offset_right = 1.0
435-
offset_bottom = 11.0
436-
grow_horizontal = 2
437-
grow_vertical = 2
438-
439422
[node name="PlayerRepel" parent="." unique_id=1324972992 instance=ExtResource("12_tnibl")]
440423
unique_name_in_owner = true
441424
position = Vector2(0, -24)

scenes/game_elements/props/character_sight/character_sight.gd

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,15 @@ func _process(_delta: float) -> void:
7676

7777

7878
func _on_area_entered(_area: Area2D) -> void:
79-
interact_area = _get_best_interact_area()
80-
interact_area_changed.emit()
79+
_update_interact_area()
8180

8281

8382
func _on_area_exited(_area: Area2D) -> void:
84-
interact_area = _get_best_interact_area()
85-
interact_area_changed.emit()
83+
_update_interact_area()
84+
85+
86+
func _update_interact_area() -> void:
87+
var best := _get_best_interact_area()
88+
if interact_area != best:
89+
interact_area = best
90+
interact_area_changed.emit()

scenes/game_elements/props/checkpoint/checkpoint.tscn

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ sprite_frames = ExtResource("4_3xcwf")
2424
animation = &"idle"
2525
autoplay = "idle"
2626

27-
[node name="InteractArea" type="Area2D" parent="." unique_id=2037902510]
27+
[node name="InteractArea" type="Area2D" parent="." unique_id=2037902510 node_paths=PackedStringArray("marker")]
2828
unique_name_in_owner = true
2929
collision_layer = 0
3030
collision_mask = 0
3131
script = ExtResource("4_bjlxs")
32-
interact_label_position = Vector2(0, -128)
32+
marker = NodePath("Marker")
3333
disabled = true
3434
action = "Admire"
3535
metadata/_custom_type_script = "uid://du8wfijr35r35"
@@ -39,6 +39,9 @@ position = Vector2(1, -4)
3939
shape = SubResource("CircleShape2D_3xcwf")
4040
debug_color = Color(0.600391, 0.54335, 0, 0.42)
4141

42+
[node name="Marker" type="Marker2D" parent="InteractArea" unique_id=1178774098]
43+
position = Vector2(0, -128)
44+
4245
[node name="TalkBehavior" type="Node" parent="." unique_id=487856562 node_paths=PackedStringArray("interact_area")]
4346
unique_name_in_owner = true
4447
script = ExtResource("5_3xcwf")

scenes/game_elements/props/collectible_item/collectible_item.tscn

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ tracks/6/keys = {
9494
"update": 0,
9595
"values": [0.0]
9696
}
97+
tracks/7/type = "value"
98+
tracks/7/imported = false
99+
tracks/7/enabled = true
100+
tracks/7/path = NodePath("InteractArea:disabled")
101+
tracks/7/interp = 1
102+
tracks/7/loop_wrap = true
103+
tracks/7/keys = {
104+
"times": PackedFloat32Array(0),
105+
"transitions": PackedFloat32Array(1),
106+
"update": 1,
107+
"values": [false]
108+
}
97109

98110
[sub_resource type="Animation" id="Animation_7ff3m"]
99111
resource_name = "collected"
@@ -197,6 +209,18 @@ tracks/7/keys = {
197209
"update": 0,
198210
"values": [0.0, 0.0, -12.0]
199211
}
212+
tracks/8/type = "value"
213+
tracks/8/imported = false
214+
tracks/8/enabled = true
215+
tracks/8/path = NodePath("InteractArea:disabled")
216+
tracks/8/interp = 1
217+
tracks/8/loop_wrap = true
218+
tracks/8/keys = {
219+
"times": PackedFloat32Array(0),
220+
"transitions": PackedFloat32Array(1),
221+
"update": 1,
222+
"values": [true]
223+
}
200224

201225
[sub_resource type="Animation" id="Animation_qy0vu"]
202226
resource_name = "reveal"
@@ -277,18 +301,21 @@ height = 28.0
277301
[node name="CollectibleItem" type="Node2D" unique_id=327991066]
278302
script = ExtResource("1_7ff3m")
279303

280-
[node name="InteractArea" type="Area2D" parent="." unique_id=1327503985]
304+
[node name="InteractArea" type="Area2D" parent="." unique_id=1327503985 node_paths=PackedStringArray("marker")]
281305
collision_layer = 32
282306
collision_mask = 0
283307
script = ExtResource("2_1kegn")
284-
interact_label_position = Vector2(0, -71)
308+
marker = NodePath("Marker")
285309
action = "Collect"
286310
metadata/_custom_type_script = "uid://du8wfijr35r35"
287311

288312
[node name="CollisionShape2D" type="CollisionShape2D" parent="InteractArea" unique_id=1121983883]
289313
shape = SubResource("CircleShape2D_ghwae")
290314
debug_color = Color(0.600391, 0.54335, 0, 0.42)
291315

316+
[node name="Marker" type="Marker2D" parent="InteractArea" unique_id=1961850851]
317+
position = Vector2(0, -48)
318+
292319
[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=1869284545]
293320
libraries/ = SubResource("AnimationLibrary_8dm1m")
294321

scenes/game_elements/props/eternal_loom/eternal_loom.tscn

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -542,14 +542,13 @@ position = Vector2(-1.5, 32)
542542
shape = SubResource("RectangleShape2D_uojly")
543543
debug_color = Color(0, 0.588235, 0.698039, 0.419608)
544544

545-
[node name="InteractArea" type="Area2D" parent="." unique_id=1735004807]
545+
[node name="InteractArea" type="Area2D" parent="." unique_id=1735004807 node_paths=PackedStringArray("marker")]
546546
unique_name_in_owner = true
547-
visible = false
548547
position = Vector2(0, 55)
549548
collision_layer = 32
550549
collision_mask = 0
551550
script = ExtResource("3_mjlja")
552-
interact_label_position = Vector2(0, 45)
551+
marker = NodePath("Marker")
553552
action = "Use Eternal Loom"
554553
metadata/_custom_type_script = "uid://du8wfijr35r35"
555554

@@ -558,6 +557,9 @@ position = Vector2(0, 120)
558557
shape = SubResource("RectangleShape2D_uhynt")
559558
debug_color = Color(0.600391, 0.54335, 0, 0.42)
560559

560+
[node name="Marker" type="Marker2D" parent="InteractArea" unique_id=556753843]
561+
position = Vector2(0, -152)
562+
561563
[node name="TalkBehavior" type="Node" parent="." unique_id=562216088 node_paths=PackedStringArray("interact_area")]
562564
unique_name_in_owner = true
563565
script = ExtResource("4_eq7jh")

scenes/game_elements/props/interact_area/interact_area.gd

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,26 @@ signal interaction_ended
2020
## Emitted when characters start or stop seeing this area for interaction.
2121
signal observers_changed
2222

23-
const EXAMPLE_INTERACTION_FONT = preload("uid://c3bb7lmvdqc5e")
24-
const EXAMPLE_INTERACTION_FONT_SIZE = 34
23+
const INDICATOR_SCENE := preload("uid://d252j2mhya0kq")
2524

26-
## Vector2 that approximates the position in which the interact label would
27-
## appear when a player is close.
28-
@export_custom(PROPERTY_HINT_RANGE, "-200,200,1,suffix:px,or_greater,or_less")
29-
var interact_label_position: Vector2:
25+
## Position at which to show an arrow when this area is being observed. This
26+
## should generally be centred, slightly above the visible extents of the parent
27+
## object.
28+
@export var marker: Marker2D:
3029
set(new_value):
31-
interact_label_position = new_value
32-
queue_redraw()
30+
if marker and _indicator:
31+
marker.remove_child(_indicator)
32+
marker = new_value
33+
if marker and _indicator:
34+
marker.add_child(_indicator)
35+
3336
@export var disabled: bool = false:
3437
set(new_value):
3538
disabled = new_value
3639
set_collision_layer_value(Enums.CollisionLayers.INTERACTABLE, not disabled)
40+
if _indicator:
41+
_indicator.visible = not disabled
42+
3743
@export var action: String = "Talk"
3844

3945
## Whether this area is being observed by one or more characters.
@@ -43,6 +49,8 @@ var is_being_observed: bool:
4349

4450
var _observers: Array[CharacterSight] = []
4551

52+
var _indicator: Node2D
53+
4654

4755
func start_interaction(player: Player, from_right: bool) -> void:
4856
interaction_started.emit(player, from_right)
@@ -52,10 +60,6 @@ func end_interaction() -> void:
5260
interaction_ended.emit()
5361

5462

55-
func get_global_interact_label_position() -> Vector2:
56-
return to_global(interact_label_position)
57-
58-
5963
## A [CharacterSight] calls this when it starts seeing this area.
6064
func add_observer(character_sight: CharacterSight) -> void:
6165
_observers.append(character_sight)
@@ -78,32 +82,19 @@ func _ready() -> void:
7882
# Initialise interactable bit in collision_layer
7983
disabled = disabled
8084

85+
if not marker and not Engine.is_editor_hint():
86+
marker = Marker2D.new()
87+
marker.name = "Marker"
88+
# Vaguely sensible default position
89+
marker.position = Vector2(0, -64)
90+
add_child(marker)
91+
92+
_indicator = INDICATOR_SCENE.instantiate()
93+
if marker:
94+
marker.add_child(_indicator)
95+
96+
observers_changed.connect(_on_observers_changed)
97+
8198

82-
func _draw() -> void:
83-
if not Engine.is_editor_hint():
84-
return
85-
86-
var string_size := EXAMPLE_INTERACTION_FONT.get_string_size(
87-
action, HORIZONTAL_ALIGNMENT_LEFT, -1, EXAMPLE_INTERACTION_FONT_SIZE
88-
)
89-
var draw_position := (
90-
interact_label_position - Vector2(string_size.x, -string_size.y * 2.0) / 2.0
91-
)
92-
draw_string(
93-
EXAMPLE_INTERACTION_FONT,
94-
draw_position,
95-
action,
96-
HORIZONTAL_ALIGNMENT_LEFT,
97-
-1,
98-
EXAMPLE_INTERACTION_FONT_SIZE
99-
)
100-
draw_string_outline(
101-
EXAMPLE_INTERACTION_FONT,
102-
draw_position,
103-
action,
104-
HORIZONTAL_ALIGNMENT_LEFT,
105-
-1,
106-
EXAMPLE_INTERACTION_FONT_SIZE,
107-
1,
108-
Color.BLACK
109-
)
99+
func _on_observers_changed() -> void:
100+
_indicator.bouncing = is_being_observed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# SPDX-FileCopyrightText: The Threadbare Authors
2+
# SPDX-License-Identifier: MPL-2.0
3+
extends Node2D
4+
5+
var bouncing := false:
6+
set = set_bouncing
7+
8+
@onready var animation_player: AnimationPlayer = %AnimationPlayer
9+
10+
11+
func set_bouncing(new_value: bool) -> void:
12+
bouncing = new_value
13+
if animation_player:
14+
animation_player.play(&"bounce" if new_value else &"idle")
15+
16+
17+
func _ready() -> void:
18+
set_bouncing(bouncing)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://darg2a58q4hrn

0 commit comments

Comments
 (0)