2024-12-30 13:19:10 +00:00
|
|
|
|
@tool
|
2025-01-13 08:09:57 +00:00
|
|
|
|
class_name Sign extends Control
|
|
|
|
|
|
2024-12-26 13:58:37 +00:00
|
|
|
|
signal interacted
|
|
|
|
|
signal cancel
|
2025-02-12 07:03:41 +00:00
|
|
|
|
signal toggle_active(activated: bool)
|
2024-12-26 13:58:37 +00:00
|
|
|
|
|
2025-06-13 08:03:19 +00:00
|
|
|
|
@export var sign_mark_offset := Vector2.ZERO:
|
|
|
|
|
set(val):
|
|
|
|
|
sign_mark_offset = val
|
|
|
|
|
if is_node_ready():
|
|
|
|
|
texture_container.position = val
|
2025-05-21 20:16:27 +00:00
|
|
|
|
# @export var lock_on_player_freezed := false
|
2025-01-20 13:45:47 +00:00
|
|
|
|
@export var enabled := true:
|
|
|
|
|
set(val):
|
2025-06-28 16:11:21 +00:00
|
|
|
|
if enabled != val:
|
|
|
|
|
enabled = val
|
|
|
|
|
align_activation()
|
2025-01-20 13:45:47 +00:00
|
|
|
|
@export var display_sign := true:
|
|
|
|
|
set(val):
|
2025-01-30 12:04:02 +00:00
|
|
|
|
if display_sign != val:
|
|
|
|
|
display_sign = val
|
2025-06-13 08:03:19 +00:00
|
|
|
|
if is_node_ready():
|
|
|
|
|
_check_sign_display()
|
2025-01-30 12:04:02 +00:00
|
|
|
|
# @export var draw_shadow := false
|
2025-01-20 13:45:47 +00:00
|
|
|
|
|
2025-01-13 08:09:57 +00:00
|
|
|
|
@onready var texture_container = %TextureContainer as Container
|
|
|
|
|
@onready var sprite2d = %Sprite2D as Sprite2D
|
|
|
|
|
|
2025-05-13 11:45:33 +00:00
|
|
|
|
# 跳过输入事件
|
|
|
|
|
var pass_unhandled_input = false
|
|
|
|
|
|
2025-01-13 08:09:57 +00:00
|
|
|
|
# var shadow_texture = preload("res://asset/art/ui/action_mark/探索空心ui.png") as Texture2D
|
|
|
|
|
|
2024-12-27 07:56:45 +00:00
|
|
|
|
# 同时只能有一个物品被激活交互态,其他物品进入等待队列
|
|
|
|
|
static var occupied: NodePath
|
2025-06-28 16:11:21 +00:00
|
|
|
|
# touching 状态改变时就要擦除
|
|
|
|
|
static var _touching_sign_arr: Array[NodePath] = []
|
|
|
|
|
# 使用互斥锁保证线程安全。在操作 occupied 或 _touching_sign_arr 时需要先锁定,操作完成后解锁
|
2024-12-27 07:56:45 +00:00
|
|
|
|
static var mutex = Mutex.new()
|
2025-01-30 12:04:02 +00:00
|
|
|
|
var tween: Tween
|
2024-12-27 07:56:45 +00:00
|
|
|
|
|
2025-06-28 13:42:20 +00:00
|
|
|
|
var player_touching := false:
|
|
|
|
|
set(val):
|
|
|
|
|
if player_touching != val:
|
|
|
|
|
player_touching = val
|
2025-06-28 16:11:21 +00:00
|
|
|
|
var path = get_path()
|
|
|
|
|
if player_touching and not _touching_sign_arr.has(path):
|
|
|
|
|
_touching_sign_arr.append(path)
|
|
|
|
|
if not player_touching:
|
|
|
|
|
_touching_sign_arr.erase(path)
|
|
|
|
|
align_activation()
|
2025-01-13 08:09:57 +00:00
|
|
|
|
var activated = false:
|
|
|
|
|
set(val):
|
2025-02-12 07:03:41 +00:00
|
|
|
|
if activated != val:
|
|
|
|
|
activated = val
|
2025-06-28 13:42:20 +00:00
|
|
|
|
_check_sign_display()
|
2025-02-12 07:03:41 +00:00
|
|
|
|
toggle_active.emit(activated)
|
2024-12-27 07:56:45 +00:00
|
|
|
|
var base_scale = Vector2.ONE
|
2024-12-26 13:58:37 +00:00
|
|
|
|
|
2024-12-27 13:32:12 +00:00
|
|
|
|
|
2024-12-26 13:58:37 +00:00
|
|
|
|
func _ready() -> void:
|
2025-06-13 08:03:19 +00:00
|
|
|
|
texture_container.position = sign_mark_offset
|
|
|
|
|
base_scale = sprite2d.scale
|
|
|
|
|
# 读取 parent 的 sign_mark_offset_updated 信号与 sign_mark_offset
|
|
|
|
|
var parent = get_parent()
|
|
|
|
|
if parent and parent.has_signal("sign_mark_offset_updated"):
|
|
|
|
|
sign_mark_offset = parent.sign_mark_offset
|
|
|
|
|
parent.sign_mark_offset_updated.connect(func(o): sign_mark_offset = o)
|
|
|
|
|
_check_sign_display()
|
2025-01-20 13:45:47 +00:00
|
|
|
|
if Engine.is_editor_hint():
|
|
|
|
|
return
|
|
|
|
|
# var point_light = get_node_or_null("../PointLight2D")
|
|
|
|
|
# if point_light:
|
|
|
|
|
# point_light.energy = 0.0
|
2024-12-27 07:56:45 +00:00
|
|
|
|
var area2d = get_node_or_null("../Area2D")
|
|
|
|
|
if area2d:
|
2025-06-28 13:42:20 +00:00
|
|
|
|
area2d.body_entered.connect(_on_body_entered)
|
|
|
|
|
area2d.body_exited.connect(_on_body_exited)
|
2025-01-20 13:45:47 +00:00
|
|
|
|
visibility_changed.connect(_on_visibility_changed)
|
2025-06-28 13:42:20 +00:00
|
|
|
|
# 关注 lock 的状态变化
|
|
|
|
|
SceneManager.get_lock().hold_changed.connect(_on_lock_hold_changed)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func is_hold() -> bool:
|
|
|
|
|
return SceneManager.get_lock().is_held()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_lock_hold_changed(count: int, is_add: bool):
|
|
|
|
|
if count == 0 or (count == 1 and is_add):
|
2025-06-28 16:11:21 +00:00
|
|
|
|
align_activation()
|
2025-01-20 13:45:47 +00:00
|
|
|
|
|
2024-12-27 07:56:45 +00:00
|
|
|
|
|
2025-01-20 13:45:47 +00:00
|
|
|
|
func _on_visibility_changed() -> void:
|
2025-06-28 16:11:21 +00:00
|
|
|
|
align_activation()
|
2025-01-20 13:45:47 +00:00
|
|
|
|
|
|
|
|
|
|
2025-06-28 13:42:20 +00:00
|
|
|
|
# activition/display mode 变化时 _check_sign_display
|
2025-01-20 13:45:47 +00:00
|
|
|
|
func _check_sign_display():
|
2025-05-13 11:45:33 +00:00
|
|
|
|
if Engine.is_editor_hint():
|
2025-06-28 13:42:20 +00:00
|
|
|
|
# 在编辑器中与 display_sign & enabled 状态保持一致
|
|
|
|
|
if display_sign and enabled:
|
2025-06-13 08:03:19 +00:00
|
|
|
|
sprite2d.modulate.a = 1.0
|
|
|
|
|
else:
|
|
|
|
|
sprite2d.modulate.a = 0.0
|
2025-05-13 11:45:33 +00:00
|
|
|
|
return
|
2025-06-28 13:42:20 +00:00
|
|
|
|
if not display_sign or not activated:
|
2025-06-13 08:03:19 +00:00
|
|
|
|
sprite2d.modulate.a = 0.0
|
2025-06-28 13:42:20 +00:00
|
|
|
|
elif sprite2d.modulate.a == 0.0:
|
2025-06-13 08:03:19 +00:00
|
|
|
|
sprite2d.modulate.a = 1.0
|
2024-12-27 07:56:45 +00:00
|
|
|
|
|
2025-01-20 13:45:47 +00:00
|
|
|
|
|
2025-06-28 13:42:20 +00:00
|
|
|
|
func _on_body_entered(_body: Node2D) -> void:
|
2025-01-20 13:45:47 +00:00
|
|
|
|
player_touching = true
|
2025-06-28 13:42:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_body_exited(_body: Node2D) -> void:
|
2025-01-20 13:45:47 +00:00
|
|
|
|
player_touching = false
|
2025-06-28 13:42:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 四种参数变化时调用 align: 1 enable 2 hold 3 visibility 4 touching
|
2025-06-28 16:11:21 +00:00
|
|
|
|
func align_activation() -> bool:
|
2025-06-28 13:42:20 +00:00
|
|
|
|
if Engine.is_editor_hint():
|
|
|
|
|
return activated
|
|
|
|
|
var ideal_status := enabled and player_touching and not is_hold() and is_visible_in_tree()
|
|
|
|
|
if activated != ideal_status:
|
|
|
|
|
if ideal_status:
|
|
|
|
|
_try_activate()
|
|
|
|
|
else:
|
|
|
|
|
_try_disactivate()
|
|
|
|
|
if GlobalConfig.DEBUG:
|
|
|
|
|
var activated_color = "cyan" if activated else "brown"
|
|
|
|
|
print_rich(
|
|
|
|
|
"[b]",
|
|
|
|
|
get_parent().name,
|
|
|
|
|
"[/b] activated=[color=",
|
|
|
|
|
activated_color,
|
|
|
|
|
"]",
|
|
|
|
|
activated,
|
|
|
|
|
"[/color]",
|
|
|
|
|
" occupied:[color=khaki]",
|
|
|
|
|
_get_name_from_path(occupied),
|
2025-06-28 16:11:21 +00:00
|
|
|
|
"[/color] touching:[color=coral]",
|
|
|
|
|
_get_name_from_paths(_touching_sign_arr),
|
2025-06-28 13:42:20 +00:00
|
|
|
|
"[/color]"
|
|
|
|
|
)
|
|
|
|
|
return activated
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _try_disactivate() -> void:
|
|
|
|
|
mutex.lock()
|
2025-01-07 11:44:10 +00:00
|
|
|
|
if activated:
|
2025-01-08 00:51:09 +00:00
|
|
|
|
activated = false
|
2025-06-28 13:42:20 +00:00
|
|
|
|
occupied = NodePath(&"")
|
2025-01-20 13:45:47 +00:00
|
|
|
|
# 转移 active 状态给下一个节点
|
2025-06-28 16:11:21 +00:00
|
|
|
|
var self_path = get_path()
|
|
|
|
|
for p in _touching_sign_arr:
|
|
|
|
|
# skip self
|
|
|
|
|
if p == self_path:
|
|
|
|
|
continue
|
|
|
|
|
var s = get_node_or_null(p) as Sign
|
|
|
|
|
if s and s.align_activation():
|
2024-12-27 13:32:12 +00:00
|
|
|
|
break
|
2025-01-07 11:44:10 +00:00
|
|
|
|
mutex.unlock()
|
2024-12-27 07:56:45 +00:00
|
|
|
|
# point_light.energy = 0.0
|
2025-01-30 12:04:02 +00:00
|
|
|
|
if tween and tween.is_valid():
|
|
|
|
|
tween.kill()
|
2025-06-13 08:03:19 +00:00
|
|
|
|
if sprite2d.modulate.a > 0.0:
|
2025-01-30 12:04:02 +00:00
|
|
|
|
tween = create_tween()
|
2025-06-13 08:03:19 +00:00
|
|
|
|
tween.tween_property(sprite2d, "modulate:a", 0.0, 0.2)
|
2024-12-26 13:58:37 +00:00
|
|
|
|
|
|
|
|
|
|
2025-06-28 13:42:20 +00:00
|
|
|
|
func _try_activate() -> bool:
|
|
|
|
|
# point_light.energy = 1.0
|
|
|
|
|
var path := get_path()
|
|
|
|
|
mutex.lock()
|
|
|
|
|
# 若 lock 为 hold 状态则直接跳过
|
2025-06-28 16:11:21 +00:00
|
|
|
|
if not occupied or occupied == path:
|
2025-06-28 13:42:20 +00:00
|
|
|
|
occupied = path
|
|
|
|
|
activated = true
|
|
|
|
|
mutex.unlock()
|
|
|
|
|
if activated and display_sign:
|
|
|
|
|
if tween and tween.is_valid():
|
|
|
|
|
tween.kill()
|
|
|
|
|
tween = create_tween()
|
|
|
|
|
tween.tween_property(sprite2d, "modulate:a", 1.0, 0.2)
|
|
|
|
|
var p_tween = tween.parallel()
|
|
|
|
|
p_tween.tween_property(sprite2d, "scale", base_scale * Vector2(1.2, 1.2), 0.3)
|
|
|
|
|
p_tween.tween_property(sprite2d, "scale", base_scale, 0.1)
|
|
|
|
|
return activated
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _get_name_from_path(path: NodePath) -> String:
|
|
|
|
|
if not path:
|
|
|
|
|
return ""
|
|
|
|
|
# return -2 idx
|
|
|
|
|
var parts = path.get_concatenated_names().split("/")
|
|
|
|
|
if parts.size() >= 2:
|
|
|
|
|
return parts[parts.size() - 2]
|
|
|
|
|
elif parts.size() >= 1:
|
|
|
|
|
return parts[parts.size() - 1]
|
|
|
|
|
else:
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _get_name_from_paths(paths: Array[NodePath]) -> String:
|
|
|
|
|
if len(paths) == 0:
|
|
|
|
|
return ""
|
|
|
|
|
var names = []
|
|
|
|
|
for path in paths:
|
|
|
|
|
names.append(_get_name_from_path(path))
|
|
|
|
|
return ", ".join(names)
|
|
|
|
|
|
|
|
|
|
|
2025-01-03 08:07:35 +00:00
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
2025-05-13 11:45:33 +00:00
|
|
|
|
if Engine.is_editor_hint() or not enabled or pass_unhandled_input:
|
|
|
|
|
return
|
2025-05-21 20:16:27 +00:00
|
|
|
|
# if lock_on_player_freezed and SceneManager.is_palyer_freezed_or_locked():
|
2025-06-04 11:46:27 +00:00
|
|
|
|
# return
|
2025-06-26 09:38:51 +00:00
|
|
|
|
if SceneManager.get_lock().is_held():
|
2025-01-20 13:45:47 +00:00
|
|
|
|
return
|
2024-12-26 13:58:37 +00:00
|
|
|
|
if activated:
|
|
|
|
|
if event.is_action_pressed("interact"):
|
2025-06-28 13:42:20 +00:00
|
|
|
|
get_viewport().set_input_as_handled()
|
2025-01-20 13:45:47 +00:00
|
|
|
|
if GlobalConfig.DEBUG:
|
|
|
|
|
print("Sign interacted:", get_parent().name)
|
2024-12-26 13:58:37 +00:00
|
|
|
|
interacted.emit()
|
2025-01-12 11:36:41 +00:00
|
|
|
|
if is_inside_tree():
|
|
|
|
|
# grab focus 放在 emit 后面,避免在 emit 时 prop hud 失去 focus
|
|
|
|
|
# 传送时会导致 is_inside_tree 为 false,此时也无需与 prop hud 抢占 focus
|
|
|
|
|
focus_mode = FOCUS_ALL
|
|
|
|
|
grab_focus()
|
2024-12-26 13:58:37 +00:00
|
|
|
|
elif event.is_action_pressed("cancel"):
|
2025-06-28 13:42:20 +00:00
|
|
|
|
get_viewport().set_input_as_handled()
|
2025-01-20 13:45:47 +00:00
|
|
|
|
if GlobalConfig.DEBUG:
|
|
|
|
|
print("Sign cancel:", get_parent().name)
|
2024-12-26 13:58:37 +00:00
|
|
|
|
cancel.emit()
|
2025-01-12 11:36:41 +00:00
|
|
|
|
release_focus()
|
2025-01-16 12:24:21 +00:00
|
|
|
|
|
|
|
|
|
|
2025-01-13 08:09:57 +00:00
|
|
|
|
var shake_tween
|
|
|
|
|
|
2025-01-16 12:24:21 +00:00
|
|
|
|
|
2025-06-04 11:46:27 +00:00
|
|
|
|
# 使用无效道具,抖动提示;抖动时使用 texture2d 并且变红为 mod
|
2025-06-13 08:03:19 +00:00
|
|
|
|
func invalid_shake(
|
|
|
|
|
normal_texture: Texture2D = null,
|
|
|
|
|
invalid_texture: Texture2D = null,
|
|
|
|
|
mod := Color(1.0, 0.6, 0.6, 1.0)
|
|
|
|
|
):
|
2025-01-13 08:09:57 +00:00
|
|
|
|
if shake_tween:
|
|
|
|
|
shake_tween.kill()
|
|
|
|
|
# 抖动效果,逐渐减弱
|
|
|
|
|
shake_tween = create_tween()
|
|
|
|
|
var fps := 12.0
|
|
|
|
|
var duration := 0.7
|
|
|
|
|
var delta := 4.0
|
|
|
|
|
var origin_pos = Vector2.ZERO
|
|
|
|
|
var count = int(duration * fps)
|
|
|
|
|
var delta_t = 1.0 / fps
|
2025-06-04 11:46:27 +00:00
|
|
|
|
if invalid_texture:
|
|
|
|
|
sprite2d.texture = invalid_texture
|
2025-06-13 08:03:19 +00:00
|
|
|
|
sprite2d.self_modulate = mod
|
2025-01-13 08:09:57 +00:00
|
|
|
|
for i in range(count):
|
|
|
|
|
var offset = Vector2(randf_range(-delta, delta), randf_range(-delta, delta)) * exp(-i)
|
|
|
|
|
shake_tween.tween_property(sprite2d, "position", origin_pos + offset, delta_t).set_trans(
|
|
|
|
|
Tween.TRANS_CUBIC
|
|
|
|
|
)
|
2025-06-04 11:46:27 +00:00
|
|
|
|
shake_tween.tween_callback(_reset_sprite2d.bind(normal_texture))
|
2025-01-13 08:09:57 +00:00
|
|
|
|
|
|
|
|
|
|
2025-06-04 11:46:27 +00:00
|
|
|
|
func _reset_sprite2d(texture2d):
|
2025-06-13 08:03:19 +00:00
|
|
|
|
sprite2d.self_modulate = Color.WHITE
|
2025-06-04 11:46:27 +00:00
|
|
|
|
if texture2d:
|
|
|
|
|
sprite2d.texture = texture2d
|