refactor: unify interactable components

This commit is contained in:
ANG-Server
2026-07-21 23:23:26 +08:00
parent 9dee47492c
commit 74a0fb8309
17 changed files with 241 additions and 208 deletions

View File

@@ -23,7 +23,6 @@ const COMPANION_NAMEPLATE_VISUAL_MAX_WIDTH: int = 118
const COMPANION_NAMEPLATE_VISUAL_CHAR_WIDTH: int = 12
const NPC_NAMEPLATE_OFFSET_Y: float = -136.0
const HIRED_PLAYER_NAMEPLATE_OFFSET_Y: float = -96.0
const InteractionAnchorUtil = preload("res://_Core/utils/InteractionAnchor.gd")
@onready var player: PlayerController = $YSortWorld/Characters/Players/Player
@onready var playerCamera: Camera2D = $YSortWorld/Characters/Players/Player/Camera2D
@@ -39,7 +38,6 @@ var _isChangingScene: bool = false
var _lastRecruitmentClickMsec: int = 0
func _ready() -> void:
add_to_group("whaletown_interactable")
_align_service_occupants()
_configure_static_companion_nameplates()
_apply_spawn_point()
@@ -47,6 +45,7 @@ func _ready() -> void:
_connect_exit_area()
_connect_recruitment_area()
_connect_cafe_companion_events()
_register_interactables()
_discover_destination()
func _discover_destination() -> void:
@@ -103,29 +102,28 @@ func _connect_cafe_companion_events() -> void:
eventSystem.call("connect_event", EventNames.CAFE_COMPANION_AGENT_REGISTERED, _on_cafe_companion_agent_registered, self)
eventSystem.call("connect_event", EventNames.CAFE_COMPANION_EMPLOYMENT_RESIGNED, _on_cafe_companion_employment_resigned, self)
func _register_interactables() -> void:
var exit_interactable: InteractableComponent = InteractableComponent.new()
exit_interactable.interaction_id = "cafe_exit"
exit_interactable.interaction_title = "离开咖啡馆"
exit_interactable.interaction_priority = 20
exit_interactable.interaction_distance = 160.0
exit_interactable.activation_method = &"_leave_to_work_zone"
exit_interactable.anchor_path = NodePath("InteractionAreas/ExitToWorkZoneArea")
add_child(exit_interactable)
var recruitment_interactable: InteractableComponent = InteractableComponent.new()
recruitment_interactable.interaction_id = "cafe_recruitment"
recruitment_interactable.interaction_title = "登记咖啡店陪伴机器人"
recruitment_interactable.interaction_priority = 30
recruitment_interactable.interaction_distance = 150.0
recruitment_interactable.activation_method = &"_try_emit_recruitment_selected"
recruitment_interactable.anchor_path = NodePath("InteractionAreas/CafeRecruitmentLogoArea")
add_child(recruitment_interactable)
func _on_exit_area_body_entered(body: Node2D) -> void:
return
func get_interaction_actions(local_player: Node2D) -> Array[Dictionary]:
if _isChangingScene or exitToWorkZoneArea == null:
return []
var anchor_position: Vector2 = InteractionAnchorUtil.get_position(exitToWorkZoneArea)
var distance: float = anchor_position.distance_to(local_player.global_position)
if distance > 160.0:
return []
return [{
"id": "cafe_exit",
"title": "离开咖啡馆",
"distance": distance,
"priority": 20,
"callback": Callable(self, "_leave_to_work_zone"),
}]
func get_interaction_marker_positions() -> Array[Vector2]:
if exitToWorkZoneArea == null:
return []
return [InteractionAnchorUtil.get_position(exitToWorkZoneArea)]
func _leave_to_work_zone() -> void:
if _isChangingScene:
return

View File

@@ -1,8 +1,6 @@
class_name ScenePortal
extends Area2D
const InteractionAnchorUtil = preload("res://_Core/utils/InteractionAnchor.gd")
# ============================================================================
# ScenePortal.gd - 场景入口传送组件
# ============================================================================
@@ -16,27 +14,20 @@ const InteractionAnchorUtil = preload("res://_Core/utils/InteractionAnchor.gd")
@export var interactionDistance: float = 160.0
func _ready() -> void:
add_to_group("whaletown_interactable")
var label: String = interactionTitle.strip_edges()
if label.is_empty():
label = "进入 %s" % targetSceneName
var interactable: InteractableComponent = InteractableComponent.new()
interactable.interaction_id = "portal:%s" % str(get_path())
interactable.interaction_title = label
interactable.interaction_priority = 20
interactable.interaction_distance = interactionDistance
interactable.activation_method = &"_change_scene"
add_child(interactable)
func _on_body_entered(body: Node2D) -> void:
return
func get_interaction_actions(player: Node2D) -> Array[Dictionary]:
var anchor_position: Vector2 = InteractionAnchorUtil.get_position(self)
var distance: float = anchor_position.distance_to(player.global_position)
if distance > interactionDistance:
return []
var label := interactionTitle.strip_edges()
if label.is_empty():
label = "进入 %s" % targetSceneName
return [{
"id": "portal:%s" % get_instance_id(),
"title": label,
"distance": distance,
"priority": 20,
"callback": Callable(self, "_change_scene"),
}]
func _change_scene() -> void:
if targetSceneName.is_empty():
push_warning("ScenePortal: targetSceneName is empty.")

View File

@@ -31,7 +31,6 @@ var _isInsideMall: bool = false
var _mallCanEnter: bool = true
func _ready() -> void:
add_to_group("whaletown_interactable")
_apply_spawn_point()
_configure_camera()
_ensure_mall_panel()

View File

@@ -8,7 +8,6 @@ extends Area2D
# ============================================================================
const INTERACTION_COLLISION_LAYER: int = 2
const InteractionAnchorUtil = preload("res://_Core/utils/InteractionAnchor.gd")
@export var buildingId: String = ""
@export var buildingTitle: String = ""
@@ -18,20 +17,18 @@ const InteractionAnchorUtil = preload("res://_Core/utils/InteractionAnchor.gd")
func _ready() -> void:
collision_layer = INTERACTION_COLLISION_LAYER
collision_mask = 0
add_to_group("whaletown_interactable")
var interactable: InteractableComponent = InteractableComponent.new()
interactable.interaction_distance = interactionDistance
add_child(interactable)
func get_interaction_actions(player: Node2D) -> Array[Dictionary]:
var anchor_position: Vector2 = InteractionAnchorUtil.get_position(self)
var distance: float = anchor_position.distance_to(player.global_position)
if distance > interactionDistance:
return []
return [{
"id": "building:%s" % buildingId,
"title": "使用 %s" % (buildingTitle if not buildingTitle.is_empty() else "设施"),
"distance": distance,
"priority": 30,
"callback": Callable(self, "interact"),
}]
func is_interaction_active(_component: InteractableComponent) -> bool:
return not buildingId.strip_edges().is_empty()
func build_interaction_actions(_component: InteractableComponent, _player: Node2D) -> Array[InteractionAction]:
var actions: Array[InteractionAction] = []
var title: String = buildingTitle if not buildingTitle.is_empty() else "设施"
actions.append(InteractionAction.create("building:%s" % buildingId, "使用 %s" % title, 30, Callable(self, "interact")))
return actions
func interact() -> void:
var payload := {