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-01-20 13:45:47 +00:00
|
|
|
|
@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
|
|
|
|
|
_check_sign_display()
|
|
|
|
|
@export var display_sign := true:
|
|
|
|
|
set(val):
|
|
|
|
|
display_sign = val
|
|
|
|
|
_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 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-01-07 11:44:10 +00:00
|
|
|
|
static var _pending_activate_sign := [] as Array[NodePath]
|
|
|
|
|
# 使用互斥锁保证线程安全。在操作 occupied 或 _pending_activate_sign 时需要先锁定,操作完成后解锁
|
2024-12-27 07:56:45 +00:00
|
|
|
|
static var mutex = Mutex.new()
|
|
|
|
|
|
2025-01-20 13:45:47 +00:00
|
|
|
|
var player_touching := false
|
2025-01-13 08:09:57 +00:00
|
|
|
|
var activated = false:
|
|
|
|
|
set(val):
|
|
|
|
|
activated = val
|
2025-01-20 13:45:47 +00:00
|
|
|
|
# queue_redraw()
|
2024-12-27 07:56:45 +00:00
|
|
|
|
# var sprite2d = Sprite2D.new()
|
|
|
|
|
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-01-20 13:45:47 +00:00
|
|
|
|
if Engine.is_editor_hint():
|
|
|
|
|
return
|
2024-12-27 07:56:45 +00:00
|
|
|
|
base_scale = scale
|
|
|
|
|
# layer = GlobalConfig.CANVAS_LAYER_FG
|
2025-01-20 13:45:47 +00:00
|
|
|
|
# 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:
|
|
|
|
|
area2d.body_entered.connect(activate)
|
|
|
|
|
area2d.body_exited.connect(disactivate)
|
2025-01-20 13:45:47 +00:00
|
|
|
|
_check_sign_display()
|
|
|
|
|
visibility_changed.connect(_on_visibility_changed)
|
|
|
|
|
|
2024-12-27 07:56:45 +00:00
|
|
|
|
|
2025-01-20 13:45:47 +00:00
|
|
|
|
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 not enabled or not display_sign or not activated:
|
|
|
|
|
modulate.a = 0.0
|
|
|
|
|
else:
|
|
|
|
|
modulate.a = 1.0
|
2024-12-27 07:56:45 +00:00
|
|
|
|
|
2025-01-20 13:45:47 +00:00
|
|
|
|
|
|
|
|
|
func activate(_body: Node2D) -> bool:
|
2024-12-26 13:58:37 +00:00
|
|
|
|
# point_light.energy = 1.0
|
2025-01-07 11:44:10 +00:00
|
|
|
|
if not is_node_ready():
|
2025-01-20 13:45:47 +00:00
|
|
|
|
return false
|
2025-01-07 11:44:10 +00:00
|
|
|
|
var path := get_path()
|
2024-12-27 07:56:45 +00:00
|
|
|
|
mutex.lock()
|
2025-01-20 13:45:47 +00:00
|
|
|
|
player_touching = true
|
|
|
|
|
if not enabled or not is_visible_in_tree():
|
|
|
|
|
mutex.unlock()
|
|
|
|
|
return false
|
2025-01-07 11:44:10 +00:00
|
|
|
|
if occupied and occupied != path:
|
2025-01-20 13:45:47 +00:00
|
|
|
|
if not _pending_activate_sign.has(path):
|
|
|
|
|
_pending_activate_sign.append(path)
|
2025-01-07 11:44:10 +00:00
|
|
|
|
mutex.unlock()
|
2025-01-20 13:45:47 +00:00
|
|
|
|
return false
|
2024-12-27 07:56:45 +00:00
|
|
|
|
else:
|
2025-01-07 11:44:10 +00:00
|
|
|
|
occupied = path
|
2024-12-27 07:56:45 +00:00
|
|
|
|
activated = true
|
|
|
|
|
mutex.unlock()
|
2025-01-20 13:45:47 +00:00
|
|
|
|
if activated and display_sign:
|
2024-12-27 07:56:45 +00:00
|
|
|
|
var tween = create_tween()
|
|
|
|
|
tween.tween_property(self, "modulate:a", 1.0, 0.2)
|
|
|
|
|
var p_tween = tween.parallel()
|
|
|
|
|
p_tween.tween_property(self, "scale", base_scale * Vector2(1.2, 1.2), 0.3)
|
|
|
|
|
p_tween.tween_property(self, "scale", base_scale, 0.1)
|
2025-01-20 13:45:47 +00:00
|
|
|
|
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
|
2024-12-26 13:58:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func disactivate(_body: Node2D) -> void:
|
2025-01-13 08:09:57 +00:00
|
|
|
|
# release_focus()d
|
2025-01-07 11:44:10 +00:00
|
|
|
|
mutex.lock()
|
2025-01-20 13:45:47 +00:00
|
|
|
|
player_touching = false
|
2025-01-07 11:44:10 +00:00
|
|
|
|
if activated:
|
2025-01-08 00:51:09 +00:00
|
|
|
|
activated = false
|
2025-01-20 13:45:47 +00:00
|
|
|
|
occupied = NodePath("")
|
|
|
|
|
# 转移 active 状态给下一个节点
|
2025-01-07 11:44:10 +00:00
|
|
|
|
while _pending_activate_sign.size() > 0:
|
|
|
|
|
var path = _pending_activate_sign.pop_front()
|
2025-01-20 13:45:47 +00:00
|
|
|
|
var _sign = get_node_or_null(path) as Sign
|
|
|
|
|
if _sign and _sign.player_touching and _sign.activate(null):
|
2024-12-27 13:32:12 +00:00
|
|
|
|
break
|
2025-01-07 11:44:10 +00:00
|
|
|
|
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()
|
2024-12-27 07:56:45 +00:00
|
|
|
|
# point_light.energy = 0.0
|
2025-01-20 13:45:47 +00:00
|
|
|
|
if modulate.a:
|
2024-12-27 07:56:45 +00:00
|
|
|
|
create_tween().tween_property(self, "modulate:a", 0.0, 0.2)
|
2024-12-26 13:58:37 +00:00
|
|
|
|
|
|
|
|
|
|
2025-01-03 08:07:35 +00:00
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
2025-01-21 10:52:36 +00:00
|
|
|
|
if Engine.is_editor_hint() or not enabled:
|
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-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()
|
2025-01-16 12:24:21 +00:00
|
|
|
|
_set_handled()
|
2024-12-26 13:58:37 +00:00
|
|
|
|
elif event.is_action_pressed("cancel"):
|
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-16 12:24:21 +00:00
|
|
|
|
_set_handled()
|
2025-01-12 11:36:41 +00:00
|
|
|
|
release_focus()
|
2025-01-16 12:24:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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-01-16 12:24:21 +00:00
|
|
|
|
|
2025-01-13 08:09:57 +00:00
|
|
|
|
# 使用无效道具,抖动提示
|
|
|
|
|
func invalid_shake():
|
|
|
|
|
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
|
|
|
|
|
sprite2d.modulate = Color(1.0, 0.6, 0.6, 1.0)
|
|
|
|
|
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_modulate)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _reset_modulate():
|
|
|
|
|
sprite2d.modulate = Color(1.0, 1.0, 1.0, 1.0)
|