using System.Collections.Generic; using UnityEditor; using UnityEditor.AddressableAssets; using UnityEditor.AddressableAssets.Settings; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; /// /// 场景加载方式配置工具(HybridCLR 配套): /// /// 规则:**凡是挂了热更脚本(Game.Hotfix)的场景,都不能随主包(Build Settings)加载**, /// 必须通过 AssetBundle/Addressables 加载,否则运行时会变成 missing script /// (典型现象:场景里的 MonoBehaviour 不工作,比如背景不动、UI 不响应)。 /// - UIScene:挂了 GameManager/TiShi/LoadingPanel/HuoDe 等 → 走 Addressables /// - XJMH/FLZY/HHWK/YYSC:挂了 BGManager/BgFit 等 → 走 Addressables /// - BootScene:纯 AOT 空场景(唯一随主包的场景),只负责启动 /// public static class BootSceneCreator { private const string BootScenePath = "Assets/MyGame/Scenes/BootScene.unity"; private const string UIScenePath = "Assets/MyGame/Scenes/UIScene.unity"; /// 游戏场景(内部挂热更脚本,必须走 Addressables 加载) private static readonly string[] GameSceneNames = { "XJMH", "FLZY", "HHWK", "YYSC" }; private static string GameScenePath(string name) => $"Assets/MyGame/Scenes/{name}.unity"; [MenuItem("Tools/Create BootScene")] public static void CreateBootScene() { // 1. 新建空场景 // 引导场景必须保持“全空”:不挂任何热更 MonoBehaviour(否则构建时会被判为 missing script), // 也不需要挂 AOT 单例——Launcher 是 static 类,靠 RuntimeInitializeOnLoadMethod 自动执行; // BeijingTimeManager 是 MonoSingleton(Awake 里 DontDestroyOnLoad),它已经挂在 UIScene 上, // 在这里再挂一份反而会产生两个实例(两份定时同步循环)。 Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); // 2. 保存场景 bool saved = EditorSceneManager.SaveScene(scene, BootScenePath); if (!saved) { Debug.LogError("[BootSceneCreator] 引导场景保存失败"); return; } Debug.Log($"[BootSceneCreator] 引导场景已创建: {BootScenePath}"); // 3. 把需要走 Addressables 的场景都标记好、并统一整理 Build Settings SetupGameScenesToAddressablesInternal(); MarkSceneAddressable(UIScenePath, "UIScene"); // 4. 打开引导场景 EditorSceneManager.OpenScene(BootScenePath); Debug.Log("[BootSceneCreator] 完成!请运行 HybridCLR > Generate > All 并重新构建。"); } /// /// 把游戏场景(XJMH/FLZY/HHWK/YYSC)标记为 Addressable(address = 场景名), /// 并把它们从 Build Settings 移除(改由 Addressables 加载)。 /// [MenuItem("Tools/Setup Game Scenes To Addressables")] public static void SetupGameScenesToAddressables() { SetupGameScenesToAddressablesInternal(); Debug.Log("[BootSceneCreator] 游戏场景已标记为 Addressable(address = 场景名)并从 Build Settings 移除。请重新构建。"); } private static void SetupGameScenesToAddressablesInternal() { foreach (var name in GameSceneNames) MarkSceneAddressable(GameScenePath(name), name); ApplyBuildSettings(); AssetDatabase.SaveAssets(); } /// /// 统一整理 Build Settings: /// - BootScene 放第一位(启动场景) /// - UIScene 与 4 个游戏场景全部移除(它们都改走 Addressables 加载) /// private static void ApplyBuildSettings() { var scenes = new List(EditorBuildSettings.scenes); // 先移除所有需要走 Addressables 的场景,避免重复 scenes.RemoveAll(s => s.path == BootScenePath); scenes.RemoveAll(s => s.path == UIScenePath); foreach (var name in GameSceneNames) { string path = GameScenePath(name); scenes.RemoveAll(s => s.path == path); } // BootScene 插到最前面 scenes.Insert(0, new EditorBuildSettingsScene(BootScenePath, true)); EditorBuildSettings.scenes = scenes.ToArray(); Debug.Log("[BootSceneCreator] Build Settings 已更新:BootScene 为启动场景,UIScene/XJMH/FLZY/HHWK/YYSC 已移除(改走 Addressables)。"); } /// /// 把一个场景标记为 Addressable,address key 设为指定值(Launcher / 游戏内加载按这个 key 找)。 /// private static void MarkSceneAddressable(string scenePath, string key) { var settings = AddressableAssetSettingsDefaultObject.Settings; if (settings == null) { Debug.LogError("[BootSceneCreator] AddressableAssetSettings 不存在,请先初始化 Addressables。"); return; } string guid = AssetDatabase.AssetPathToGUID(scenePath); if (string.IsNullOrEmpty(guid)) { Debug.LogError($"[BootSceneCreator] 找不到场景: {scenePath}"); return; } // 用默认组(Default Local Group)。如果项目有专门的场景组,可手动改。 AddressableAssetGroup group = settings.DefaultGroup; if (group == null) { Debug.LogError("[BootSceneCreator] Addressables 默认组不存在。"); return; } AddressableAssetEntry entry = settings.CreateOrMoveEntry(guid, group); entry.address = key; EditorUtility.SetDirty(settings); Debug.Log($"[BootSceneCreator] 场景已标记为 Addressable: {scenePath}(address='{key}',组='{group.Name}')。"); } }