@tool class_name GroundLoader extends Node2D @export_group("Scene") @export var ignore_archive := false @export var current_scene := "c02_s01" @export var entrance_portal := "left" @export var debug_reload := false: set(new_val): debug_reload = false if is_node_ready() and current_scene and entrance_portal: transition_to_scene(current_scene, entrance_portal, true) @export var archive_scene := "" @export var archive_portal := "" @onready var mask_layer := %MaskLayer as CanvasLayer @onready var mask := %Mask as ColorRect var first_entered := true var ground: Ground2D var display_mask_time = 0.0 var scenes_dir = "res://scene/ground/scene/" # 场景名字映射到路径 var ground_scene_path_dict = {} func _ready() -> void: mask_layer.layer = GlobalConfig.CANVAS_LAYER_GROUND_MASK mask.visible = true mask.color.a = 0.0 # grounds _read_grounds() # ground = get_node_or_null("Ground") # load save if not ignore_archive: _load_save() if current_scene and entrance_portal: transition_to_scene(current_scene, entrance_portal, true) func _read_grounds() -> void: # read grounds var dir = DirAccess.open(scenes_dir) for c_dir in dir.get_directories(): var c_path = scenes_dir + c_dir + "/" for s_file in DirAccess.open(c_path).get_files(): if s_file.ends_with(".tscn"): var s_path = c_path + s_file ground_scene_path_dict[c_dir.substr(0, 3) + "_" + s_file.substr(0, 3)] = s_path # # 确保每个 ground 都初始化 archive # for key in ground_scene_path_dict.keys(): # if GlobalConfig.DEBUG: # print("check ground_archive:", key) # ArchiveManager.archive.ground_archive(key) func _load_save(): if not Engine.is_editor_hint() and ArchiveManager.archive: if ArchiveManager.archive.current_scene: archive_scene = ArchiveManager.archive.current_scene if ArchiveManager.archive.entrance_portal: archive_portal = ArchiveManager.archive.entrance_portal # 使用 archive 所记录的场景 if archive_scene and archive_portal: current_scene = archive_scene entrance_portal = archive_portal func _toggle_mask(display: bool, _immediately: bool) -> Tween: var tween = get_tree().create_tween() if display: tween.tween_property(mask, "color:a", 1.0, 0.3).set_trans(Tween.TRANS_CUBIC) display_mask_time = Time.get_ticks_msec() else: # var time = Time.get_ticks_msec() # # 转场至少 0.6s, 除去 0.3s 最后的淡出,需要 0.3s 的等待时间(包含 mask 的淡入) # if not _immediately: # var wait_time = max(display_mask_time + 300 - time, 0.0) * 0.001 # if wait_time: # tween.tween_interval(wait_time) tween.tween_property(mask, "color:a", 0.0, 0.3).set_trans(Tween.TRANS_CUBIC) return tween func transition_to_scene(scene_name: String, portal: String, immediately: bool) -> void: var scene_path = ground_scene_path_dict.get(scene_name) if scene_path: current_scene = scene_name entrance_portal = portal # 优先更新 archive,使 ground 可以访问自己的 current_scene 键值 _update_archive() # 转场效果,在 _load_ground_node 之前播放 var tween = _toggle_mask(true, immediately) tween.tween_callback(_do_transition.bind(scene_name)) tween.tween_callback(_toggle_mask.bind(false, immediately)) else: print("Scene not found: " + scene_name) func _update_player_position(): if ignore_archive or Engine.is_editor_hint(): return var player = SceneManager.get_player() as MainPlayer if player and ArchiveManager.archive: # if GlobalConfig.DEBUG: # print("update player position", ArchiveManager.archive.player_global_position) if ArchiveManager.archive.player_global_position_x >= 0: player.global_position.x = ArchiveManager.archive.player_global_position_x player.set_facing_direction(ArchiveManager.archive.player_direction) func _do_transition(scene_name: String): # SceneManager.freeze_player(0) var ground_node = _load_ground_node(scene_name) if ground == ground_node: return if ground: # 提前移除,防止命名冲突 remove_child(ground) # 不需要释放,因为会缓存,在 ground_node_cache 中释放 # ground.queue_free() # 先设置 ground,再添加到场景中 # 因为 ground 在 enter_tree 时会用到 SceneManager 的方法 # 其中间接用到了 GroundLoader 的 ground ground = ground_node _add_ground() # 预先加载邻居场景 _post_transition() if GlobalConfig.DEBUG and not Engine.is_editor_hint(): _watch_scene_update() func _add_ground(): ground.name = "Ground" add_child(ground) if not Engine.is_editor_hint(): var portal_node = ground.get_node_or_null("DeployLayer/portal_" + entrance_portal) as Node2D if portal_node: var player = SceneManager.get_player() if player: # player.global_position.x = -20.0 player.global_position.x = portal_node.global_position.x if GlobalConfig.DEBUG: print("move player to portal:", entrance_portal, portal_node.global_position) else: printerr(current_scene + " portal not found: " + entrance_portal) # 更新玩家位置 if first_entered and not Engine.is_editor_hint(): _update_player_position() first_entered = false # SceneManager.release_player() func _update_archive(): if not Engine.is_editor_hint() and ArchiveManager.archive: ArchiveManager.archive.current_scene = current_scene ArchiveManager.archive.entrance_portal = entrance_portal archive_scene = current_scene archive_portal = entrance_portal func _load_ground_node(scene_name: String) -> Node2D: if not ground_scene_path_dict.has(scene_name): return null var path = ground_scene_path_dict[scene_name] var scene = ResourceLoader.load(path) as PackedScene if scene: var instance = scene.instantiate() as Node2D var ground_node = instance.get_child(0) instance.remove_child(ground_node) ground_node.owner = null instance.queue_free() return ground_node return null # 读取 portals,预加载邻居场景 func _post_transition(): if ground: var scene_names = [] var deploy_layer = ground.get_node("DeployLayer") if deploy_layer: for node in deploy_layer.get_children(): var portal = node as Portal2D if not portal or not portal.target_scene: continue if ground_scene_path_dict.has(portal.target_scene): scene_names.append(portal.target_scene) if scene_names: for scene_name in scene_names: ResourceLoader.load_threaded_request(ground_scene_path_dict[scene_name]) if GlobalConfig.DEBUG: print("preload neighbor scenes:", scene_names) var update_watcher: Timer var last_modify_time = 0 # DEBUG 时重新加载资源 func _watch_scene_update(): var scene_path = ground_scene_path_dict[current_scene] if scene_path: last_modify_time = FileAccess.get_modified_time(scene_path) if not update_watcher: update_watcher = Timer.new() update_watcher.wait_time = 1 update_watcher.one_shot = false add_child(update_watcher) update_watcher.start() else: # remove all connections for c in update_watcher.timeout.get_connections(): update_watcher.timeout.disconnect(c.callable) update_watcher.timeout.connect(_check_scene_update.bind(scene_path)) func _check_scene_update(scene_path): var modify = FileAccess.get_modified_time(scene_path) if modify != last_modify_time: last_modify_time = modify _on_resources_reload(scene_path) func _on_resources_reload(res): print("resources_reload processing:", res) if not Engine.is_editor_hint() and res.ends_with(".tscn"): ArchiveManager.save_all() transition_to_scene(current_scene, entrance_portal, true)