From eabf5391cecd7705f886caf9ffc6ef6b6c957fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Tue, 24 Mar 2026 07:11:22 -0300 Subject: [PATCH 1/4] Rework and document CharacterSight and PlayerInteraction Use body_entered and body_exited signals for obtaining the best area, instead of doing it in the process loop. Rename the node InteractZone in player to CharacterSight and give it a class name, moving it to game_elements. --- .../player/components/interact_zone.gd | 39 ---------- .../player/components/player_interaction.gd | 41 ++++++---- .../characters/player/player.tscn | 6 +- .../props/character_sight/character_sight.gd | 76 +++++++++++++++++++ .../character_sight/character_sight.gd.uid} | 0 5 files changed, 105 insertions(+), 57 deletions(-) delete mode 100644 scenes/game_elements/characters/player/components/interact_zone.gd create mode 100644 scenes/game_elements/props/character_sight/character_sight.gd rename scenes/game_elements/{characters/player/components/interact_zone.gd.uid => props/character_sight/character_sight.gd.uid} (100%) diff --git a/scenes/game_elements/characters/player/components/interact_zone.gd b/scenes/game_elements/characters/player/components/interact_zone.gd deleted file mode 100644 index 91b4b531ae..0000000000 --- a/scenes/game_elements/characters/player/components/interact_zone.gd +++ /dev/null @@ -1,39 +0,0 @@ -# SPDX-FileCopyrightText: The Threadbare Authors -# SPDX-License-Identifier: MPL-2.0 -extends Area2D - -@export var character: CharacterBody2D - -var is_looking_from_right: bool = false - - -func _ready() -> void: - if not character and owner is CharacterBody2D: - character = owner - - -func get_interact_area() -> InteractArea: - var areas := get_overlapping_areas() - var best: InteractArea = null - var best_distance: float = INF - - for area in areas: - var distance := global_position.distance_to(area.global_position) - if not best or distance < best_distance: - best_distance = distance - best = area - - return best - - -func _process(_delta: float) -> void: - if not character: - return - if not monitoring: - return - if not is_zero_approx(character.velocity.x): - if character.velocity.x < 0: - scale.x = -1 - else: - scale.x = 1 - is_looking_from_right = scale.x < 0 diff --git a/scenes/game_elements/characters/player/components/player_interaction.gd b/scenes/game_elements/characters/player/components/player_interaction.gd index 0ef7e58e86..93505a3685 100644 --- a/scenes/game_elements/characters/player/components/player_interaction.gd +++ b/scenes/game_elements/characters/player/components/player_interaction.gd @@ -13,7 +13,7 @@ extends Node2D var is_interacting: bool: get = _get_is_interacting -@onready var interact_zone: Area2D = %InteractZone +@onready var character_sight: CharacterSight = %CharacterSight @onready var interact_marker: Marker2D = %InteractMarker @onready var interact_label: FixedSizeLabel = %InteractLabel @@ -26,7 +26,7 @@ func _set_character(new_character: CharacterBody2D) -> void: func _get_is_interacting() -> bool: - return not interact_zone.monitoring + return not character_sight.monitoring func _get_configuration_warnings() -> PackedStringArray: @@ -36,24 +36,24 @@ func _get_configuration_warnings() -> PackedStringArray: return warnings +func _ready() -> void: + character_sight.interact_area_changed.connect(_on_character_sight_interact_area_changed) + _on_character_sight_interact_area_changed() + + func _process(_delta: float) -> void: - if is_interacting: + if not character_sight.interact_area: return - - var interact_area: InteractArea = interact_zone.get_interact_area() - if not interact_area: - interact_label.visible = false - else: - interact_label.visible = true - interact_label.label_text = interact_area.action - interact_marker.global_position = interact_area.get_global_interact_label_position() + interact_marker.global_position = ( + character_sight.interact_area.get_global_interact_label_position() + ) func _unhandled_input(_event: InputEvent) -> void: if is_interacting: return - var interact_area: InteractArea = interact_zone.get_interact_area() + var interact_area := character_sight.interact_area if interact_area and Input.is_action_just_pressed(&"interact"): # While interacting, this class takes control over the player movement. if character.has_method("take_control"): @@ -61,14 +61,25 @@ func _unhandled_input(_event: InputEvent) -> void: character.velocity = Vector2.ZERO get_viewport().set_input_as_handled() - interact_zone.monitoring = false + character_sight.monitoring = false interact_label.visible = false interact_area.interaction_ended.connect(_on_interaction_ended, CONNECT_ONE_SHOT) - interact_area.start_interaction(player, interact_zone.is_looking_from_right) + interact_area.start_interaction(player, character_sight.is_looking_from_right) func _on_interaction_ended() -> void: - interact_zone.monitoring = true + character_sight.monitoring = true # After interacting, return control to the user. if character.has_method("return_control"): character.return_control(self) + + +func _on_character_sight_interact_area_changed() -> void: + if is_interacting: + return + + if not character_sight.interact_area: + interact_label.visible = false + else: + interact_label.visible = true + interact_label.label_text = character_sight.interact_area.action diff --git a/scenes/game_elements/characters/player/player.tscn b/scenes/game_elements/characters/player/player.tscn index 625dd33013..dd9ae46b46 100644 --- a/scenes/game_elements/characters/player/player.tscn +++ b/scenes/game_elements/characters/player/player.tscn @@ -8,7 +8,7 @@ [ext_resource type="Script" uid="uid://bpu6jo4kvehlg" path="res://scenes/game_elements/characters/player/components/player_interaction.gd" id="3_dqkch"] [ext_resource type="Script" uid="uid://ciw2w16c38ewq" path="res://scenes/game_elements/characters/player/components/player_dust_particles.gd" id="3_j0tly"] [ext_resource type="Script" uid="uid://qro4uo83ba8f" path="res://scenes/game_elements/characters/player/components/player_sprite.gd" id="3_qlg0r"] -[ext_resource type="Script" uid="uid://necvar42rnih" path="res://scenes/game_elements/characters/player/components/interact_zone.gd" id="6_3in67"] +[ext_resource type="Script" uid="uid://necvar42rnih" path="res://scenes/game_elements/props/character_sight/character_sight.gd" id="6_3in67"] [ext_resource type="PackedScene" uid="uid://yfpfno276rol" path="res://scenes/game_elements/props/fixed_size_label/fixed_size_label.tscn" id="6_h17s1"] [ext_resource type="Script" uid="uid://e78f8iq448e1" path="res://scenes/game_elements/characters/player/components/animation_player.gd" id="7_0owmy"] [ext_resource type="Script" uid="uid://kni2yl26matc" path="res://scenes/game_elements/characters/player/components/player_repel.gd" id="7_5gtgg"] @@ -583,14 +583,14 @@ unique_name_in_owner = true script = ExtResource("3_dqkch") character = NodePath("..") -[node name="InteractZone" type="Area2D" parent="PlayerInteraction" unique_id=888605377 node_paths=PackedStringArray("character")] +[node name="CharacterSight" type="Area2D" parent="PlayerInteraction" unique_id=888605377 node_paths=PackedStringArray("character")] unique_name_in_owner = true collision_layer = 0 collision_mask = 32 script = ExtResource("6_3in67") character = NodePath("../..") -[node name="CollisionShape2D" type="CollisionShape2D" parent="PlayerInteraction/InteractZone" unique_id=255765935] +[node name="CollisionShape2D" type="CollisionShape2D" parent="PlayerInteraction/CharacterSight" unique_id=255765935] position = Vector2(47, -30) shape = SubResource("RectangleShape2D_blfj0") debug_color = Color(0.600391, 0.54335, 0, 0.42) diff --git a/scenes/game_elements/props/character_sight/character_sight.gd b/scenes/game_elements/props/character_sight/character_sight.gd new file mode 100644 index 0000000000..d2bf174748 --- /dev/null +++ b/scenes/game_elements/props/character_sight/character_sight.gd @@ -0,0 +1,76 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +class_name CharacterSight +extends Area2D +## The area of sight of the character. +## +## The character sees one [InteractArea] at a time to interact. +## This area also faces toward the character current direction. +## [br][br] +## This script automatically configures the correct [member collision_layer] and +## [member collision_mask] values to enable interaction with the player. + +## Emitted when [member interact_area] changes. +signal interact_area_changed + +## The character. +@export var character: CharacterBody2D + +## The direction the character is facing. +## [br][br] +## TODO: Use east/west instead of right/left? +var is_looking_from_right: bool = false + +## The area that the character is currently observing. +var interact_area: InteractArea + + +func _ready() -> void: + if not character and owner is CharacterBody2D: + character = owner + collision_layer = 0 + collision_mask = 0 + set_collision_mask_value(Enums.CollisionLayers.INTERACTABLE, true) + area_entered.connect(_on_area_entered) + area_exited.connect(_on_area_exited) + + +func _get_best_interact_area() -> InteractArea: + if not monitoring: + return null + var areas := get_overlapping_areas() + var best: InteractArea = null + var best_distance: float = INF + + # TODO: This picks the closest area according to their global position, + # which could be misleading. An Area2D may have a collision shape + # that is far away from it's anchor. + for area in areas: + var distance := global_position.distance_to(area.global_position) + if not best or distance < best_distance: + best_distance = distance + best = area + + return best + + +func _process(_delta: float) -> void: + if not character: + return + # Flip this area according to the character current direction: + if not is_zero_approx(character.velocity.x): + if character.velocity.x < 0: + scale.x = -1 + else: + scale.x = 1 + is_looking_from_right = scale.x < 0 + + +func _on_area_entered(_area: Area2D) -> void: + interact_area = _get_best_interact_area() + interact_area_changed.emit() + + +func _on_area_exited(_area: Area2D) -> void: + interact_area = _get_best_interact_area() + interact_area_changed.emit() diff --git a/scenes/game_elements/characters/player/components/interact_zone.gd.uid b/scenes/game_elements/props/character_sight/character_sight.gd.uid similarity index 100% rename from scenes/game_elements/characters/player/components/interact_zone.gd.uid rename to scenes/game_elements/props/character_sight/character_sight.gd.uid From 29188a7a528643807e29a2d1ef50af3e5b9091cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Tue, 24 Mar 2026 08:57:40 -0300 Subject: [PATCH 2/4] champ StoryQuest: Fix champ_stealth scene Adapt to the recent changes in player. --- .../story_quests/champ/3_stealth/champ_stealth.tscn | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scenes/quests/story_quests/champ/3_stealth/champ_stealth.tscn b/scenes/quests/story_quests/champ/3_stealth/champ_stealth.tscn index c43ec6a029..d59a251165 100644 --- a/scenes/quests/story_quests/champ/3_stealth/champ_stealth.tscn +++ b/scenes/quests/story_quests/champ/3_stealth/champ_stealth.tscn @@ -32,7 +32,7 @@ [ext_resource type="Texture2D" uid="uid://c1vlj61hnl1dx" path="res://scenes/quests/story_quests/champ/tiles/assets/shore_and_lake_tiles.png" id="15_wbd51"] [ext_resource type="Texture2D" uid="uid://cexg7otw5enpu" path="res://assets/third_party/tiny-swords/Terrain/Water/Foam/Foam.png" id="16_40h7x"] [ext_resource type="Script" uid="uid://dwd8vjl5h6nxy" path="res://scenes/quests/story_quests/champ/3_stealth/champ_incomplete_character.gd" id="16_fw64s"] -[ext_resource type="Script" uid="uid://necvar42rnih" path="res://scenes/game_elements/characters/player/components/interact_zone.gd" id="16_g11k1"] +[ext_resource type="Script" uid="uid://necvar42rnih" path="res://scenes/game_elements/props/character_sight/character_sight.gd" id="16_g11k1"] [ext_resource type="PackedScene" uid="uid://ipvcfv2g0oi1" path="res://scenes/game_elements/characters/npcs/talker/talker.tscn" id="16_ol5cv"] [ext_resource type="Resource" uid="uid://c3yuq52k21sga" path="res://scenes/quests/story_quests/champ/3_stealth/stealth_components/champ_incomplete_stealth_guard_path_hint.dialogue" id="17_8qkne"] [ext_resource type="Texture2D" uid="uid://cnmmh3uq7rkla" path="res://assets/third_party/tiny-swords/Terrain/Bridge/Bridge_All.png" id="17_g2bud"] @@ -1825,17 +1825,19 @@ limit_bottom = 9999999 position_smoothing_enabled = true editor_draw_limits = true -[node name="PlayerInteraction" type="Node2D" parent="Incomplete Character" unique_id=161308293] +[node name="PlayerInteraction" type="Node2D" parent="Incomplete Character" unique_id=161308293 node_paths=PackedStringArray("character")] unique_name_in_owner = true script = ExtResource("15_ecrvd") +character = NodePath("..") -[node name="InteractZone" type="Area2D" parent="Incomplete Character/PlayerInteraction" unique_id=826531250] +[node name="CharacterSight" type="Area2D" parent="Incomplete Character/PlayerInteraction" unique_id=826531250 node_paths=PackedStringArray("character")] unique_name_in_owner = true collision_layer = 0 collision_mask = 32 script = ExtResource("16_g11k1") +character = NodePath("../..") -[node name="CollisionShape2D" type="CollisionShape2D" parent="Incomplete Character/PlayerInteraction/InteractZone" unique_id=663814065] +[node name="CollisionShape2D" type="CollisionShape2D" parent="Incomplete Character/PlayerInteraction/CharacterSight" unique_id=663814065] position = Vector2(167, 5) shape = SubResource("RectangleShape2D_680ig") debug_color = Color(0.600391, 0.54335, 0, 0.42) From 58d2abcaec343a63559f8e8a7fb3780c1516c5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Tue, 24 Mar 2026 09:00:07 -0300 Subject: [PATCH 3/4] after_the_tremor StoryQuest: Adapt minijuego2 level to player changes --- .../2_sequence_puzzle/2_tallerjuego/minijuego2.tscn | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scenes/quests/story_quests/after_the_tremor/2_sequence_puzzle/2_tallerjuego/minijuego2.tscn b/scenes/quests/story_quests/after_the_tremor/2_sequence_puzzle/2_tallerjuego/minijuego2.tscn index bf48ed45de..4b1602e96c 100644 --- a/scenes/quests/story_quests/after_the_tremor/2_sequence_puzzle/2_tallerjuego/minijuego2.tscn +++ b/scenes/quests/story_quests/after_the_tremor/2_sequence_puzzle/2_tallerjuego/minijuego2.tscn @@ -1,6 +1,7 @@ [gd_scene format=4 uid="uid://g64l65moc1sx"] [ext_resource type="TileSet" uid="uid://4ewcvb1sibsf" path="res://scenes/quests/story_quests/after_the_tremor/3_combat/2_cinematicacombate/tile/tilestaller.tres" id="1_8drhf"] +[ext_resource type="Script" uid="uid://pk3ucq7e2eah" path="res://scenes/game_logic/player_mode.gd" id="1_pm0tv"] [ext_resource type="Shader" uid="uid://dmm7vum4u3k5e" path="res://scenes/quests/story_quests/after_the_tremor/0_intro/0_intro_part6/night.gdshader" id="2_ps5ij"] [ext_resource type="Script" uid="uid://x1mxt6bmei2o" path="res://scenes/ui_elements/cinematic/cinematic.gd" id="3_a1j0i"] [ext_resource type="Resource" uid="uid://x8dfau2t48gw" path="res://scenes/quests/story_quests/after_the_tremor/2_sequence_puzzle/2_tallerjuego/dialogojuego2.dialogue" id="4_ilpog"] @@ -25,7 +26,6 @@ [ext_resource type="Script" uid="uid://cs3mackqqsjmr" path="res://scenes/quests/story_quests/after_the_tremor/2_sequence_puzzle/2_tallerjuego/reveal_tilemap.gd" id="23_ea4l7"] [ext_resource type="AudioStream" uid="uid://buxupti22mp4y" path="res://scenes/quests/story_quests/after_the_tremor/music/minijuego2.ogg" id="24_8drhf"] [ext_resource type="Script" uid="uid://d31cxd03praji" path="res://scenes/quests/story_quests/after_the_tremor/3_combat/scripts/MusicFix.gd" id="25_ls33p"] -[ext_resource type="Script" uid="uid://pk3ucq7e2eah" path="res://scenes/game_logic/player_mode.gd" id="1_pm0tv"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_famss"] shader = ExtResource("2_ps5ij") @@ -105,7 +105,7 @@ sprite_frames = ExtResource("9_cbqvq") position = Vector2(-2.6667075, -73.333336) sprite_frames = ExtResource("9_cbqvq") -[node name="CollisionShape2D" parent="Player/PlayerInteraction/InteractZone" parent_id_path=PackedInt32Array(894731746, 888605377) index="0" unique_id=255765935] +[node name="CollisionShape2D" parent="Player/PlayerInteraction/CharacterSight" parent_id_path=PackedInt32Array(894731746, 888605377) index="0" unique_id=255765935] position = Vector2(53.333294, -73.333336) [node name="Camera2D" type="Camera2D" parent="Player" unique_id=1018623546] From 856a1cdb73aed3041b9f3de8525f71bc1c04b8a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Tue, 24 Mar 2026 09:01:20 -0300 Subject: [PATCH 4/4] fixup! Rework and document CharacterSight and PlayerInteraction --- scenes/game_elements/props/interact_area/interact_area.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scenes/game_elements/props/interact_area/interact_area.gd b/scenes/game_elements/props/interact_area/interact_area.gd index b7e5aa616a..0144f3a1ab 100644 --- a/scenes/game_elements/props/interact_area/interact_area.gd +++ b/scenes/game_elements/props/interact_area/interact_area.gd @@ -7,7 +7,7 @@ extends Area2D ## ## To make an interactable object, add an [InteractArea] to it, and handle ## [signal interaction_started]. When the interaction is complete, call [method -## end_interaction]. This area is detected by the player scene's [InteractZone]; +## end_interaction]. This area is detected by character's [CharacterSight]; ## the player scene is typically responsible for calling [method ## start_interaction] in response to player input. ## [br][br]