xiandie/scene/little_game/general/draggable.gd
2025-07-01 01:16:42 +08:00

158 lines
3.9 KiB
GDScript

@tool
# A draggable item in the game that can be picked up and displayed with an outline effect.
class_name Draggable2D extends Area2D
# pass self
signal picked(node: Draggable2D)
signal dropped(node: Draggable2D)
# 表现为按钮,即点击发送 picked 信号
@export var act_as_button := false
# z +1 when picked (-1 when dropped)
# @export var z_up_on_picked := true
@export var item_name = ""
@export var sprite_offset := Vector2(0, 0):
set(val):
sprite_offset = val
if is_node_ready():
sprite.offset = sprite_offset
@export var freezing := false:
set(val):
freezing = val
if freezing:
if holding:
_drop()
elif touching:
_on_mouse_exited()
@export var texture: Texture2D:
# The texture to display for the item
set(val):
texture = val
if is_node_ready():
sprite.texture = texture
@export var limit_rect := Rect2(Vector2.ZERO, Vector2(564, 316))
@onready var sprite = $Sprite2D as Sprite2D
func _ready() -> void:
sprite.texture = texture
sprite.offset = sprite_offset
if Engine.is_editor_hint():
return
_toggle_outline(false)
mouse_entered.connect(_on_mouse_entered)
mouse_exited.connect(_on_mouse_exited)
# Whether the item is currently being held by the player
var holding = false
var touching = false
static var current_focusing_item = "":
set(val):
current_focusing_item = val
if GlobalConfig.DEBUG:
print("current_focusing_item=", current_focusing_item)
static var pending_enter_callables := [] as Array[Callable]
func is_focused() -> bool:
return current_focusing_item == item_name
func _on_mouse_entered() -> bool:
touching = true
if freezing or not is_visible_in_tree():
return false
if holding or is_focused():
return true
# 尝试获得 current_focusing_item
if current_focusing_item != "":
if not pending_enter_callables.has(_on_mouse_entered):
pending_enter_callables.append(_on_mouse_entered)
return false
current_focusing_item = item_name
_toggle_outline(true)
return true
func _on_mouse_exited() -> void:
touching = false
pending_enter_callables.erase(_on_mouse_entered)
# frezzing 不影响 mouse exited
if is_focused():
current_focusing_item = ""
for c in pending_enter_callables:
if c.call():
break
_toggle_outline(false)
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
if holding:
_drop()
elif touching:
_on_mouse_exited()
func _input(event: InputEvent) -> void:
if freezing or Engine.is_editor_hint() or not is_visible_in_tree():
return
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
# Trigger the pick action
if touching and not holding:
_try_pick()
elif holding:
_drop()
elif holding and event is InputEventMouseMotion:
_update_pos_to_mouse()
func _update_pos_to_mouse():
# update global position
global_position = get_global_mouse_position()
global_position.x = clamp(global_position.x, limit_rect.position.x, limit_rect.end.x)
global_position.y = clamp(global_position.y, limit_rect.position.y, limit_rect.end.y)
func _try_pick() -> void:
if act_as_button:
# 作为按钮,发送 picked 信号
picked.emit(self)
var tween = create_tween()
tween.tween_property(sprite.material, "shader_parameter/alpha_ratio", 1.0, 0.15)
return
if current_focusing_item != item_name:
return
# reset rotation
rotation = 0
_toggle_outline(false)
holding = true
# z_index += 1
picked.emit(self)
_update_pos_to_mouse()
func _drop() -> void:
if holding:
holding = false
# z_index -= 1
dropped.emit(self)
if is_focused():
_toggle_outline(true)
else:
# not touching but dropped, remove current_focusing_item if any
current_focusing_item = ""
func _toggle_outline(display = true):
var tween = create_tween()
if display:
tween.tween_property(sprite.material, "shader_parameter/thickness", 1.0, 0.2)
else:
tween.tween_property(sprite.material, "shader_parameter/thickness", 0.0, 0.2)