forked from xiangwang25/whale-town-front-v2
refactor: unify interactable components
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 := {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 头顶生成一次性聊天气泡。
|
||||
#
|
||||
# 参数:
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -4,25 +4,17 @@ class_name DatawhaleHonorBoard
|
||||
const RANKING_PANEL_SCENE: PackedScene = preload("res://scenes/ui/datawhale_honor_ranking_panel.tscn")
|
||||
const RANKING_PANEL_NAME: String = "DatawhaleHonorRankingPanel"
|
||||
const INTERACTION_COLLISION_LAYER: int = 2
|
||||
const InteractionAnchorUtil = preload("res://_Core/utils/InteractionAnchor.gd")
|
||||
|
||||
func _ready() -> void:
|
||||
collision_layer = INTERACTION_COLLISION_LAYER
|
||||
collision_mask = 0
|
||||
add_to_group("whaletown_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 > 150.0:
|
||||
return []
|
||||
return [{
|
||||
"id": "honor_board:%s" % get_instance_id(),
|
||||
"title": "查看荣誉榜",
|
||||
"distance": distance,
|
||||
"priority": 35,
|
||||
"callback": Callable(self, "interact"),
|
||||
}]
|
||||
var interactable: InteractableComponent = InteractableComponent.new()
|
||||
interactable.interaction_id = "honor_board:%s" % str(get_path())
|
||||
interactable.interaction_title = "查看荣誉榜"
|
||||
interactable.interaction_priority = 35
|
||||
interactable.interaction_distance = 150.0
|
||||
interactable.activation_method = &"interact"
|
||||
add_child(interactable)
|
||||
|
||||
func interact() -> void:
|
||||
var root: Window = get_tree().root
|
||||
|
||||
@@ -4,25 +4,17 @@ class_name NoticeBoard
|
||||
const NOTICE_DIALOG_SCENE: PackedScene = preload("res://scenes/ui/notice_dialog.tscn")
|
||||
const NOTICE_DIALOG_NAME: String = "NoticeDialog"
|
||||
const INTERACTION_COLLISION_LAYER: int = 2
|
||||
const InteractionAnchorUtil = preload("res://_Core/utils/InteractionAnchor.gd")
|
||||
|
||||
func _ready() -> void:
|
||||
collision_layer = INTERACTION_COLLISION_LAYER
|
||||
collision_mask = 0
|
||||
add_to_group("whaletown_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 > 150.0:
|
||||
return []
|
||||
return [{
|
||||
"id": "notice_board:%s" % get_instance_id(),
|
||||
"title": "查看公告栏",
|
||||
"distance": distance,
|
||||
"priority": 35,
|
||||
"callback": Callable(self, "interact"),
|
||||
}]
|
||||
var interactable: InteractableComponent = InteractableComponent.new()
|
||||
interactable.interaction_id = "notice_board:%s" % str(get_path())
|
||||
interactable.interaction_title = "查看公告栏"
|
||||
interactable.interaction_priority = 35
|
||||
interactable.interaction_distance = 150.0
|
||||
interactable.activation_method = &"interact"
|
||||
add_child(interactable)
|
||||
|
||||
func interact() -> void:
|
||||
var root: Window = get_tree().root
|
||||
|
||||
@@ -4,25 +4,17 @@ class_name WelcomeBoard
|
||||
const WELCOME_DIALOG_SCENE: PackedScene = preload("res://scenes/ui/welcome_dialog.tscn")
|
||||
const WELCOME_DIALOG_NAME: String = "WelcomeDialog"
|
||||
const INTERACTION_COLLISION_LAYER: int = 2
|
||||
const InteractionAnchorUtil = preload("res://_Core/utils/InteractionAnchor.gd")
|
||||
|
||||
func _ready() -> void:
|
||||
collision_layer = INTERACTION_COLLISION_LAYER
|
||||
collision_mask = 0
|
||||
add_to_group("whaletown_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 > 150.0:
|
||||
return []
|
||||
return [{
|
||||
"id": "welcome_board:%s" % get_instance_id(),
|
||||
"title": "查看新人引导",
|
||||
"distance": distance,
|
||||
"priority": 35,
|
||||
"callback": Callable(self, "interact"),
|
||||
}]
|
||||
var interactable: InteractableComponent = InteractableComponent.new()
|
||||
interactable.interaction_id = "welcome_board:%s" % str(get_path())
|
||||
interactable.interaction_title = "查看新人引导"
|
||||
interactable.interaction_priority = 35
|
||||
interactable.interaction_distance = 150.0
|
||||
interactable.activation_method = &"interact"
|
||||
add_child(interactable)
|
||||
|
||||
func interact() -> void:
|
||||
var root: Window = get_tree().root
|
||||
|
||||
Reference in New Issue
Block a user