xiandie/scene/little_game/general/draggable.gd

199 lines
4.9 KiB
GDScript3
Raw Normal View History

2025-05-29 06:14:02 +00:00
@tool
# A draggable item in the game that can be picked up and displayed with an outline effect.
class_name Draggable2D extends Area2D
2025-05-30 11:05:06 +00:00
# pass self
signal picked(node: Draggable2D)
signal dropped(node: Draggable2D)
2025-05-29 06:14:02 +00:00
2025-05-30 11:05:06 +00:00
# 表现为按钮,即点击发送 picked 信号
@export var act_as_button := false
2025-05-29 06:14:02 +00:00
# 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
2025-05-30 11:05:06 +00:00
if freezing:
if holding:
_drop()
elif touching:
_on_mouse_exited()
2025-05-29 06:14:02 +00:00
@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))
2025-07-24 07:52:46 +00:00
@onready var sprite: Sprite2D = $Sprite2D
# Whether the item is currently being held by the player
var holding := false:
set(val):
if holding != val:
holding = val
var touching := false
# 静态变量优化:使用对象引用而非字符串
static var current_focusing_node: Draggable2D = null
static var pending_enter_callables: Array[Callable] = []
# 缓存常量
const OUTLINE_THICKNESS := 1.0
const OUTLINE_TWEEN_DURATION := 0.2
const BUTTON_ALPHA_DURATION := 0.15
# 缓存变量
var _outline_tween: Tween
var _button_tween: Tween
2025-05-29 06:14:02 +00:00
func _ready() -> void:
sprite.texture = texture
sprite.offset = sprite_offset
if Engine.is_editor_hint():
return
# 初始化隐藏白边
2025-07-24 07:52:46 +00:00
sprite.material.set_shader_parameter("thickness", 0.0)
# 安全检查
if has_signal("mouse_entered"):
mouse_entered.connect(_on_mouse_entered)
mouse_exited.connect(_on_mouse_exited)
else:
printerr("Draggable2D: mouse_entered or mouse_exited signal not found.")
2025-06-30 17:16:42 +00:00
2025-05-29 06:14:02 +00:00
2025-06-30 17:16:42 +00:00
func is_focused() -> bool:
2025-07-24 07:52:46 +00:00
return current_focusing_node == self
2025-06-30 17:16:42 +00:00
func _on_mouse_entered() -> bool:
touching = true
2025-05-30 11:05:06 +00:00
if freezing or not is_visible_in_tree():
2025-06-30 17:16:42 +00:00
return false
if holding or is_focused():
return true
2025-07-24 07:52:46 +00:00
# 尝试获得 current_focusing_node
if current_focusing_node:
2025-06-30 17:16:42 +00:00
if not pending_enter_callables.has(_on_mouse_entered):
pending_enter_callables.append(_on_mouse_entered)
return false
2025-07-24 07:52:46 +00:00
current_focusing_node = self
2025-05-29 06:14:02 +00:00
_toggle_outline(true)
2025-06-30 17:16:42 +00:00
return true
2025-05-29 06:14:02 +00:00
func _on_mouse_exited() -> void:
2025-06-30 17:16:42 +00:00
touching = false
pending_enter_callables.erase(_on_mouse_entered)
2025-07-24 07:52:46 +00:00
# freezing 不影响 mouse exited
if is_focused() and not holding:
current_focusing_node = null
while pending_enter_callables.size() > 0:
var c = pending_enter_callables.pop_front()
2025-06-30 17:16:42 +00:00
if c.call():
break
2025-05-29 06:14:02 +00:00
_toggle_outline(false)
2025-05-30 11:05:06 +00:00
2025-05-29 06:14:02 +00:00
func _input(event: InputEvent) -> void:
2025-05-30 11:05:06 +00:00
if freezing or Engine.is_editor_hint() or not is_visible_in_tree():
2025-05-29 06:14:02 +00:00
return
2025-07-24 07:52:46 +00:00
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
if is_focused() and not holding:
get_viewport().set_input_as_handled()
_try_pick.call_deferred()
elif holding:
get_viewport().set_input_as_handled()
_drop()
2025-05-29 06:14:02 +00:00
elif holding and event is InputEventMouseMotion:
_update_pos_to_mouse()
2025-05-30 11:05:06 +00:00
2025-07-24 07:52:46 +00:00
func _update_pos_to_mouse() -> void:
2025-05-29 06:14:02 +00:00
# update global position
2025-07-24 07:52:46 +00:00
var target_pos = get_global_mouse_position()
global_position.x = clamp(target_pos.x, limit_rect.position.x, limit_rect.end.x)
global_position.y = clamp(target_pos.y, limit_rect.position.y, limit_rect.end.y)
2025-05-29 06:14:02 +00:00
func _try_pick() -> void:
2025-05-30 11:05:06 +00:00
if act_as_button:
# 作为按钮,发送 picked 信号
picked.emit(self)
2025-07-24 07:52:46 +00:00
if _button_tween and _button_tween.is_running():
_button_tween.kill()
_button_tween = create_tween()
_button_tween.tween_property(sprite.material, "shader_parameter/alpha_ratio", 1.0, BUTTON_ALPHA_DURATION)
2025-05-30 11:05:06 +00:00
return
2025-07-24 07:52:46 +00:00
if not is_focused():
2025-05-29 06:14:02 +00:00
return
2025-07-24 07:52:46 +00:00
2025-05-29 06:14:02 +00:00
# reset rotation
rotation = 0
_toggle_outline(false)
holding = true
# z_index += 1
2025-05-30 11:05:06 +00:00
picked.emit(self)
2025-05-29 06:14:02 +00:00
_update_pos_to_mouse()
func _drop() -> void:
2025-07-24 07:52:46 +00:00
if touching:
_toggle_outline(true)
2025-05-29 06:14:02 +00:00
if holding:
holding = false
2025-07-24 07:52:46 +00:00
if not touching:
current_focusing_node = null
# 清空数组前创建副本,避免在迭代时修改数组
var callables_copy = pending_enter_callables.duplicate()
for c in callables_copy:
if c.call():
break
2025-05-29 06:14:02 +00:00
# z_index -= 1
2025-05-30 11:05:06 +00:00
dropped.emit(self)
2025-05-29 06:14:02 +00:00
2025-07-24 07:52:46 +00:00
func _toggle_outline(display: bool) -> void:
# 避免重复创建 tween
if _outline_tween and _outline_tween.is_running():
_outline_tween.kill()
_outline_tween = create_tween()
var target_thickness := OUTLINE_THICKNESS if display else 0.0
_outline_tween.tween_property(
sprite.material, "shader_parameter/thickness", target_thickness, OUTLINE_TWEEN_DURATION
)
2025-07-14 10:46:43 +00:00
func _exit_tree() -> void:
if touching:
2025-07-24 07:52:46 +00:00
_on_mouse_exited()
# 清理静态引用,避免内存泄漏
if current_focusing_node == self:
current_focusing_node = null
func force_hold() -> void:
if holding:
return
if not is_focused() and current_focusing_node:
current_focusing_node._drop()
_toggle_outline(false)
current_focusing_node = self
holding = true
picked.emit(self)