class_name InteractableComponent extends Node # 可挂载于任意 Node2D 宿主的交互组件。 # 它统一负责交互锚点、距离过滤、白圈位置和静态动作;复杂宿主可通过 # build_interaction_actions(component, player) 返回多个 InteractionAction。 const GROUP: StringName = &"whaletown_interactable_component" @export_category("Interaction") @export var interaction_id: String = "" @export var interaction_title: String = "" @export var interaction_priority: int = 100 @export var interaction_distance: float = 150.0 @export var activation_method: StringName = &"" @export var show_marker: bool = true @export_category("Anchor") @export var anchor_path: NodePath = NodePath("") var _host: Node2D func _ready() -> void: _host = get_parent() as Node2D if _host == null: push_error("InteractableComponent 必须挂在 Node2D 宿主下:%s" % get_path()) return add_to_group(GROUP) func get_actions(player: Node2D) -> Array[InteractionAction]: var actions: Array[InteractionAction] = [] if player == null or not is_interaction_active(): return actions var distance: float = get_anchor_position().distance_to(player.global_position) if distance > interaction_distance: return actions if _host != null and _host.has_method("build_interaction_actions"): var actions_variant: Variant = _host.call("build_interaction_actions", self, player) if actions_variant is Array: for action_variant: Variant in actions_variant as Array: if action_variant is InteractionAction: var action: InteractionAction = action_variant as InteractionAction if action.is_valid(): action.distance = distance actions.append(action) return actions var default_action: InteractionAction = _create_default_action() if default_action != null: default_action.distance = distance actions.append(default_action) return actions func get_marker_positions() -> Array[Vector2]: if not show_marker or not is_interaction_active(): return [] return [get_anchor_position()] func get_anchor_position() -> Vector2: var anchor: Node2D = _resolve_anchor() if anchor == null: return Vector2.ZERO var collision_shape: CollisionShape2D = _first_enabled_collision_shape(anchor) return collision_shape.global_position if collision_shape != null else anchor.global_position func is_interaction_active() -> bool: if _host == null: return false if _host.has_method("is_interaction_active"): return bool(_host.call("is_interaction_active", self)) return true func _create_default_action() -> InteractionAction: if _host == null or activation_method.is_empty() or interaction_id.strip_edges().is_empty() or interaction_title.strip_edges().is_empty(): return null if not _host.has_method(activation_method): push_warning("InteractableComponent: %s 不存在方法 %s" % [_host.get_path(), activation_method]) return null return InteractionAction.create(interaction_id, interaction_title, interaction_priority, Callable(_host, activation_method)) func _resolve_anchor() -> Node2D: if _host == null: return null if not anchor_path.is_empty(): var explicit_anchor: Node2D = _host.get_node_or_null(anchor_path) as Node2D if explicit_anchor != null: return explicit_anchor return _host func _first_enabled_collision_shape(anchor: Node2D) -> CollisionShape2D: var direct_shape: CollisionShape2D = anchor as CollisionShape2D if direct_shape != null and not direct_shape.disabled and direct_shape.shape != null: return direct_shape for child: Node in anchor.get_children(): var collision_shape: CollisionShape2D = child as CollisionShape2D if collision_shape != null and not collision_shape.disabled and collision_shape.shape != null: return collision_shape return null