@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) # 强制覆盖 archive 记录 @export var force_archive_scene := "" @export var force_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 static func _static_init() -> void: _read_grounds() # 场景名字映射到路径 static var GROUND_SCENE_PATH_DICT = {} static func _read_grounds() -> void: var scenes_dir = "res://scene/ground/scene/" # 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 func _ready() -> void: mask_layer.layer = GlobalConfig.CANVAS_LAYER_GROUND_MASK mask.visible = true mask.color.a = 0.0 ground = get_node_or_null("Ground") as Ground2D if ground: ground.queue_free() # load save if not ignore_archive: _load_save() if current_scene and entrance_portal: transition_to_scene(current_scene, entrance_portal, true) func _load_save(): # 强制覆盖 archive 记录 if force_archive_scene or force_archive_portal: current_scene = force_archive_scene entrance_portal = force_archive_portal return if not Engine.is_editor_hint(): if ArchiveManager.archive.current_scene: current_scene = ArchiveManager.archive.current_scene if ArchiveManager.archive.entrance_portal: entrance_portal = ArchiveManager.archive.entrance_portal func _toggle_mask(display: 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 键值 if not Engine.is_editor_hint(): _update_archive() if not immediately: # 转场效果,在 _load_ground_node 之前播放 var tween = _toggle_mask(true) tween.tween_callback(call_deferred.bind("_do_transition", scene_name)) tween.tween_callback(_toggle_mask.bind(false)) else: call_deferred("_do_transition", scene_name) else: print("Scene not found: " + scene_name) func _update_archive(): ArchiveManager.archive.current_scene = current_scene ArchiveManager.archive.entrance_portal = entrance_portal func _do_transition(scene_name: String): # SceneManager.freeze_player(0) ground = get_node_or_null("Ground") as Ground2D if ground: # 防止命名冲突 remove_child(ground) ground.queue_free() # 先设置 ground,再添加到场景中 # 因为 ground 在 enter_tree 时会用到 SceneManager 的方法 # 其中间接用到了 GroundLoader 的 ground ground = _load_ground_node(scene_name) _add_ground() # 预先加载邻居场景 _post_transition() if GlobalConfig.DEBUG and not Engine.is_editor_hint(): _watch_scene_update() func _add_ground(): add_child(ground) ground.name = "Ground" if not Engine.is_editor_hint(): # move player to portal ground.move_player_to_portal(entrance_portal) # 更新玩家位置 if first_entered: _update_player_position_from_archive() first_entered = false # SceneManager.release_player() func _update_player_position_from_archive(): if ignore_archive or Engine.is_editor_hint(): return var player = SceneManager.get_player() as MainPlayer if player: # 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 _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() first_entered = true transition_to_scene(current_scene, entrance_portal, true)