## DragDropComponent — reusable drag-and-drop node; attach to any Character or InteractiveObject. class_name DragDropComponent extends Node signal drag_picked_up(global_position: Vector2) signal drag_released(global_position: Vector2) const DRAG_Z_INDEX: int = 10 const DRAG_SCALE: float = 1.1 const DEFAULT_DRAG_RADIUS: float = 64.0 @export var drag_target: Node2D var _is_dragging: bool = false var _drag_offset: Vector2 = Vector2.ZERO var _original_z_index: int = 0 var _original_scale: Vector2 = Vector2.ONE func _ready() -> void: if drag_target == null: drag_target = get_parent() as Node2D func _input(event: InputEvent) -> void: if event is InputEventScreenTouch: _handle_press(event.pressed, event.position) elif event is InputEventScreenDrag and _is_dragging: _move_to(event.position) elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: _handle_press(event.pressed, event.position) elif event is InputEventMouseMotion and _is_dragging: _move_to(event.position) func _handle_press(pressed: bool, screen_pos: Vector2) -> void: if pressed and _is_position_over_target(screen_pos): _start_drag(screen_pos) elif not pressed and _is_dragging: _end_drag(screen_pos) func _start_drag(screen_pos: Vector2) -> void: _is_dragging = true var world_pos: Vector2 = _screen_to_world(screen_pos) _drag_offset = drag_target.global_position - world_pos _original_z_index = drag_target.z_index _original_scale = drag_target.scale drag_target.z_index = DRAG_Z_INDEX drag_target.scale = _original_scale * DRAG_SCALE drag_picked_up.emit(drag_target.global_position) func _end_drag(_screen_pos: Vector2) -> void: _is_dragging = false drag_target.z_index = _original_z_index drag_target.scale = _original_scale drag_released.emit(drag_target.global_position) func _move_to(screen_pos: Vector2) -> void: var world_pos: Vector2 = _screen_to_world(screen_pos) drag_target.global_position = world_pos + _drag_offset func _screen_to_world(screen_pos: Vector2) -> Vector2: var canvas_transform: Transform2D = drag_target.get_viewport().get_canvas_transform() return canvas_transform.affine_inverse() * screen_pos func _is_position_over_target(screen_pos: Vector2) -> bool: if drag_target == null: return false var world_pos: Vector2 = _screen_to_world(screen_pos) var local_pos: Vector2 = drag_target.to_local(world_pos) var area: Area2D = drag_target.get_node_or_null("CollisionArea") as Area2D if area == null: return local_pos.length() < DEFAULT_DRAG_RADIUS for child in area.get_children(): if child is CollisionShape2D and child.shape != null: return child.shape.get_rect().has_point(local_pos) return local_pos.length() < DEFAULT_DRAG_RADIUS