@tool class_name AnimationRoot extends AnimationPlayer # 在继承 AnimationRoot 的各场景内的脚本中,可以直接调用 DialogueResource var dialogue_c01 := preload("res://asset/dialogue/c01.dialogue") as DialogueResource var dialogue_c02 := preload("res://asset/dialogue/c02.dialogue") as DialogueResource var dialogue_c03 := preload("res://asset/dialogue/c03.dialogue") as DialogueResource var dialogue_c04 := preload("res://asset/dialogue/c04.dialogue") as DialogueResource var dialogue_c05 := preload("res://asset/dialogue/c05.dialogue") as DialogueResource var dialogue_c06 := preload("res://asset/dialogue/c06.dialogue") as DialogueResource @export var data = { # 首次进入场景时触发 "oneshot_animation_played": false } @export_tool_button("reset 存档") var reset_archive = _reset_archive # event 也混合其中 @export var debug_global_data: Dictionary[String, Variant] = { "enabled_items": {}, "player_x": 30.0, } @export var debug_ground_data: Dictionary[String, Variant] = {} @export var auto_reset_on_debug_restarting := false @export_tool_button("auto reference") var auto_ref = _auto_setup_node_reference var oneshot_animation := "" var ground_archive: GroundArchive var ground: Ground2D # 继承覆盖该方法 func _default_data() -> Dictionary: print("read default data from root") return {} func _ready() -> void: ground = get_node("..") if not ground: printerr("ground not found") return if ground.restarting: print("auto reset archive=", auto_reset_on_debug_restarting) if auto_reset_on_debug_restarting: _reset_archive() print("restarting: skip animation root _ready()") return data.merge(_default_data(), true) if Engine.is_editor_hint(): # notify_property_list_changed() # 更新 oneshot_animation 的可选项 animation_libraries_updated.connect(notify_property_list_changed) return ground_archive = ArchiveManager.archive.ground_archive() as GroundArchive var archive_data = ground_archive.get_data(name) # merge data for key in archive_data.keys(): if data.has(key): data[key] = archive_data[key] # 等待 DeployLayer 先加载完成 if not ground.is_node_ready(): ground.ready.connect(_setup_node_reference) ground.ready.connect(_on_ground_ready) else: _setup_node_reference() _on_ground_ready() ready.connect(_on_ready) func _setup_node_reference() -> void: pass func _on_ground_ready() -> void: pass func _on_ready() -> void: if Engine.is_editor_hint(): return # 仅在首次进入场景时触发 if oneshot_animation: if not data["oneshot_animation_played"]: play(oneshot_animation) animation_finished.connect(_oneshot_animation_finished, CONNECT_ONE_SHOT) else: if GlobalConfig.DEBUG: print("oneshot_animation_played:", oneshot_animation) func _oneshot_animation_finished(animation_name) -> void: if GlobalConfig.DEBUG: print("oneshot_animation_finished:", animation_name) set_data("oneshot_animation_played", true) func set_data(property: StringName, value: Variant) -> bool: if data.has(property): ground_archive.set_pair(name, property, value) data[property] = value return true return false func set_global_entry(property: StringName, value: Variant) -> void: ArchiveManager.set_global_entry(property, value) func get_global_value(property: StringName, default_value = null) -> Variant: var val = ArchiveManager.get_global_value(property) if val == null: return default_value return val func _get(property: StringName) -> Variant: if property == "oneshot_animation": return oneshot_animation return null func _set(property: StringName, value: Variant) -> bool: if property == "oneshot_animation": oneshot_animation = value return true return false func _get_property_list() -> Array[Dictionary]: return [ { "name": "oneshot_animation", "type": TYPE_STRING, "hint": PROPERTY_HINT_ENUM_SUGGESTION, "hint_string": ",".join(get_animation_list()), } ] ###### TOOL BUTTON func _reset_archive() -> void: ground = get_tree().edited_scene_root.get_node("Ground") if not ground: printerr("ground not found") return var archive = ( ResourceLoader.load("user://data/archives/save000.tres", "AssembledArchive") as AssembledArchive ) # 从 code 中找到 set_global_entry/get_global_value 方法中第一个 property var code = get_script().source_code # set_global_entry(property: StringName, value) # get_global_value(property: StringName, default = null) var setter_regx = RegEx.create_from_string(r'set_global_entry\(.?"(.+)"') as RegEx var getter_regx = RegEx.create_from_string(r'get_global_value\(.?"(.+)"') as RegEx var properties = {} for setter_match in setter_regx.search_all(code): var key = setter_match.get_string(1) if not properties.has(key): properties[key] = false print("Match global_data_setter: " + key) for getter_match in getter_regx.search_all(code): var key = getter_match.get_string(1) if not properties.has(key): properties[key] = false print("Match global_data_getter: " + key) for p in properties.keys(): if debug_global_data.get(p) == null: debug_global_data[p] = false # archive.set_global_entry(p, new_data[p]) archive.global_data_dict[p] = debug_global_data[p] # 重置 ground_archive if not archive.ground_archives.has(ground.scene_name): archive.ground_archives[ground.scene_name] = GroundArchive.new() archive.ground_archives[ground.scene_name].scene_name = ground.scene_name # setup _setup_ground_data(debug_ground_data, ground) archive.ground_archives[ground.scene_name].data = debug_ground_data # 重置 event 状态 # EventManager.set_stage(&"xxx", 2) # EventManager.set_stage_if_greater(&"xxx", 5) var event_setter_regx = RegEx.create_from_string(r'EventManager.set_stage\(.?"(.+)"') as RegEx var event_getter_regx = RegEx.create_from_string(r'EventManager.get_stage\(.?"(.+)"') as RegEx var event_set_greater_regx = ( RegEx.create_from_string(r'set_stage_if_greater\(.?"(.+)"') as RegEx ) var events = {} for event_match in event_setter_regx.search_all(code): var key = event_match.get_string(1) if not events.has(key): events[key] = false print("Match event_setter: " + key) for event_match in event_set_greater_regx.search_all(code): var key = event_match.get_string(1) if not events.has(key): events[key] = false print("Match event_greate_setter: " + key) for event_match in event_getter_regx.search_all(code): var key = event_match.get_string(1) if not events.has(key): events[key] = false print("Match event_getter: " + key) # 遍历 ".." 下所有节点,找到属于 Event2D 的节点 _find_event(events, ground) for e in events: if debug_global_data.get(e) == null: debug_global_data[e] = 0 archive.event_stage[e] = debug_global_data[e] # 重置 props 状态 var props = debug_global_data.get("enabled_items") if props == null or not props is Dictionary: props = {} debug_global_data["enabled_items"] = props var prop_disabler_regx = RegEx.create_from_string(r'SceneManager.disable_prop_item\(.?"(.+)"') as RegEx var prop_enabler_regx = RegEx.create_from_string(r'EventMSceneManageranager.enable_prop_item\(.?"(.+)"') as RegEx for p_match in prop_disabler_regx.search_all(code): var key = p_match.get_string(1) if not props.has(key): props[key] = true print("Match global_data_setter: " + key) for p_match in prop_enabler_regx.search_all(code): var key = p_match.get_string(1) if not props.has(key): props[key] = false print("Match global_data_getter: " + key) _find_props(props, ground) var prop_arr = archive.prop_inventory.default_enabled_items if get_node("../MainPlayer").character.begins_with("吕萍"): prop_arr = archive.prop_inventory.xdie_enabled_items elif get_node("../MainPlayer").character.begins_with("小小蝶"): prop_arr = archive.prop_inventory.xxdie_enabled_items elif get_node("../MainPlayer").character.begins_with("小小小蝶"): prop_arr = archive.prop_inventory.xxxdie_enabled_items for prop in props: if props[prop] and not prop_arr.has(prop): prop_arr.append(prop) if not props[prop] and prop_arr.has(prop): prop_arr.erase(prop) # 重置其他全局变量 archive.player_global_position_x = debug_global_data.get_or_add("player_x", 30.0) print("reset archive data success") ResourceSaver.save(archive) notify_property_list_changed() func _setup_ground_data(g_data: Dictionary, node: Node): if not node: return for child in node.get_children(): if child is Ambush2D and not g_data.has(child.name): print("Find Ambush2D: " + child.name) g_data[child.name] = {"played": false} elif child is Interactable2D and not g_data.has(child.name): g_data[child.name] = {"interacted_times": 0} print("Find Interactable2D: " + child.name) elif child is Pickable2D and not g_data.has(child.name): g_data[child.name] = {"picked": false} print("Find Pickable2D: " + child.name) elif child is AnimationRoot and not g_data.has(child.name): g_data[child.name] = child.data print("Find AnimationRoot: " + child.name) _setup_ground_data(g_data, child) func _find_event(events: Dictionary, node: Node) -> void: if not node: return for child in node.get_children(): if child is Event2D: print("Find Event2D: " + child.name) if child.event != &"": events[child.event] = true print("Find event: " + child.event) if child.pre_event != &"": events[child.pre_event] = true print("Find pre_event: " + child.pre_event) elif child is EventBinder: print("Find EventBinder...") if child.trigger_event != &"": events[child.trigger_event] = true print("Find trigger_event: " + child.trigger_event) elif child.updater_event != &"": events[child.updater_event] = true print("Find updater_event: " + child.updater_event) _find_event(events, child) func _find_props(props: Dictionary, node: Node) -> void: if not node: return for child in node.get_children(): if child is Interactable2D: if child.prop_key != &"": props[child.prop_key] = true print("Find Interactable2D prop1: " + child.prop_key) elif child.prop_key2 != &"": props[child.prop_key2] = true print("Find Interactable2D prop2: " + child.prop_key2) elif child.prop_key3 != &"": props[child.prop_key3] = true print("Find Interactable2D prop3: " + child.prop_key3) elif child is Pickable2D: if child.prop_key != &"": props[child.prop_key] = true print("Find Pickable2D prop: " + child.prop_key) _find_props(props, child) var func_line_id := -1 var region_start_id := -1 var region_end_id := -1 # #region node_reference # #endregion # # 读取设置变量名 # func _setup_node_reference() -> void: # pass func _auto_setup_node_reference(): var script = get_script() as GDScript if not script: printerr("script not found") return var code_lines := script.source_code.split("\n") as PackedStringArray func_line_id = -1 region_start_id = -1 region_end_id = -1 for i in len(code_lines): var line = code_lines[i] if line.begins_with("func _setup_node_reference()"): func_line_id = i elif line.begins_with("#region node_reference"): region_start_id = i elif region_start_id != -1 and region_end_id == -1 and line.begins_with("#endregion"): region_end_id = i if func_line_id == -1: printerr("func _setup_node_reference() not found") return if region_start_id == -1: print("#region node_reference not found, creat region upon func_line..") region_start_id = func_line_id - 2 func_line_id += 1 code_lines.insert(region_start_id, "#region node_reference") if region_end_id == -1: region_end_id = region_start_id + 1 code_lines.insert(region_end_id, "#endregion") func_line_id += 1 print("auto reference start, region_start_id=", region_start_id, " region_end_id=", region_end_id) var existing_vars = _read_existing_vars(code_lines) var created_vars = [] _traverse_nodes(get_node(".."), get_node(".."), existing_vars, code_lines, created_vars) script.source_code = "\n".join(code_lines) ResourceSaver.save(script) print("auto reference done.") print_rich("skipped existing_vars:[color=cyan]", existing_vars.size(), existing_vars) print_rich("created_vars:[color=green]", created_vars.size(), created_vars) func _read_existing_vars(code_lines: PackedStringArray) -> Dictionary: var existing_vars = {} for i in range(region_start_id + 1, region_end_id): var line = code_lines[i] if line.begins_with("var "): var var_name = line.split(" ")[1].split(":")[0].strip_escapes() if var_name: existing_vars[var_name] = true return existing_vars func _traverse_nodes(ground_node, node: Node, existing_vars: Dictionary, code_lines: PackedStringArray, created_vars): if node: _parse_node(ground_node, node, existing_vars, code_lines, created_vars) for child in node.get_children(): _traverse_nodes(ground_node, child, existing_vars, code_lines, created_vars) func _parse_node(ground_node, node:Node, existing_vars:Dictionary, code_lines:PackedStringArray, created_vars): # 0. filter unique mark # 1. create `var snake_case_variable_name = $"../xx/yy/NodeName"` # 2. create `var snake_case_variable_name = $"../xx/yy/NodeName"` if not node.unique_name_in_owner or ground_node.get_node_or_null("%"+node.name) == null: # 只读取 unique 标记过的节点; node.unique_name_in_owner 参数不可靠,需要实际检查 return var var_name = node.name.to_snake_case() if existing_vars.has(var_name): return created_vars.append(var_name) code_lines.insert(region_start_id + 1, "var " + var_name + ": "+ str(node.get_class())) func_line_id += 1 var path = self.get_path_to(node).get_concatenated_names() # closeup花名册 = $"../DeployLayer/Closeup花名册" code_lines.insert(func_line_id + 1, "\t" + var_name +" = $\"" + path +"\"")