| Assets/AssetBundleCollectorSetting.asset | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| Assets/Launch/Config/DynamicCdnRemoteServices.cs | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| Assets/Launch/Config/DynamicCdnRemoteServices.cs.meta | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| Assets/Launch/Launch.cs | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| Assets/Launch/Manager/YooAssetInitializer.cs | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| Assets/Launch/UI/LaunchWins/LaunchExWin.cs | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
Assets/AssetBundleCollectorSetting.asset
@@ -199,7 +199,7 @@ CollectorGUID: 6a0e1d814ea59174985b17c9f4ecaba3 CollectorType: 0 AddressRuleName: AddressByRelativePath PackRuleName: PackSeparately PackRuleName: PackDirectory FilterRuleName: CollectConfigExcludeOPConfig AssetTags: UserData: @@ -431,3 +431,25 @@ FilterRuleName: CollectAll AssetTags: UserData: - PackageName: PreSceneRes PackageDesc: EnableAddressable: 0 SupportExtensionless: 1 LocationToLower: 0 IncludeAssetGUID: 0 AutoCollectShaders: 1 IgnoreRuleName: NormalIgnoreRule Groups: - GroupName: Prefab GroupDesc: AssetTags: ActiveRuleName: EnableGroup Collectors: - CollectPath: Assets/ResourcesOut/PreSceneRes CollectorGUID: f8f09fc3749c3d34bbb9483c2bc202f2 CollectorType: 0 AddressRuleName: AddressByFileName PackRuleName: PackSeparately FilterRuleName: CollectAll AssetTags: UserData: Assets/Launch/Config/DynamicCdnRemoteServices.cs
New file @@ -0,0 +1,41 @@ using YooAsset; /// <summary> /// 惰性读取 cdnUrl 的远程服务。 /// PreSceneRes 在 Awake 阶段就要用 HostPlayMode 初始化(以便支持后台更新), /// 但此时版本服务器尚未返回 cdnUrl。CacheFileSystem 初始化本身不需要 cdnUrl, /// 真正用到远程地址是在请求版本/下载时——那时 cdnUrl 已就绪。 /// 因此这里在每次取 URL 时实时从 VersionConfigEx.config 读取,避免初始化时机问题。 /// CDN 目录结构:{cdnUrl}{fixPath}/{packageName}/{fileName} /// </summary> public class DynamicCdnRemoteServices : IRemoteServices { private readonly string _packageName; public DynamicCdnRemoteServices(string packageName) { _packageName = packageName; } private string GetRoot() { string cdnUrl = VersionConfigEx.config?.cdnUrl; if (string.IsNullOrEmpty(cdnUrl)) return string.Empty; string root = cdnUrl.TrimEnd('/') + LocalResManager.fixPath; if (!string.IsNullOrEmpty(_packageName)) root = $"{root}/{_packageName}"; return root; } public string GetRemoteMainURL(string fileName) { return $"{GetRoot()}/{fileName}"; } public string GetRemoteFallbackURL(string fileName) { return $"{GetRoot()}/{fileName}"; } } Assets/Launch/Config/DynamicCdnRemoteServices.cs.meta
New file @@ -0,0 +1,11 @@ fileFormatVersion: 2 guid: e4fec10b9826c5c43b57bc94911cc248 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Assets/Launch/Launch.cs
@@ -53,7 +53,15 @@ } private GameObject launchExWin = null; private GameObject launchStaticWin = null; private bool isOpeningLaunchUI = false; private bool isOpeningLaunchStaticWin = false; private const string PRE_SCENE_RES_PACKAGE_NAME = "PreSceneRes"; private const string LAUNCH_STATIC_WIN_LOCATION = "Assets/ResourcesOut/PreSceneRes/LaunchStaticWin.prefab"; // 上次更新成功的 PreSceneRes 版本号(方案 A:自存版本号,下次启动本地激活,不联网)。 private const string PRE_SCENE_RES_VERSION_KEY = "yoo_ver_PreSceneRes"; private static Launch m_Instance; private void Awake() @@ -79,7 +87,210 @@ #else Debug.unityLogger.logEnabled = true; #endif OpenLaunchStaticWin().Forget(); } private async UniTaskVoid OpenLaunchStaticWin() { if (isOpeningLaunchStaticWin || launchStaticWin != null) return; isOpeningLaunchStaticWin = true; try { GameObject prefab = null; #if UNITY_EDITOR // Editor 模拟(非 AB 模式):直接走 AssetDatabase,秒开,便于本地调试。 if (!AssetSource.isUseAssetBundle) { prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(LAUNCH_STATIC_WIN_LOCATION); if (prefab == null) { Debug.LogError($"[Launch] LaunchStaticWin prefab not found: {LAUNCH_STATIC_WIN_LOCATION}"); return; } launchStaticWin = Instantiate(prefab); launchStaticWin.name = "LaunchStaticWin"; launchStaticWin.AddComponent<DontDestroyOnLoad>(); return; } #endif #if UNITY_WEBGL && !UNITY_EDITOR // WebGL:PreSceneRes 由 YooAssetInitializer 走标准联网初始化,这里等待就绪后异步加载。 while (YooAssetInitializer.Instance.State != YooAssetInitializer.InitState.Ready) { if (YooAssetInitializer.Instance.State == YooAssetInitializer.InitState.InitFailed || YooAssetInitializer.Instance.State == YooAssetInitializer.InitState.VersionFailed) { Debug.LogWarning($"[Launch] Skip LaunchStaticWin because YooAsset state={YooAssetInitializer.Instance.State}"); return; } if (this == null) return; await UniTask.Yield(); } var webPackage = YooAssetInitializer.Instance.GetPackage(PRE_SCENE_RES_PACKAGE_NAME); if (webPackage == null) { Debug.LogWarning($"[Launch] Package '{PRE_SCENE_RES_PACKAGE_NAME}' not initialized. Skip LaunchStaticWin."); return; } var webHandle = webPackage.LoadAssetAsync<GameObject>(LAUNCH_STATIC_WIN_LOCATION); await webHandle.ToUniTask(); if (webHandle.Status != EOperationStatus.Succeed) { Debug.LogError($"[Launch] Load LaunchStaticWin failed: {webHandle.LastError}"); return; } prefab = webHandle.GetAssetObject<GameObject>(); #else // Android/iOS/PC(含 Editor AB 模式):本地秒开,独立初始化 PreSceneRes,不等网络。 YooAssets.Initialize(); var package = YooAssets.TryGetPackage(PRE_SCENE_RES_PACKAGE_NAME) ?? YooAssets.CreatePackage(PRE_SCENE_RES_PACKAGE_NAME); if (package.InitializeStatus != EOperationStatus.Succeed) { // 始终用 HostPlayMode(随包 buildin + 缓存)。 // cdnUrl 在 Awake 阶段通常还没拿到,但 CacheFileSystem 初始化不需要它, // 远程地址由 DynamicCdnRemoteServices 在请求版本/下载时实时读取。 var remoteServices = new DynamicCdnRemoteServices(PRE_SCENE_RES_PACKAGE_NAME); var initParams = new HostPlayModeParameters { BuildinFileSystemParameters = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(), CacheFileSystemParameters = FileSystemParameters.CreateDefaultCacheFileSystemParameters(remoteServices) }; var initOp = package.InitializeAsync(initParams); await initOp.ToUniTask(); if (initOp.Status != EOperationStatus.Succeed) { Debug.LogError($"[Launch] Init PreSceneRes failed: {initOp.Error}"); return; } } // 本地激活 manifest(不联网):取「上次更新保存的版本」与「包内 buildin 版本」中较新的一个。 // 版本号格式 yyyy-MM-dd-HHmm 字典序可排序; // 这样重新打包带入更新的 buildin 资源时,不会被旧的 LocalSave 版本号覆盖。 string savedVer = LocalSave.GetString(PRE_SCENE_RES_VERSION_KEY); string buildinVer = await ReadBuildinPreSceneResVersionAsync(); string localVer; if (string.IsNullOrEmpty(savedVer)) localVer = buildinVer; else if (string.IsNullOrEmpty(buildinVer)) localVer = savedVer; else localVer = string.CompareOrdinal(savedVer, buildinVer) >= 0 ? savedVer : buildinVer; if (string.IsNullOrEmpty(localVer)) { Debug.LogError("[Launch] PreSceneRes local version unavailable."); return; } Debug.Log($"[Launch] PreSceneRes localVer={localVer} (saved={savedVer}, buildin={buildinVer})"); var localManifestOp = package.UpdatePackageManifestAsync(localVer); await localManifestOp.ToUniTask(); if (localManifestOp.Status != EOperationStatus.Succeed) { // 选中的版本激活失败(如缓存被清且无网络),回退到包内 buildin 版本。 if (!string.IsNullOrEmpty(buildinVer) && buildinVer != localVer) { Debug.LogWarning($"[Launch] Activate manifest v{localVer} failed, fallback buildin v{buildinVer}: {localManifestOp.Error}"); localVer = buildinVer; localManifestOp = package.UpdatePackageManifestAsync(localVer); await localManifestOp.ToUniTask(); } if (localManifestOp.Status != EOperationStatus.Succeed) { Debug.LogError($"[Launch] Activate PreSceneRes manifest v{localVer} failed: {localManifestOp.Error}"); return; } } var handle = package.LoadAssetSync<GameObject>(LAUNCH_STATIC_WIN_LOCATION); if (handle.Status != EOperationStatus.Succeed) { Debug.LogError($"[Launch] Load LaunchStaticWin failed: {handle.LastError}"); return; } prefab = handle.GetAssetObject<GameObject>(); // 后台更新(不阻塞秒开):等 cdnUrl 就绪后请求最新版本、更新清单、加载一次触发下载落地, // 并保存新版本号,供下次启动直接用最新资源。 YooAssetInitializer.Instance.UpdateAndWarmupPackageAsync( package, LAUNCH_STATIC_WIN_LOCATION, PRE_SCENE_RES_VERSION_KEY, localVer).Forget(); #endif if (prefab == null) { Debug.LogError("[Launch] LaunchStaticWin prefab is null."); return; } launchStaticWin = Instantiate(prefab); launchStaticWin.name = "LaunchStaticWin"; launchStaticWin.AddComponent<DontDestroyOnLoad>(); } catch (Exception ex) { Debug.LogError($"[Launch] OpenLaunchStaticWin exception: {ex}"); } finally { isOpeningLaunchStaticWin = false; } } #if !UNITY_WEBGL || UNITY_EDITOR /// <summary> /// 读取包内(StreamingAssets)PreSceneRes 的内置版本号,纯本地不联网。 /// 仅在 LocalSave 尚无保存版本(首次启动/未更新过)时使用。 /// </summary> private async UniTask<string> ReadBuildinPreSceneResVersionAsync() { try { string folder = YooAssetSettingsData.GetDefaultYooFolderName(); string fileName = YooAssetSettingsData.GetPackageVersionFileName(PRE_SCENE_RES_PACKAGE_NAME); string filePath = $"{Application.streamingAssetsPath}/{folder}/{PRE_SCENE_RES_PACKAGE_NAME}/{fileName}"; // Android 上 streamingAssetsPath 已是 jar:file://... 形式,可直接用于 UnityWebRequest; // 其它平台为本地文件路径,需补 file:// 前缀。 string url; #if UNITY_ANDROID && !UNITY_EDITOR url = filePath; #elif UNITY_EDITOR_WIN url = $"file:///{filePath}"; #else url = $"file://{filePath}"; #endif using (var req = UnityEngine.Networking.UnityWebRequest.Get(url)) { await req.SendWebRequest().ToUniTask(); if (req.result != UnityEngine.Networking.UnityWebRequest.Result.Success) { Debug.LogWarning($"[Launch] Read buildin PreSceneRes version failed: {req.error}"); return null; } return req.downloadHandler.text.Trim(); } } catch (Exception ex) { Debug.LogError($"[Launch] ReadBuildinPreSceneResVersionAsync exception: {ex}"); return null; } } #endif // 启动入口 async void Start() @@ -242,6 +453,7 @@ try { launchExWin = await LaunchExWin.OpenWindow(); launchExWin.AddComponent<DontDestroyOnLoad>(); } catch (Exception ex) { @@ -337,13 +549,19 @@ // 由 Main 程序集中 YooAssetService 接管使用(通过 IYooAssetBridge) // YooAssetInitializer 的 Release 由 YooAssetService 初始化时决定 // DestroyLaunchExWin(); LaunchExWin.Instance.NextState(); stop = true; } public void DestroyLaunchExWin() { if (null != launchExWin) { Destroy(launchExWin); launchExWin = null; } stop = true; launchExWin = null; } bool stop = false; Assets/Launch/Manager/YooAssetInitializer.cs
@@ -42,6 +42,9 @@ public const string DLL_PACKAGE_NAME = "Dll"; /// <summary>启动前置场景资源包 — 含 LaunchStaticWin 等 Awake 阶段展示资源</summary> public const string PRE_SCENE_RES_PACKAGE_NAME = "PreSceneRes"; public const string DEFAULT_PACKAGE_NAME = BUILTIN_PACKAGE_NAME; // Launch 阶段读取的启动配置文件被 CollectResBeforeUpdate 额外收进 Builtin 包。 @@ -51,7 +54,16 @@ /// <summary> /// Launch 阶段需要初始化的包名(与 Collector 一致)。 /// </summary> private static readonly string[] LAUNCH_PACKAGES = new[] { BUILTIN_PACKAGE_NAME, DLL_PACKAGE_NAME }; private static readonly string[] LAUNCH_PACKAGES = new[] { BUILTIN_PACKAGE_NAME, DLL_PACKAGE_NAME, #if UNITY_WEBGL && !UNITY_EDITOR // WebGL 下 PreSceneRes 走标准联网初始化流程; // 非 WebGL 由 Launch.OpenLaunchStaticWin 独立本地秒开加载,不在此初始化。 PRE_SCENE_RES_PACKAGE_NAME, #endif }; private EPlayMode _playMode; private IRemoteServices _remoteServices; @@ -365,6 +377,83 @@ } } /// <summary> /// 对指定包请求最新版本并更新清单,更新成功后立即加载一次目标资源(不实例化), /// 让 HostPlayMode 自动把对应 bundle 从 CDN 下载落地,并把新版本号保存到本地, /// 供下次启动直接用本地新资源秒开。 /// 适用于 Launch 独立创建的本地秒开包(如 PreSceneRes),不阻塞当前启动流程。 /// </summary> /// <param name="package">已初始化的包裹(由调用方创建并初始化)</param> /// <param name="location">用于触发下载的资源定位地址</param> /// <param name="versionKey">保存新版本号的 LocalSave key</param> /// <param name="currentVer">当前已激活的本地版本号(与最新一致则跳过)</param> public async UniTask UpdateAndWarmupPackageAsync(ResourcePackage package, string location, string versionKey, string currentVer) { if (package == null) { Debug.LogWarning("[YooAssetInitializer] UpdateAndWarmup skipped: package is null."); return; } try { Debug.Log($"[YooAssetInitializer] UpdateAndWarmup enter '{package.PackageName}', current={currentVer}, waiting cdnUrl..."); // 等待 cdnUrl 就绪(版本服务器返回后由 LocalResManager 写入 VersionConfigEx.config.cdnUrl)。 // 最多等待 30 秒,超时则放弃本次更新(本次仍用本地版本,不影响秒开)。 float waitStart = Time.realtimeSinceStartup; while (string.IsNullOrEmpty(VersionConfigEx.config?.cdnUrl)) { if (Time.realtimeSinceStartup - waitStart > 30f) { Debug.LogWarning("[YooAssetInitializer] UpdateAndWarmup give up: cdnUrl not ready within 30s."); return; } await UniTask.Yield(); } Debug.Log($"[YooAssetInitializer] UpdateAndWarmup '{package.PackageName}' cdnUrl ready, requesting latest version..."); var versionOp = package.RequestPackageVersionAsync(); await versionOp.ToUniTask(); if (versionOp.Status != EOperationStatus.Succeed) { Debug.LogWarning($"[YooAssetInitializer] UpdateAndWarmup request version failed: {versionOp.Error}"); return; } string latest = versionOp.PackageVersion; Debug.Log($"[YooAssetInitializer] UpdateAndWarmup '{package.PackageName}': current={currentVer}, latest={latest}"); if (string.IsNullOrEmpty(latest) || latest == currentVer) return; // 已是最新 var manifestOp = package.UpdatePackageManifestAsync(latest); await manifestOp.ToUniTask(); if (manifestOp.Status != EOperationStatus.Succeed) { Debug.LogWarning($"[YooAssetInitializer] UpdateAndWarmup update manifest failed: {manifestOp.Error}"); return; } // 更新清单后直接加载一次目标资源(不实例化), // HostPlayMode 下 LoadAssetAsync 会自动把对应 bundle 从 CDN 下载落地。 var loadHandle = package.LoadAssetAsync<UnityEngine.Object>(location); await loadHandle.ToUniTask(); if (loadHandle.Status != EOperationStatus.Succeed) { Debug.LogWarning($"[YooAssetInitializer] UpdateAndWarmup load failed: {loadHandle.LastError}"); return; } loadHandle.Release(); LocalSave.SetString(versionKey, latest); Debug.Log($"[YooAssetInitializer] Package '{package.PackageName}' updated to {latest}, will be used on next launch."); } catch (Exception ex) { Debug.LogError($"[YooAssetInitializer] UpdateAndWarmupPackageAsync exception: {ex}"); } } #if UNITY_WEBGL || TEST_BUILD private void LogPackageState(string pkgName, ResourcePackage package, string phase) { Assets/Launch/UI/LaunchWins/LaunchExWin.cs
@@ -27,13 +27,26 @@ // [SerializeField] Button deleteBranchBtn; public int ProgressState = 0;//1.LocalResManager阶段 2. int AllTimes = 8; bool ShowCircleView = false; string sliderText; private static LaunchExWin launchExWin; public static LaunchExWin Instance { get { return launchExWin; } } void Awake() { launchExWin = this; ProgressState = 1; SetProgressBarImagesVisible(false); InitSpritesAsync().Forget(); UpdateProgress(); @@ -44,6 +57,8 @@ /// </summary> public async UniTask InitSpritesAsync() { Debug.LogError($"<color=yellow>[YLTEST][LaunchExWin] 开始初始化精灵</color>"); var languageShowDict = JsonMapper.ToObject<Dictionary<string, string>>(InitialFunctionConfigEx.Get("Language").Numerical1); var id = string.IsNullOrEmpty(LocalResManager.Id) ? "zh" : LocalResManager.Id; // 默认中文 sliderText = languageShowDict[id]; @@ -56,18 +71,63 @@ if (this == null) return; // destroyed check after await Debug.LogError($"<color=cyan>[YLTEST][LaunchExWin] 精灵加载完成 - LoadingBottom={sprites.Item3 != null}, LoadingSlider={sprites.Item4 != null}</color>"); imagebg1.sprite = sprites.Item1; imagebg2.sprite = sprites.Item1; imageCircle.sprite = sprites.Item2; imagebg3.sprite = sprites.Item3; imageloding.sprite = sprites.Item4; // 诊断日志:检查进度条组件状态 DiagnoseProgressBarComponents(); if (sprites.Item3 != null && sprites.Item4 != null) { SetProgressBarImagesVisible(true); Debug.LogError($"<color=green>[YLTEST][LaunchExWin] 进度条精灵设置成功并显示</color>"); } else { Debug.LogError($"[LaunchExWin] Progress bar sprite load failed. LoadingBottom={sprites.Item3 != null}, LoadingSlider={sprites.Item4 != null}"); Debug.LogError($"<color=red>[YLTEST][LaunchExWin] 进度条精灵加载失败 LoadingBottom={sprites.Item3 != null}, LoadingSlider={sprites.Item4 != null}</color>"); } } private void DiagnoseProgressBarComponents() { Debug.LogError($"<color=yellow>[YLTEST][LaunchExWin] 检查进度条组件状态</color>"); if (imagebg3 != null) { Debug.LogError($"<color=cyan>[YLTEST][LaunchExWin] imagebg3 (Background) 存在: {imagebg3.name}</color>"); Debug.LogError($"<color=cyan>[YLTEST][LaunchExWin] - Sprite: {(imagebg3.sprite != null ? imagebg3.sprite.name : "NULL")}</color>"); Debug.LogError($"<color=cyan>[YLTEST][LaunchExWin] - Color: {imagebg3.color}</color>"); Debug.LogError($"<color=cyan>[YLTEST][LaunchExWin] - Material: {(imagebg3.material != null ? imagebg3.material.name : "NULL")}</color>"); Debug.LogError($"<color=cyan>[YLTEST][LaunchExWin] - Shader: {(imagebg3.material != null && imagebg3.material.shader != null ? imagebg3.material.shader.name : "NULL")}</color>"); Debug.LogError($"<color=cyan>[YLTEST][LaunchExWin] - Enabled: {imagebg3.enabled}</color>"); } else { Debug.LogError($"<color=red>[YLTEST][LaunchExWin] imagebg3 is NULL</color>"); } if (imageloding != null) { Debug.LogError($"<color=cyan>[YLTEST][LaunchExWin] imageloding (Fill) 存在: {imageloding.name}</color>"); Debug.LogError($"<color=cyan>[YLTEST][LaunchExWin] - Sprite: {(imageloding.sprite != null ? imageloding.sprite.name : "NULL")}</color>"); Debug.LogError($"<color=cyan>[YLTEST][LaunchExWin] - Color: {imageloding.color}</color>"); Debug.LogError($"<color=cyan>[YLTEST][LaunchExWin] - Material: {(imageloding.material != null ? imageloding.material.name : "NULL")}</color>"); Debug.LogError($"<color=cyan>[YLTEST][LaunchExWin] - Shader: {(imageloding.material != null && imageloding.material.shader != null ? imageloding.material.shader.name : "NULL")}</color>"); Debug.LogError($"<color=cyan>[YLTEST][LaunchExWin] - Enabled: {imageloding.enabled}</color>"); if (imageloding.sprite == null) { Debug.LogError($"<color=orange>[YLTEST][LaunchExWin] 问题: imageloding Sprite 为 NULL,这会导致显示白色</color>"); } } else { Debug.LogError($"<color=red>[YLTEST][LaunchExWin] imageloding is NULL</color>"); } } @@ -82,6 +142,14 @@ { imageloding.enabled = visible; } } public void NextState() { ProgressState = 2; var LaunchStaticWin = GameObject.Find("LaunchStaticWin"); DestroyImmediate(LaunchStaticWin); UpdateProgress(); } private void OnEnable() @@ -154,31 +222,50 @@ private void UpdateProgress() { float value = LocalResManager.step == LocalResManager.LoadDllStep.None ? 0.05f : Mathf.Max(0.05f, Launch.DllLoadProgress); if (ProgressState == 1) { float value = LocalResManager.step == LocalResManager.LoadDllStep.None ? 0.05f : Mathf.Max(0.05f, Launch.DllLoadProgress); if (float.IsNaN(value) || value < 0.05) { value = 0.05f; if (float.IsNaN(value) || value < 0.05) { value = 0.05f; } if (ShowCircleView) { m_IosProgressTip.text = StringUtility.Concat(sliderText, " ", ((int)(value * 100)).ToString(), "%"); //circleImg.Rotate(Vector3.forward, -1800 * Time.deltaTime); } else { m_TotalProgressSlider.value = value; m_StageDescription.text = StringUtility.Concat(sliderText, " ", ((int)(value * 100)).ToString(), "%"); } } if (ShowCircleView) else if (ProgressState == 2) { m_IosProgressTip.text = StringUtility.Concat(sliderText, " ", ((int)(value * 100)).ToString(), "%"); //circleImg.Rotate(Vector3.forward, -1800 * Time.deltaTime); } else { float value = m_TotalProgressSlider.value + 0.025f; value = Mathf.Min(1f, value); m_TotalProgressSlider.value = value; m_StageDescription.text = StringUtility.Concat(sliderText, " ", ((int)(value * 100)).ToString(), "%"); } else { } } void OnDestroy() { launchExWin = null; } public static async UniTask<GameObject> OpenWindow() { GameObject window = GameObject.Instantiate(await LocalResManager.Instance.LoadBuiltInPrefabAsync("LaunchExWin")); window.transform.localScale = Vector3.zero; // window.transform.localScale = Vector3.zero; // 移除这行,让 LaunchExWin 正常显示直到 LaunchWin 打开 return window; } }