三国卡牌客户端基础资源仓库
yyl
2026-06-11 98ae1c2e623731e862a7b6a1a30caf3ee52e26db
启动流程优化 + 部分bug修复
4个文件已修改
2个文件已添加
506 ■■■■■ 已修改文件
Assets/AssetBundleCollectorSetting.asset 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Launch/Config/DynamicCdnRemoteServices.cs 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Launch/Config/DynamicCdnRemoteServices.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Launch/Launch.cs 224 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Launch/Manager/YooAssetInitializer.cs 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Launch/UI/LaunchWins/LaunchExWin.cs 115 ●●●● 补丁 | 查看 | 原始文档 | 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;
    }
}