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

@@ -19,9 +19,22 @@ class_name CafeCompanionTarget
var _lastClickMsec: int = 0
func _ready() -> void:
var interactable: InteractableComponent = InteractableComponent.new()
interactable.interaction_distance = 160.0
add_child(interactable)
input_pickable = true
set_process_unhandled_input(true)
func is_interaction_active(_component: InteractableComponent) -> bool:
return not servicePointId.strip_edges().is_empty() and not companionId.strip_edges().is_empty()
func build_interaction_actions(_component: InteractableComponent, _player: Node2D) -> Array[InteractionAction]:
var actions: Array[InteractionAction] = []
var display_name: String = _resolved_persona_name()
var action_id: String = "cafe_companion:%s" % servicePointId
actions.append(InteractionAction.create(action_id, "%s 交流" % display_name, 45, Callable(self, "_try_emit_target_selected")))
return actions
func _input_event(_viewport: Viewport, event: InputEvent, _shapeIdx: int) -> void:
if not (event is InputEventMouseButton):
return

View File

@@ -18,7 +18,6 @@ class_name NPCController
signal interaction_happened(text: String)
const CHAT_BUBBLE_SCENE: PackedScene = preload("res://scenes/ui/ChatBubble.tscn")
const InteractionAnchorUtil = preload("res://_Core/utils/InteractionAnchor.gd")
const CHAT_BUBBLE_LAYER_NAME: String = "WorldChatBubbleLayer"
const CHAT_BUBBLE_TARGET_OFFSET: Vector2 = Vector2(0, -84)
const NPC_TALKED_EVENT: String = "npc_talked"
@@ -42,7 +41,14 @@ const NAMEPLATE_VISUAL_CHAR_WIDTH: int = 12
var _nameplate: Label
func _ready() -> void:
add_to_group("whaletown_interactable")
if get_node_or_null("CafeCompanionTarget") == null:
var interactable: InteractableComponent = InteractableComponent.new()
interactable.interaction_id = "npc:%s" % str(get_path())
interactable.interaction_title = "%s 交谈" % npcName
interactable.interaction_priority = 40
interactable.interaction_distance = interactionDistance
interactable.activation_method = &"interact"
add_child(interactable)
# 播放场景里配置好的待机动画,让不同 NPC 可以复用同一个控制器。
if animation_player.has_animation("idle"):
animation_player.play("idle")
@@ -68,19 +74,6 @@ func interact() -> void:
})
interaction_happened.emit(dialogue)
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": "npc:%s" % get_instance_id(),
"title": "%s 交谈" % npcName,
"distance": distance,
"priority": 40,
"callback": Callable(self, "interact"),
}]
# 在 NPC 头顶生成一次性聊天气泡。
#
# 参数:

View File

@@ -26,7 +26,6 @@ const CAFE_NAME_LABEL_VISUAL_MAX_WIDTH: int = 118
const CAFE_NAME_LABEL_VISUAL_CHAR_WIDTH: int = 12
const CAFE_NAME_LABEL_OFFSET_Y: float = -96.0
const WORLD_TEXT_THEME = preload("res://assets/ui/world_text_theme.tres")
const InteractionAnchorUtil = preload("res://_Core/utils/InteractionAnchor.gd")
const DIRECTION_ROWS: Dictionary = {
"down": 0,
"up": 1,
@@ -37,9 +36,12 @@ const DIRECTION_ROWS: Dictionary = {
@onready var sprite: Sprite2D = $Sprite2D
var _nameLabel: Label
var _cafeCompanionTarget: CafeCompanionTarget
var _interactable: InteractableComponent
func _ready() -> void:
add_to_group("whaletown_interactable")
_interactable = InteractableComponent.new()
_interactable.interaction_distance = 160.0
add_child(_interactable)
# 初始化时确保无物理处理
set_physics_process(false)
# 初始位置设为当前位置
@@ -54,37 +56,16 @@ func _ready() -> void:
if has_node("CollisionShape2D"):
$CollisionShape2D.disabled = true
func get_interaction_actions(player: Node2D) -> Array[Dictionary]:
if userId.strip_edges().is_empty() or player == null:
return []
var anchor_position: Vector2 = InteractionAnchorUtil.get_position(self)
var distance: float = anchor_position.distance_to(player.global_position)
if distance > 160.0:
return []
func is_interaction_active(_component: InteractableComponent) -> bool:
return not userId.strip_edges().is_empty()
func build_interaction_actions(_component: InteractableComponent, _player: Node2D) -> Array[InteractionAction]:
var actions: Array[InteractionAction] = []
var display_name := username if not username.strip_edges().is_empty() else "玩家"
return [
{
"id": "player_card:%s" % userId,
"title": "查看 %s 的社区名片" % display_name,
"distance": distance,
"priority": 60,
"callback": Callable(self, "_show_community_profile"),
},
{
"id": "player_dm:%s" % userId,
"title": "私聊 %s" % display_name,
"distance": distance,
"priority": 61,
"callback": Callable(self, "_open_private_chat"),
},
{
"id": "player_friend:%s" % userId,
"title": "申请添加 %s 为好友" % display_name,
"distance": distance,
"priority": 62,
"callback": Callable(self, "_request_friend"),
},
]
actions.append(InteractionAction.create("player_card:%s" % userId, "查看 %s 的社区名片" % display_name, 60, Callable(self, "_show_community_profile")))
actions.append(InteractionAction.create("player_dm:%s" % userId, "私聊 %s" % display_name, 61, Callable(self, "_open_private_chat")))
actions.append(InteractionAction.create("player_friend:%s" % userId, "申请添加 %s 为好友" % display_name, 62, Callable(self, "_request_friend")))
return actions
func _show_community_profile() -> void:
var socialManager := get_node_or_null("/root/SocialManager")