@tool class_name Interactable2D extends Sprite2D # interacted_times 增加后,再发出信号 signal interacted signal interacted_with_key(key) # 交互 prop 不匹配 signal interact_mismatch_failed signal sign_mark_offset_updated # sign_mark 节点在 ready 时会直接读取 @export var sign_mark_offset := Vector2.ZERO: set(val): sign_mark_offset = val sign_mark_offset_updated.emit(val) @export var enabled := true: set(val): enabled = val if is_node_ready(): _check_sign_display() @export var unrevealed_sign_texture: Texture2D @export var unmatched_sign_texture: Texture2D @export var matched_sign_texture: Texture2D @export var mute_when_interacted := false @export var mute_when_invalid := false @export var one_shot := true @export var one_shot_max_times := 1 @export var disable_prop_after_interacted := false @export var interacted_texture: Texture2D var prop_key := "" var prop_key2 := "" var prop_key3 := "" @onready var sfx_invalid = $SfxInvalid as Sfx @onready var sfx_success = $SfxSuccess as Sfx @onready var sign_mark = %Sign as Sign @onready var area2d = %Area2D as Area2D var ground_archive: GroundArchive # 尝试互动的次数 var tried_times: int: set(val): tried_times = val ground_archive.set_pair(name, "tried_times", val) # 成功互动的次数 var interacted_times: int: set(val): interacted_times = val ground_archive.set_pair(name, "interacted_times", val) static var item_config_res = preload("res://asset/dialogue/item_description.dialogue") func _ready() -> void: if Engine.is_editor_hint(): return area2d.body_entered.connect(_reset) area2d.body_exited.connect(_on_cancel) sign_mark.interacted.connect(_on_interacted) sign_mark.cancel.connect(_on_cancel) sign_mark.enabled = enabled # setup default value ground_archive = ArchiveManager.archive.ground_archive() tried_times = ground_archive.get_value(name, "tried_times", 0) interacted_times = ground_archive.get_value(name, "interacted_times", 0) if interacted_times and interacted_texture: texture = interacted_texture _check_sign_display() func _check_sign_display(): sign_mark.enabled = enabled and (not one_shot or interacted_times < one_shot_max_times) func _reset(_body = null) -> void: _reset_sign_testure_to_prop() var prop_hud = SceneManager.get_prop_hud() as PropHud if not prop_hud.current_item_changed.is_connected(_set_sign_texture_to_prop): prop_hud.current_item_changed.connect(_set_sign_texture_to_prop) func _reset_sign_testure_to_prop(): var key = SceneManager.get_current_prop(false) _set_sign_texture_to_prop(key) # 根据当前 prop,调整 sign 所显示的 texture func _set_sign_texture_to_prop(key): if tried_times == 0: # 首次交互前 unrevealed sign_mark.sprite2d.texture = unrevealed_sign_texture return # 目前默认都显示 matched_sign_texture sign_mark.sprite2d.texture = matched_sign_texture # if is_key_matched(key): # sign_mark.sprite2d.texture = matched_sign_texture # else: # sign_mark.sprite2d.texture = unmatched_sign_texture func _on_cancel(_body = null) -> void: # disconnect signal var prop_hud = SceneManager.get_prop_hud() as PropHud if prop_hud and prop_hud.current_item_changed.is_connected(_set_sign_texture_to_prop): prop_hud.current_item_changed.disconnect(_set_sign_texture_to_prop) var interact_mutex = Mutex.new() func is_key_matched(key) -> bool: return ( # 第一个 prop_key 若空,则表示不需要匹配 not prop_key or ( # 只要有一个 prop_key 匹配即可 (prop_key and key == prop_key) or (prop_key2 and key == prop_key2) or (prop_key3 and key == prop_key3) ) ) func _on_interacted() -> void: interact_mutex.lock() tried_times += 1 if one_shot and interacted_times >= one_shot_max_times: interact_mutex.unlock() return var key = SceneManager.get_current_prop(false) if not is_key_matched(key): if not mute_when_invalid: sfx_invalid.play() sign_mark.invalid_shake(matched_sign_texture, unmatched_sign_texture) # SceneManager.on_toggle_invalid_prop() interact_mutex.unlock() interact_mismatch_failed.emit() return if not mute_when_interacted: sfx_success.play() if disable_prop_after_interacted and key: SceneManager.disable_prop_item(key) interacted_times += 1 if interacted_texture: texture = interacted_texture # interacted_times 增加后,再发出信号 interacted.emit() interacted_with_key.emit(key) EventManager.prop_interacted(name, key, interacted_times) # print("%s interacted with %s. total times: %s" % [name, key, interacted_times]) interact_mutex.unlock() _check_sign_display() _reset_sign_testure_to_prop() func _get_property_list() -> Array[Dictionary]: var items = [] if Engine.is_editor_hint(): var id = item_config_res.titles["PropItems"] var current_line = item_config_res.lines[id] while current_line: if current_line.has("translation_key"): items.append(current_line.translation_key) if not current_line.has("next_id") or current_line.next_id == "end": break current_line = item_config_res.lines[current_line.next_id] return [ { "name": "prop_key", "type": TYPE_STRING, "hint": PROPERTY_HINT_ENUM_SUGGESTION, "hint_string": ",".join(items), }, { "name": "prop_key2", "type": TYPE_STRING, "hint": PROPERTY_HINT_ENUM_SUGGESTION, "hint_string": ",".join(items), }, { "name": "prop_key3", "type": TYPE_STRING, "hint": PROPERTY_HINT_ENUM_SUGGESTION, "hint_string": ",".join(items), } ] func _get(property: StringName) -> Variant: if property == "prop_key": return prop_key elif property == "prop_key2": return prop_key2 elif property == "prop_key3": return prop_key3 return null func _set(property: StringName, value: Variant) -> bool: if property == "prop_key": prop_key = value return true elif property == "prop_key2": prop_key2 = value return true elif property == "prop_key3": prop_key3 = value return true return false