xiandie/scene/entity/ux/sign.gd

275 lines
7.4 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@tool
class_name Sign extends Control
signal interacted
signal cancel
signal toggle_active(activated: bool)
@export var sign_mark_offset := Vector2.ZERO:
set(val):
sign_mark_offset = val
if is_node_ready():
texture_container.position = val
# @export var lock_on_player_freezed := false
@export var enabled := true:
set(val):
if enabled != val:
enabled = val
align_activation()
@export var display_sign := true:
set(val):
if display_sign != val:
display_sign = val
if is_node_ready():
_check_sign_display()
# @export var draw_shadow := false
@onready var texture_container = %TextureContainer as Container
@onready var sprite2d = %Sprite2D as Sprite2D
# 跳过输入事件
var pass_unhandled_input = false
# var shadow_texture = preload("res://asset/art/ui/action_mark/探索空心ui.png") as Texture2D
# 同时只能有一个物品被激活交互态,其他物品进入等待队列
static var occupied: NodePath
# touching 状态改变时就要擦除
static var _touching_sign_arr: Array[NodePath] = []
# 使用互斥锁保证线程安全。在操作 occupied 或 _touching_sign_arr 时需要先锁定,操作完成后解锁
static var mutex = Mutex.new()
var tween: Tween
var player_touching := false:
set(val):
if player_touching != val:
player_touching = val
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()
var activated = false:
set(val):
if activated != val:
activated = val
_check_sign_display()
toggle_active.emit(activated)
var base_scale = Vector2.ONE
func _ready() -> void:
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()
if Engine.is_editor_hint():
return
# var point_light = get_node_or_null("../PointLight2D")
# if point_light:
# point_light.energy = 0.0
var area2d = get_node_or_null("../Area2D")
if area2d:
area2d.body_entered.connect(_on_body_entered)
area2d.body_exited.connect(_on_body_exited)
visibility_changed.connect(_on_visibility_changed)
# 关注 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):
align_activation()
func _on_visibility_changed() -> void:
align_activation()
# activition/display mode 变化时 _check_sign_display
func _check_sign_display():
if Engine.is_editor_hint():
# 在编辑器中与 display_sign & enabled 状态保持一致
if display_sign and enabled:
sprite2d.modulate.a = 1.0
else:
sprite2d.modulate.a = 0.0
return
if not display_sign or not activated:
sprite2d.modulate.a = 0.0
elif sprite2d.modulate.a == 0.0:
sprite2d.modulate.a = 1.0
func _on_body_entered(_body: Node2D) -> void:
player_touching = true
func _on_body_exited(_body: Node2D) -> void:
player_touching = false
# 四种参数变化时调用 align: 1 enable 2 hold 3 visibility 4 touching
func align_activation() -> bool:
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),
"[/color] touching:[color=coral]",
_get_name_from_paths(_touching_sign_arr),
"[/color]"
)
return activated
func _try_disactivate() -> void:
mutex.lock()
if activated:
activated = false
occupied = NodePath(&"")
# 转移 active 状态给下一个节点
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():
break
mutex.unlock()
# point_light.energy = 0.0
if tween and tween.is_valid():
tween.kill()
if sprite2d.modulate.a > 0.0:
tween = create_tween()
tween.tween_property(sprite2d, "modulate:a", 0.0, 0.2)
func _try_activate() -> bool:
# point_light.energy = 1.0
var path := get_path()
mutex.lock()
# 若 lock 为 hold 状态则直接跳过
if not occupied or occupied == path:
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)
func _unhandled_input(event: InputEvent) -> void:
if Engine.is_editor_hint() or not enabled or pass_unhandled_input:
return
# if lock_on_player_freezed and SceneManager.is_palyer_freezed_or_locked():
# return
if SceneManager.get_lock().is_held():
return
if activated:
if event.is_action_pressed("interact"):
get_viewport().set_input_as_handled()
if GlobalConfig.DEBUG:
print("Sign interacted:", get_parent().name)
interacted.emit()
if is_inside_tree():
# grab focus 放在 emit 后面,避免在 emit 时 prop hud 失去 focus
# 传送时会导致 is_inside_tree 为 false此时也无需与 prop hud 抢占 focus
focus_mode = FOCUS_ALL
grab_focus()
elif event.is_action_pressed("cancel"):
get_viewport().set_input_as_handled()
if GlobalConfig.DEBUG:
print("Sign cancel:", get_parent().name)
cancel.emit()
release_focus()
var shake_tween
# 使用无效道具,抖动提示;抖动时使用 texture2d 并且变红为 mod
func invalid_shake(
normal_texture: Texture2D = null,
invalid_texture: Texture2D = null,
mod := Color(1.0, 0.6, 0.6, 1.0)
):
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
if invalid_texture:
sprite2d.texture = invalid_texture
sprite2d.self_modulate = mod
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
)
shake_tween.tween_callback(_reset_sprite2d.bind(normal_texture))
func _reset_sprite2d(texture2d):
sprite2d.self_modulate = Color.WHITE
if texture2d:
sprite2d.texture = texture2d