forked from xiangwang25/whale-town-front-v2
97 lines
3.7 KiB
GDScript
97 lines
3.7 KiB
GDScript
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
|