| | |
| | | } |
| | | } |
| | | |
| | | #if UNITY_EDITOR && !UNITY_WEBGL |
| | | public bool isPCTestDownLoad = false; //开启下载并开启下载bytes |
| | | public bool isOpenDownLoad = false; //只开启下载 |
| | | #endif |
| | | |
| | | public static int downLoadCount = 0; |
| | | |
| | | public static readonly string[] VERSION_URL = new string[] |
| | |
| | | private const int YOO_MAX_RETRY = 3; |
| | | private const int YOO_BASE_DELAY_MS = 500; |
| | | |
| | | // package 为 null 时默认使用 PrefabPackage(Config/Shader/Materials/ScriptableObject/Scenes 等) |
| | | // package 为 null 时默认使用启动配置包(当前为 Builtin) |
| | | // BuiltIn 路径下的资源(Sprites/Prefabs)应传入 DefaultPackage(= Builtin 包) |
| | | private async UniTask<T> LoadAssetWithRetryAsync<T>(string location, ResourcePackage package = null) where T : UnityEngine.Object |
| | | { |
| | |
| | | /// </summary> |
| | | private void OnVersionCheckResult(bool _ok, string _result) |
| | | { |
| | | if (_ok) |
| | | if (!_ok) |
| | | { |
| | | versionUrlResult = NormalizeVersionJson(_result); |
| | | if (string.IsNullOrEmpty(versionUrlResult)) |
| | | { |
| | | Debug.LogError("[LocalResManager] 服务端返回空版本数据,请检查服务端是否已配置该渠道。将在1秒后重试..."); |
| | | Clock.AlarmAt(DateTime.Now + new TimeSpan(TimeSpan.TicksPerSecond), RequestVersionCheck); |
| | | RetryVersionCheck($"[LocalResManager] 版本接口请求失败,url={versionUrl},error={_result}"); |
| | | return; |
| | | } |
| | | |
| | | versionUrlResult = _result; |
| | | |
| | | // 版本接口是启动链路入口:先拿到可解析的版本数据,再允许后续资源初始化。 |
| | | if (!TryUpdateVersionInfo(_result)) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | Debug.Log("OK OnVersionCheckResult " + versionUrlResult); |
| | | |
| | | // resource_url 会决定 YooAsset HostPlayMode 的远端根路径,必须在初始化 YooAsset 前写入。 |
| | | TryApplyCdnUrlFromVersionInfo(); |
| | | |
| | | // InitialFunction 是启动期功能开关和语言配置;CDN 失败时会回落到包内表,避免启动卡死。 |
| | | LoadInitialFunctionAndStartAsync(BuildInitialFunctionUrl()).Forget(); |
| | | } |
| | | |
| | | private bool TryUpdateVersionInfo(string rawResult) |
| | | { |
| | | versionUrlResult = NormalizeVersionJson(rawResult); |
| | | if (string.IsNullOrEmpty(versionUrlResult)) |
| | | { |
| | | RetryVersionCheck("[LocalResManager] 服务端返回空版本数据,请检查服务端是否已配置该渠道。"); |
| | | return false; |
| | | } |
| | | |
| | | try |
| | | { |
| | | versionInfo = JsonMapper.ToObject<VersionInfo>(versionUrlResult); |
| | | return true; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | var firstChar = versionUrlResult.Length > 0 ? ((int)versionUrlResult[0]).ToString() : "<empty>"; |
| | | var preview = versionUrlResult.Length > 32 ? versionUrlResult.Substring(0, 32) : versionUrlResult; |
| | | Debug.LogError($"[LocalResManager] Version json parse failed. firstChar={firstChar}, preview='{preview}', error={ex}"); |
| | | RetryVersionCheck($"[LocalResManager] Version json parse failed. firstChar={firstChar}, preview='{preview}', error={ex}"); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private void TryApplyCdnUrlFromVersionInfo() |
| | | { |
| | | var versionConfig = VersionConfigEx.config; |
| | | if (versionInfo == null || versionConfig == null) |
| | | { |
| | | Debug.LogWarning("[LocalResManager] VersionInfo 或 VersionConfigEx 为空,将使用本地/离线资源模式。"); |
| | | return; |
| | | } |
| | | |
| | | Debug.Log("OK OnVersionCheckResult " + _result); |
| | | |
| | | // 从服务器返回的 resource_url 中提取 CDN 地址,写入 VersionConfigEx 供 YooAsset 初始化使用 |
| | | if (versionInfo != null && VersionConfigEx.config != null) |
| | | { |
| | | // #if TEST_BUILD && UNITY_WEBGL |
| | | // 本地测试:强制使用本地 CDN,忽略服务器返回的 resource_url |
| | | // VersionConfigEx.config.cdnUrl = "http://localhost:8081/"; |
| | | // Debug.Log("[LocalResManager] TEST_BUILD+WebGL: 强制使用本地 CDN http://localhost:8081/"); |
| | | // #else |
| | | try |
| | | { |
| | | string resourceUrl = versionInfo.GetResourcesURL(VersionConfigEx.config.branch); |
| | | if (!string.IsNullOrEmpty(resourceUrl)) |
| | | string resourceUrl = versionInfo.GetResourcesURL(versionConfig.branch); |
| | | if (string.IsNullOrEmpty(resourceUrl)) |
| | | { |
| | | VersionConfigEx.config.cdnUrl = resourceUrl; |
| | | Debug.Log($"[LocalResManager] 已从 resource_url 设置 cdnUrl={resourceUrl} (branch={VersionConfigEx.config.branch})"); |
| | | Debug.LogWarning($"[LocalResManager] resource_url 为空,将使用本地/离线资源模式。branch={versionConfig.branch}"); |
| | | return; |
| | | } |
| | | |
| | | versionConfig.cdnUrl = resourceUrl.TrimEnd('/'); |
| | | Debug.Log($"[LocalResManager] 已从 resource_url 设置 cdnUrl={versionConfig.cdnUrl} (branch={versionConfig.branch})"); |
| | | } |
| | | catch (Exception urlEx) |
| | | { |
| | | Debug.LogWarning($"[LocalResManager] 获取 resource_url 失败,将使用 OfflinePlayMode: {urlEx.Message}"); |
| | | Debug.LogWarning($"[LocalResManager] 获取 resource_url 失败,将使用本地/离线资源模式: {urlEx.Message}"); |
| | | } |
| | | // #endif |
| | | } |
| | | |
| | | Launch.Instance.InitYooAssetEarlyAsync().ContinueWith(() => |
| | | private string BuildInitialFunctionUrl() |
| | | { |
| | | var cdnUrl = VersionConfigEx.config?.cdnUrl; |
| | | if (string.IsNullOrEmpty(cdnUrl)) |
| | | { |
| | | return string.Empty; |
| | | } |
| | | |
| | | return cdnUrl.TrimEnd('/') + "/InitialFunction.txt"; |
| | | } |
| | | |
| | | private async UniTaskVoid LoadInitialFunctionAndStartAsync(string initFuncUrl) |
| | | { |
| | | bool configReady = await TryLoadInitialFunctionFromCdnAsync(initFuncUrl); |
| | | if (!configReady) |
| | | { |
| | | await TryLoadInitialFunctionFromResourcesAsync(); |
| | | } |
| | | |
| | | await InitializeYooAssetAndEnterReadBytesAsync(); |
| | | } |
| | | |
| | | private async UniTask<bool> TryLoadInitialFunctionFromCdnAsync(string initFuncUrl) |
| | | { |
| | | if (string.IsNullOrEmpty(initFuncUrl)) |
| | | { |
| | | Debug.LogWarning("[LocalResManager] cdnUrl 为空,跳过 CDN InitialFunction.txt,尝试读取包内配置。"); |
| | | return false; |
| | | } |
| | | |
| | | var response = await HttpRequest.Instance.UnityWebRequestGetAsync(initFuncUrl, HttpRequest.defaultHttpContentType, 10); |
| | | if (!response.ok) |
| | | { |
| | | Debug.LogWarning($"[LocalResManager] 从 CDN 加载 InitialFunction.txt 失败,url={initFuncUrl},error={response.message}"); |
| | | return false; |
| | | } |
| | | |
| | | if (string.IsNullOrEmpty(response.message)) |
| | | { |
| | | Debug.LogWarning($"[LocalResManager] CDN InitialFunction.txt 内容为空,url={initFuncUrl}"); |
| | | return false; |
| | | } |
| | | |
| | | try |
| | | { |
| | | InitialFunctionConfig.Init(response.message); |
| | | Debug.Log($"[LocalResManager] 成功从 CDN 加载 InitialFunction.txt,url={initFuncUrl}"); |
| | | return true; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Debug.LogError($"[LocalResManager] 解析 CDN InitialFunction.txt 失败,url={initFuncUrl},error={ex}"); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private async UniTask<bool> TryLoadInitialFunctionFromResourcesAsync() |
| | | { |
| | | try |
| | | { |
| | | TextAsset textAsset = await Resources.LoadAsync<TextAsset>("Config/InitialFunction") as TextAsset; |
| | | if (textAsset == null || string.IsNullOrEmpty(textAsset.text)) |
| | | { |
| | | Debug.LogWarning("[LocalResManager] 包内 InitialFunction.txt 不存在或内容为空,将继续启动。"); |
| | | return false; |
| | | } |
| | | |
| | | InitialFunctionConfig.Init(textAsset.text); |
| | | Debug.Log("[LocalResManager] 已使用包内 InitialFunction.txt 初始化启动配置。"); |
| | | return true; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Debug.LogError($"[LocalResManager] 读取包内 InitialFunction.txt 失败,将继续启动。error={ex}"); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private async UniTask InitializeYooAssetAndEnterReadBytesAsync() |
| | | { |
| | | if (Launch.Instance == null) |
| | | { |
| | | Debug.LogError("[LocalResManager] Launch.Instance 为空,无法继续启动流程。"); |
| | | return; |
| | | } |
| | | |
| | | await Launch.Instance.InitYooAssetEarlyAsync(); |
| | | if (YooAssetInitializer.Instance.State != YooAssetInitializer.InitState.Ready) |
| | | { |
| | | Debug.LogError($"[LocalResManager] YooAsset 初始化失败(State={YooAssetInitializer.Instance.State}),无法继续加载资源"); |
| | | return; |
| | | } |
| | | |
| | | // YooAsset 就绪后,加载配置表并显示加载界面 |
| | | InitTable(() => |
| | | { |
| | | // YooAsset 的 Manifest 就绪后,才可以显示加载界面并进入 DLL 读取阶段。 |
| | | InitDefaultLanguage(); |
| | | Launch.Instance.ShowLaunchUI(); |
| | | step = LoadDllStep.ReadBytes; |
| | | }).Forget(); |
| | | }).Forget(); |
| | | } |
| | | else |
| | | |
| | | private void RetryVersionCheck(string message) |
| | | { |
| | | Debug.Log("http 数据通讯: VersionUtility:" + versionUrl + " result:" + versionUrlResult); |
| | | Clock.AlarmAt(DateTime.Now + new TimeSpan(TimeSpan.TicksPerSecond), RequestVersionCheck); |
| | | } |
| | | Debug.LogWarning(message + " 将在1秒后重试..."); |
| | | Clock.AlarmAt(DateTime.Now + TimeSpan.FromSeconds(1), RequestVersionCheck); |
| | | } |
| | | |
| | | private static string NormalizeVersionJson(string raw) |
| | |
| | | // 通过 YooAsset 加载 TextAsset 类型的文本文件。 |
| | | // location: 完整的 YooAsset 路径,如 "Assets/ResourcesOut/Config/InitialFunction.txt" |
| | | // Editor 下直接读物理文件(剥离 Assets/ 前缀后拼 Application.dataPath) |
| | | private async UniTask ReadText(string location, Action<bool, string> OnComplete = null) |
| | | private async UniTask ReadText(string fileName, Action<bool, string> OnComplete = null) |
| | | { |
| | | string content = string.Empty; |
| | | bool result = false; |
| | | |
| | | #if UNITY_EDITOR |
| | | // location 形如 "Assets/ResourcesOut/Config/Foo.txt" → 物理路径 |
| | | var editorPath = Application.dataPath + "/" + location.Substring("Assets/".Length); |
| | | if (File.Exists(editorPath)) |
| | | { |
| | | content = File.ReadAllText(editorPath); |
| | | result = true; |
| | | } |
| | | else |
| | | { |
| | | Debug.LogError($"ReadText: file not found at '{editorPath}'"); |
| | | } |
| | | await UniTask.Yield(); |
| | | #else |
| | | #if UNITY_WEBGL || TEST_BUILD |
| | | Debug.Log($"[LocalResManager][Diag] ReadText via YooAsset location='{location}', state={YooAssetInitializer.Instance.State}"); |
| | | #endif |
| | | var textAsset = await LoadAssetWithRetryAsync<TextAsset>(location); |
| | | string path = "Config/" + fileName; |
| | | |
| | | TextAsset textAsset = await Resources.LoadAsync<TextAsset>(path) as TextAsset; // 预热资源,避免后续首次加载时的额外延迟 |
| | | if (textAsset != null) |
| | | { |
| | | content = textAsset.text; |
| | | result = true; |
| | | Debug.Log($"ReadText '{location}' size:{content.Length}"); |
| | | } |
| | | #endif |
| | | else |
| | | { |
| | | Debug.LogError("LoadResourceFailure " + path); |
| | | } |
| | | |
| | | OnComplete?.Invoke(result, content); |
| | | } |
| | | |
| | |
| | | #endif |
| | | } |
| | | |
| | | await ReadText("Assets/ResourcesOut/Config/InitialFunction.txt", (isOK, value) => |
| | | await ReadText("InitialFunction", (isOK, value) => |
| | | { |
| | | if (isOK) |
| | | { |