xiandie/scene/prop/prop_hud.gd

347 lines
9.7 KiB
GDScript

@tool
class_name PropHud extends Control
signal current_item_changed(prop_key: String)
@export_group("DebugItem")
@export var enable_item := false:
set(value):
if value:
enable_item = false
enable_prop_item(item_key)
@export var item_key: String
@export_group("Inventory")
@export var inventory: PropInventory:
set(value):
inventory = value
if inventory and not inventory.current_item_changed.is_connected(current_item_changed.emit):
inventory.current_item_changed.connect(current_item_changed.emit)
@export_group("UI-UX")
@export var display_time := 2.5 # 不包含渐入渐出(约 0.6s)的时长
@export var locked := false:
set(value):
locked = value
if value:
selected = false
@export var selected := false:
set(value):
selected = value
# if is_node_ready():
# %Mark.visible = value
# mark.modulate.a = 1.0
@onready var sfx_click = %SfxClick as Sfx
@onready var left_btn = %LeftButton as TextureButton
@onready var right_btn = %RightButton as TextureButton
@onready var panel = %HudPanel as TextureButton
@onready var prop = %Prop as TextureRect
@onready var prop_container = %PropContainer as Container
@onready var mark = %Mark as TextureRect
@onready var title_label = %TitleLabel as Label
var items_dict := {}
# 从配置文件加载 prop items
var item_config_res = preload("res://asset/dialogue/item_description.dialogue")
var path_prefix = "res://asset/art/prop/"
var cached_inventory_textures := {}
var listen_mouse = false
var displaying = false
var timer := Timer.new()
var display_tween: Tween
var container_tween: Tween
func _ready() -> void:
if Engine.is_editor_hint():
return
_load_items()
_load_from_archive()
focus_exited.connect(_on_focus_exited)
# 存档更新时,从存档加载 prop
ArchiveManager.archive_loaded.connect(_load_from_archive)
# tween timer
timer.wait_time = display_time
timer.one_shot = true
timer.autostart = false
timer.timeout.connect(_on_timer_timeout)
add_child(timer)
# connect signals
left_btn.pressed.connect(on_left_pressed)
right_btn.pressed.connect(on_right_pressed)
panel.pressed.connect(_on_panel_pressed)
# %Mark.visible = selected
# mark.modulate.a = 0.8
title_label.modulate.a = 0.0
# _toggle_btn_ability(false)
left_btn.modulate.a = 0.5
right_btn.modulate.a = 0.5
mouse_entered.connect(_on_mouse_entered)
mouse_exited.connect(_on_mouse_exited)
func _on_focus_exited() -> void:
if selected:
selected = false
func _load_items():
var id = item_config_res.titles["PropItems"]
var current_line = item_config_res.lines[id]
while current_line:
if current_line.has("tags") and current_line.has("translation_key"):
var wrapped_texture := "texture="
var wrapped_inspect := "inspect="
var texture_path = ""
var inspect_path = ""
for t in current_line.tags:
if t.begins_with(wrapped_texture):
texture_path = t.replace(wrapped_texture, "").strip_edges()
elif t.begins_with(wrapped_inspect):
inspect_path = t.replace(wrapped_inspect, "").strip_edges()
var item = PropItem.new()
item.key = current_line.translation_key
if texture_path:
item.texture_path = path_prefix + texture_path
if inspect_path:
item.inspect_path = path_prefix + inspect_path
items_dict[item.key] = item
if not current_line.has("next_id") or current_line.next_id == "end":
break
current_line = item_config_res.lines[current_line.next_id]
func _load_from_archive() -> void:
if ArchiveManager.archive:
inventory = ArchiveManager.archive.prop_inventory
_load_texture_cache()
_update_prop_display_with_texture()
func _load_texture_cache() -> void:
if not inventory:
return
for k in cached_inventory_textures:
# remove invalid textures
if not k in inventory.enabled_items:
cached_inventory_textures.erase(k)
for key in inventory.enabled_items:
if cached_inventory_textures.has(key):
continue
# 以 items_dict 为准,如果 enabled_items 中的 key 不存在,则删除
if not key in items_dict:
inventory.enabled_items.erase(key)
continue
var path = items_dict[key].texture_path
if not path:
continue
var texture = load(path) as Texture2D
if texture:
if GlobalConfig.DEBUG:
print("Cache load prop texture:", path)
cached_inventory_textures[key] = texture
# wrap index
inventory.current_index = wrapi(inventory.current_index, 0, inventory.enabled_items.size())
func _update_prop_display_with_texture():
if not inventory:
return
if inventory.enabled_items.size() == 0:
prop.texture = null
return
var item = items_dict[inventory.current_item_key()]
if not item:
prop.texture = null
push_error("PropItem is null! index=" + str(inventory.current_index))
return
if item.key in cached_inventory_textures:
prop.texture = cached_inventory_textures[item.key]
else:
prop.texture = null
title_label.text = tr(item.key)
func on_left_pressed() -> void:
if locked:
return
sfx_click.play()
_mouse_moved_on_listening()
if inventory.index_wrap_add(-1):
selected = false
_update_prop_display_with_texture()
_tween_container(true)
func on_right_pressed() -> void:
if locked:
return
sfx_click.play()
_mouse_moved_on_listening()
if inventory.index_wrap_add(1):
selected = false
_update_prop_display_with_texture()
_tween_container(false)
func _tween_container(left_to_right := true) -> void:
if container_tween and container_tween.is_running():
container_tween.kill()
container_tween = create_tween()
if left_to_right:
container_tween.tween_property(prop_container, "offset_left", 0.0, 0.3).from(-200.0)
prop_container.offset_right = 0.0
else:
container_tween.tween_property(prop_container, "offset_right", 0.0, 0.3).from(200.0)
prop_container.offset_left = 0.0
func _on_panel_pressed() -> void:
if locked:
return
sfx_click.play()
if not selected:
focus_mode = FOCUS_ALL
grab_focus()
selected = true
_mouse_moved_on_listening()
func _on_mouse_entered() -> void:
if locked:
return
listen_mouse = true
_mouse_moved_on_listening()
func _on_mouse_exited() -> void:
listen_mouse = false
func _mouse_moved_on_listening() -> void:
if not displaying:
toggle_details(true)
return
timer.start(display_time)
func _on_timer_timeout() -> void:
toggle_details(false)
func _unhandled_input(event: InputEvent) -> void:
if locked:
return
if event.is_action_pressed("prop_left"):
on_left_pressed()
get_viewport().set_input_as_handled()
elif event.is_action_pressed("prop_right"):
on_right_pressed()
get_viewport().set_input_as_handled()
elif event.is_action_pressed("prop_select"):
_on_panel_pressed()
get_viewport().set_input_as_handled()
func _input(event: InputEvent) -> void:
if locked:
return
if listen_mouse and (event is InputEventMouseMotion or event is InputEventScreenTouch):
_mouse_moved_on_listening()
func toggle_details(display := true) -> void:
if display_tween and display_tween.is_running():
display_tween.kill()
display_tween = create_tween()
if display:
displaying = true
_toggle_btn_ability(true)
display_tween.parallel().tween_property(title_label, "modulate:a", 1.0, 0.3)
display_tween.parallel().tween_property(left_btn, "modulate:a", 1.0, 0.3)
display_tween.parallel().tween_property(right_btn, "modulate:a", 1.0, 0.3)
# display_tween.parallel().tween_property(mark, "modulate:a", 1.0, 0.3)
# display_tween.parallel().tween_property(mark, "scale", Vector2(1.1, 1.1), 0.3).set_trans(
# Tween.TRANS_CUBIC
# )
# display_tween.tween_property(mark, "scale", Vector2.ONE, 0.2).set_trans(Tween.TRANS_CUBIC)
timer.start(display_time)
else:
displaying = false
# display_tween.tween_property(mark, "modulate:a", 0.8, 0.6)
display_tween.parallel().tween_property(title_label, "modulate:a", 0.0, 0.6)
display_tween.parallel().tween_property(left_btn, "modulate:a", 0.5, 0.5)
display_tween.parallel().tween_property(right_btn, "modulate:a", 0.5, 0.5)
# display_tween.tween_callback(_toggle_btn_ability.bind(false))
timer.stop()
func _toggle_btn_ability(v: bool) -> void:
left_btn.disabled = !v
right_btn.disabled = !v
func enable_prop_item(prop_key: String) -> void:
if not inventory or not prop_key:
return
if not items_dict.has(prop_key):
push_error("PropItem not found! key=" + prop_key)
return
inventory.enable_item(prop_key)
_load_texture_cache()
_update_prop_display_with_texture()
if GlobalConfig.DEBUG:
print("PropHUD Enable prop item:", prop_key)
var inspector = SceneManager.get_inspector()
if inspector:
var inspect_path = items_dict[prop_key].inspect_path
var prop_title = tr(prop_key)
if inspect_path:
var texture = load(inspect_path) as Texture2D
inspector.pop_prop_inspection(prop_title, texture)
else:
var texture = cached_inventory_textures[prop_key]
inspector.pop_prop_inspection(prop_title, texture, true)
func disable_prop_item(prop_key: String) -> void:
if not inventory or not prop_key:
return
inventory.disable_item(prop_key)
_load_texture_cache()
_update_prop_display_with_texture()
# save to archive immediately
ArchiveManager.save_all()
var shake_tween
# 使用无效道具,抖动提示
func on_toggle_invalid_prop():
if GlobalConfig.DEBUG:
print("使用无效道具. current prop:", inventory.current_item_key())
if not inventory or not inventory.current_item_key():
return
if shake_tween:
shake_tween.kill()
# 抖动效果,逐渐减弱
shake_tween = create_tween()
var fps := 15.0
var duration := 0.8
var delta := 12.0
var origin_pos = Vector2.ZERO
var count = int(duration * fps)
var delta_t = 1.0 / fps
prop.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(prop, "position", origin_pos + offset, delta_t).set_trans(
Tween.TRANS_CUBIC
)
shake_tween.tween_callback(_reset_prop_modulate)
func _reset_prop_modulate():
prop.modulate = Color(1.0, 1.0, 1.0, 1.0)