xiandie/scene/entity/ux/sign.gd

254 lines
7.2 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:
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
@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
static var _pending_activate_sign: Array[NodePath] = []
# 使用互斥锁保证线程安全。在操作 occupied 或 _pending_activate_sign 时需要先锁定,操作完成后解锁
static var mutex = Mutex.new()
var tween: Tween
var player_touching := false
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
# if activated:
# 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:
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
# if lock_on_player_freezed and SceneManager.is_palyer_freezed_or_locked():
# 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()
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