@tool class_name Ambush2D extends Node2D signal triggered @export var enabled := true: set(val): enabled = val if is_node_ready(): sign_mark.enabled = val _check_sign_display() @export_enum("enter", "interact") var trigger_mode := "enter": set(val): trigger_mode = val if is_node_ready(): _check_sign_display() @export var one_shot := true # 首次进入 tree 就直接启用 @export var on_first_enter_tree := false @export var freeze_time := 5.0 var hook_animation = "" @export var lock_player_on_playing_dialogue = true @export_enum("c01", "c02") var hook_dialogue_res = "c01": set(val): hook_dialogue_res = val match val: "c01": dialogue_res = dialogue_c01 "c02": dialogue_res = dialogue_c02 if is_node_ready() and Engine.is_editor_hint(): notify_property_list_changed() var hook_dialogue_title = "" var dialogue_c01 = preload("res://asset/dialogue/c01.dialogue") var dialogue_c02 = preload("res://asset/dialogue/c02.dialogue") var dialogue_res = dialogue_c01 var played_time := 0.0 # var played := false: # set(val): # if played != val and ground_archive: # ground_archive.set_pair(name, "played", played) # played = val @onready var sign_mark := %Sign as Sign @onready var area := %Area2D as Area2D var ground_archive: GroundArchive var played: bool: set(val): played = val ground_archive.set_pair(name, "played", played) # Called when the node enters the scene tree for the first time. func _ready() -> void: if Engine.is_editor_hint(): var animation_player = _get_animation_player() # 更新 hook_animation 的可选项 if animation_player: animation_player.animation_libraries_updated.connect(notify_property_list_changed) return _check_sign_display() if one_shot and played: if GlobalConfig.DEBUG: print("Ambush already played, name=", name) return if on_first_enter_tree: _entered(null) sign_mark.interacted.connect(_interacted) area.body_entered.connect(_entered) sign_mark.enabled = enabled # setup default value ground_archive = ArchiveManager.archive.ground_archive() played = ground_archive.get_value(name, "played", false) func _check_sign_display(): sign_mark.display_sign = trigger_mode == "interact" and (not one_shot or not played) func _get_animation_player() -> AnimationPlayer: return get_node_or_null("../../AnimationPlayer") as AnimationPlayer var enter_mutex = Mutex.new() func _interacted(): if enabled and trigger_mode == "interact": _do_trigger() func _entered(_body = null): if enabled and trigger_mode == "enter": _do_trigger() func _do_trigger(): var time = Time.get_ticks_msec() # 确保只有一个线程进入该逻辑,因为有时 player 碰撞和首次进入 tree 都会触发该方法 if not enter_mutex.try_lock(): return if not one_shot and freeze_time > 0: var time_left = freeze_time - (time - played_time) * 0.001 if time_left > 0: if GlobalConfig.DEBUG: print("Ambush freeze time not reached, time left=", time_left) enter_mutex.unlock() return if one_shot and played: enter_mutex.unlock() return played_time = time played = true # hook_animation if hook_animation: var animation_player = _get_animation_player() if animation_player: animation_player.play(hook_animation) # hook_dialogue if hook_dialogue_title: if lock_player_on_playing_dialogue: SceneManager.freeze_player(0.0) DialogueManager.show_dialogue_balloon(dialogue_res, hook_dialogue_title) DialogueManager.dialogue_ended.connect(_on_dialogue_ended, CONNECT_ONE_SHOT) triggered.emit() if GlobalConfig.DEBUG: print("ambush body_entered!") enter_mutex.unlock() _check_sign_display() func _on_dialogue_ended(_res): if GlobalConfig.DEBUG: print("Ambush dialogue ended") if lock_player_on_playing_dialogue: SceneManager.release_player() func _get(property: StringName) -> Variant: if property == "hook_dialogue_title": return hook_dialogue_title elif property == "hook_animation": return hook_animation return null func _set(property: StringName, value: Variant) -> bool: if property == "hook_dialogue_title": hook_dialogue_title = value return true elif property == "hook_animation": hook_animation = value return true return false func _get_property_list() -> Array[Dictionary]: var animation_list: PackedStringArray var animation_player = _get_animation_player() if animation_player: animation_list = animation_player.get_animation_list() return [ { "name": "hook_dialogue_title", "type": TYPE_STRING, "hint": PROPERTY_HINT_ENUM_SUGGESTION, "hint_string": ",".join(dialogue_res.get_ordered_titles()) }, { "name": "hook_animation", "type": TYPE_STRING, "hint": PROPERTY_HINT_ENUM_SUGGESTION, "hint_string": ",".join(animation_list) } ]