xiandie/scene/entity/ux/sign.gd

254 lines
7.2 KiB
GDScript3
Raw Normal View History

@tool
2025-01-13 08:09:57 +00:00
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
2025-05-21 20:16:27 +00:00
# @export var lock_on_player_freezed := false
@export var enabled := true:
set(val):
if enabled == val:
return
enabled = val
if enabled and player_touching and not activated:
activate(null)
if not enabled and activated:
disactivate(null)
# 保持 player_touching 为 true避免在 enable 时无法激活
player_touching = true
if is_node_ready():
_check_sign_display()
@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
2025-01-13 08:09:57 +00:00
@onready var texture_container = %TextureContainer as Container
@onready var sprite2d = %Sprite2D as Sprite2D
# 跳过输入事件
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
# 同时只能有一个物品被激活交互态,其他物品进入等待队列
static var occupied: NodePath
static var _pending_activate_sign: Array[NodePath] = []
# 使用互斥锁保证线程安全。在操作 occupied 或 _pending_activate_sign 时需要先锁定,操作完成后解锁
static var mutex = Mutex.new()
var tween: Tween
var player_touching := false
2025-01-13 08:09:57 +00:00
var activated = false:
set(val):
if activated != val:
activated = val
toggle_active.emit(activated)
# queue_redraw()
# var sprite2d = Sprite2D.new()
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
# layer = GlobalConfig.CANVAS_LAYER_FG
# 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(activate)
area2d.body_exited.connect(disactivate)
visibility_changed.connect(_on_visibility_changed)
func _on_visibility_changed() -> void:
if is_visible_in_tree():
if player_touching and not activated and enabled:
activate(null)
else:
if activated:
disactivate(null)
player_touching = true
func _check_sign_display():
if Engine.is_editor_hint():
# 在编辑器中始终显示
if display_sign:
sprite2d.modulate.a = 1.0
else:
sprite2d.modulate.a = 0.0
return
if not enabled or not display_sign or not activated:
sprite2d.modulate.a = 0.0
elif sprite2d.modulate.a == 0.0 and activated:
sprite2d.modulate.a = 1.0
func activate(_body: Node2D) -> bool:
# point_light.energy = 1.0
# print("activate:", get_path())
var path := get_path()
mutex.lock()
player_touching = true
if not enabled or not is_visible_in_tree():
mutex.unlock()
return false
if occupied and occupied != path:
if not _pending_activate_sign.has(path):
_pending_activate_sign.append(path)
mutex.unlock()
return false
else:
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 true
2025-01-08 00:51:09 +00:00
# if activated:
2025-01-13 08:09:57 +00:00
# focus_mode = FOCUS_ALL
# grab_focus()
# 绘制 prop 图标
# func _draw() -> void:
# if draw_shadow and sprite2d.texture:
# var texture_x = sprite2d.texture.get_size().x
# # var radius = max(36.0, min(texture_x * 0.5, 48.0))
# var radius = 46.0
# var rect = Rect2(-radius, -radius, radius * 2, radius * 2)
# draw_texture_rect(shadow_texture, rect, false)
# # 比 2.0 略小,选择使用 1.6
# var texture_scale = min(radius * 1.6 / texture_x, 1.1)
# sprite2d.scale = Vector2(texture_scale, texture_scale)
# # var texture_radius = shadow_texture.x * 0.5
# # draw_circle(Vector2.ZERO, radius, Color(0.8, 0.8, 0.8, 0.8), false, 6.0, true)
# # radius -= 6.0
# # if radius > 0:
# # draw_circle(Vector2.ZERO, radius, Color(0.3, 0.3, 0.3, 0.3))
# else:
# sprite2d.scale = Vector2.ONE
func disactivate(_body: Node2D) -> void:
# release_focus()
mutex.lock()
player_touching = false
if activated:
2025-01-08 00:51:09 +00:00
activated = false
occupied = NodePath("")
# 转移 active 状态给下一个节点
while _pending_activate_sign.size() > 0:
var path = _pending_activate_sign.pop_front()
var _sign = get_node_or_null(path) as Sign
if _sign and _sign.player_touching and _sign.activate(null):
break
else:
# make sure the sign is not in the pending list
_pending_activate_sign.erase(get_path())
# double check. because the sign may be activated by other body
if activated:
disactivate(_body)
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 _unhandled_input(event: InputEvent) -> void:
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
var player = SceneManager.get_player()
if not player or player.action_locked:
return
if activated:
if event.is_action_pressed("interact"):
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()
_set_handled()
elif event.is_action_pressed("cancel"):
if GlobalConfig.DEBUG:
print("Sign cancel:", get_parent().name)
cancel.emit()
_set_handled()
release_focus()
func _set_handled():
var viewport = get_viewport()
if viewport:
viewport.set_input_as_handled()
2025-01-13 08:09:57 +00:00
var shake_tween
2025-06-04 11:46:27 +00:00
# 使用无效道具,抖动提示;抖动时使用 texture2d 并且变红为 mod
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
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):
sprite2d.self_modulate = Color.WHITE
2025-06-04 11:46:27 +00:00
if texture2d:
sprite2d.texture = texture2d