using UnityEngine; using System.Collections.Generic; using System; using UnityEngine.U2D; using System.IO; using UnityEngine.Video; using Spine.Unity; using UnityEngine.UI; using Cysharp.Threading.Tasks; using System.Threading; using ProjSG.Resource; using YooAsset; #if UNITY_EDITOR using UnityEditor; #endif using LaunchCommon; public class ResManager : Singleton { //不下载时本地安卓测试路径 // public string assetBundlesPath = Application.dataPath + "/../AssetBundles/Android/"; // public static string bytesFolderName = "logicbytes/"; public string StreamingAssetPath { get { return ResourcesPath.Instance.StreamingAssetPath; } } public string ExternalStorePath { get { return ResourcesPath.Instance.ExternalStorePath; } } //用于editor 下的资源路径 public string ResourcesOutPath { get { return ResourcesPath.ResourcesOutPath; } } public string CONFIG_FODLER { get { return ResourcesPath.CONFIG_FODLER; } } public string ResourcesOutAssetPath { get { return ResourcesPath.ResourcesOutAssetPath; } } private static readonly Dictionary fileExtensionDict = new Dictionary() { {typeof(GameObject), "prefab"}, {typeof(Sprite), "png"}, {typeof(Texture2D), "jpg"}, {typeof(Texture), "jpg"}, {typeof(Shader), "shader"}, {typeof(TextAsset), "txt"}, {typeof(AudioClip), "wav"}, {typeof(Font), "ttf"}, {typeof(Material), "mat"}, {typeof(VideoClip), "mp4"}, {typeof(SpriteAtlas), "spriteatlasv2"}, {typeof(SkeletonDataAsset), "asset"}, }; public void Init() { } public void Release() { } private string GetExtension(Type type) { if (fileExtensionDict.TryGetValue(type, out string extension)) return "." + extension; else { Debug.LogErrorFormat("GetExtension() => 不支持的资源类型: {0}.", type.Name); return ""; } } #if UNITY_EDITOR public string GetFullDirectory(string directory) { string fullDirectory = Path.Combine(ResourcesOutPath, directory); return fullDirectory; } public string FindFilePath(string directory, string fileName) { string[] files = Directory.GetFiles(GetFullDirectory(directory), fileName, SearchOption.AllDirectories); if (files.Length > 0) { return files[0]; } return string.Empty; } public string GetRelativePath(string absolutePath) { string relativePath = absolutePath.Replace(ResourcesOutPath, "Assets/ResourcesOut/"); return relativePath; } #endif // // needExt 是否需要函数内部添加后缀 // [System.Obsolete("US2: Use LoadAssetAsync(directory, name, needExt) returning UniTask instead.")] // public T LoadAsset(string directory, string name, bool needExt = true) where T : UnityEngine.Object // { // directory = directory.Replace("\\", "/"); // name = name.Replace("\\", "/"); // // 特殊处理 因为有一层图集的关系 directory要传入的应该是atlas的名字 // if (typeof(T) == typeof(Sprite)) // { // return LoadSprite(directory, name) as T; // } // else if (typeof(T) == typeof(SkeletonDataAsset)) // { // //文件目录调整,name中包含了路径 // if (name.Contains("/")) // { // directory += name.Substring(0, name.LastIndexOf("/")); // name = name.Substring(name.LastIndexOf("/") + 1); // } // } // return LoadAssetInternal(directory, name, needExt); // } // //needExt 是否需要函数内部添加后缀 // private T LoadAssetInternal(string directory, string name, bool needExt = true) where T : UnityEngine.Object // { // // 已禁用同步加载,强制业务全部走异步API。 // throw new NotSupportedException("同步资源加载已禁用,请使用异步接口"); // } // [System.Obsolete("US2: Use LoadConfigAsync returning UniTask instead.")] // public string[] LoadConfig(string name) // { // string path = string.Empty; // #if UNITY_EDITOR // if (!AssetSource.isUseAssetBundle) // { // path = ResourcesPath.CONFIG_FODLER + "/" + name + ".txt"; // } // else // #endif // { // path = AssetVersionUtility.GetAssetFilePath($"config/{name}.txt"); // } // return File.ReadAllLines(path); // } public async UniTask LoadAssetAsync(string directory, string name) where T : UnityEngine.Object { return await LoadAssetAsync(directory, name, needExt: true); } public async UniTask LoadAssetAsync(string directory, string name, bool needExt = true) where T : UnityEngine.Object { return await LoadAssetAsync(directory, name, needExt, CancellationToken.None); } private void LoadSpriteAsync(string atlasName, string spriteName, Action callBack) where T : UnityEngine.Object { if (!AssetSource.isUseAssetBundle) { // Editor 模式下可直接加载 sprite LoadAssetAsyncInternal(atlasName, spriteName, callBack); } else { // AB 模式下直接加载单独的 Sprite 文件(YooAsset 自动处理 SpriteAtlas 依赖) LoadAssetAsyncInternal(atlasName, spriteName, (isLoaded, sprite) => { callBack?.Invoke(isLoaded, sprite); }); } } private void LoadAssetAsyncInternal(string directory, string name, Action callBack, bool needExt = true) where T : UnityEngine.Object { var path = string.Concat($"Assets/ResourcesOut/{directory}/{name}", (needExt ? GetExtension(typeof(T)) : "")).Replace("//", "/"); if (!AssetSource.isUseAssetBundle) { #if UNITY_EDITOR var asset = UnityEditor.AssetDatabase.LoadAssetAtPath(path); callBack?.Invoke(asset != null, asset); #endif } else { // US1: Route through YooAssetService async CoLoadViaYooAsset(path, callBack).Forget(); } } private async UniTaskVoid CoLoadViaYooAsset(string path, Action callBack, CancellationToken ct = default) where T : UnityEngine.Object { try { var asset = await YooAssetService.Instance.LoadAssetAsync(path, ct: ct); callBack?.Invoke(asset != null, asset); } catch (Exception ex) { Debug.LogError($"[ResManager] Async load via YooAsset failed: {ex.Message}"); callBack?.Invoke(false, null); } } public void UnloadAsset(string directory, string assetName) { directory = directory.Replace("\\", "/").TrimEnd('/'); assetName = assetName.Replace("\\", "/"); string path = ($"Assets/ResourcesOut/{directory}/{assetName}").Replace("//", "/"); YooAssetService.Instance.UnloadAsset(path); } public string GetAssetFilePath(string _assetKey) { var path = Path.Combine(ExternalStorePath, _assetKey); if (!File.Exists(path)) { path = Path.Combine(StreamingAssetPath, _assetKey); } return path; } // ==================================================================== // US1: New UniTask-based async variants // ==================================================================== /// /// 异步加载资源(UniTask 版本,US1 新增)。 /// public async UniTask LoadAssetAsync(string directory, string name, bool needExt = true, CancellationToken ct = default) where T : UnityEngine.Object { directory = directory.Replace("\\", "/"); name = name.Replace("\\", "/"); if (typeof(T) == typeof(Sprite)) { return await LoadSpriteAsyncUniTask(directory, name, ct) as T; } if (typeof(T) == typeof(SkeletonDataAsset)) { if (name.Contains("/")) { directory += name.Substring(0, name.LastIndexOf("/")); name = name.Substring(name.LastIndexOf("/") + 1); } } var path = ($"Assets/ResourcesOut/{directory}/{name}" + (needExt ? GetExtension(typeof(T)) : "")) .Replace("//", "/").Trim().Replace("\\", "/"); // 音频文件扩展名统一转小写,避免 .WAV/.MP3 大小写不匹配 if (typeof(T) == typeof(AudioClip)) { var pathExt = System.IO.Path.GetExtension(path); if (!string.IsNullOrEmpty(pathExt) && pathExt != pathExt.ToLower()) path = path.Substring(0, path.Length - pathExt.Length) + pathExt.ToLower(); } if (!AssetSource.isUseAssetBundle) { #if UNITY_EDITOR return UnityEditor.AssetDatabase.LoadAssetAtPath(path); #else return null; #endif } return await YooAssetService.Instance.LoadAssetAsync(path, ct: ct); } /// /// US4: 异步加载资源并走缓存层(缓存命中直接返回,未命中则加载并缓存)。 /// public async UniTask LoadAssetCachedAsync(string directory, string name, bool needExt = true, CancellationToken ct = default) where T : UnityEngine.Object { directory = directory.Replace("\\", "/"); name = name.Replace("\\", "/"); var path = ($"Assets/ResourcesOut/{directory}/{name}" + (needExt ? GetExtension(typeof(T)) : "")) .Replace("//", "/").Trim().Replace("\\", "/"); // 音频文件扩展名统一转小写,避免 .WAV/.MP3 大小写不匹配 if (typeof(T) == typeof(AudioClip)) { var pathExt2 = System.IO.Path.GetExtension(path); if (!string.IsNullOrEmpty(pathExt2) && pathExt2 != pathExt2.ToLower()) path = path.Substring(0, path.Length - pathExt2.Length) + pathExt2.ToLower(); } if (!AssetSource.isUseAssetBundle) { #if UNITY_EDITOR return UnityEditor.AssetDatabase.LoadAssetAtPath(path); #else return null; #endif } return await ResourceCacheManager.Instance.GetOrLoadAsync(path); } private async UniTask LoadSpriteAsyncUniTask(string atlasName, string spriteName, CancellationToken ct = default) { if (!AssetSource.isUseAssetBundle) { var atlas = await LoadAssetAsync("Sprite", atlasName.Replace("Sprite/", ""), ct: ct); return atlas?.GetSprite(spriteName); } else { var path = $"Assets/ResourcesOut/{atlasName}/{spriteName}.png" .Replace("//", "/").Trim().Replace("\\", "/"); var sprite = await YooAssetService.Instance.LoadAssetAsync(path, ct: ct); #if UNITY_WEBGL if (sprite == null) Debug.LogWarning($"[ResManager][WebGL-Diag] Sprite load returned NULL: path={path}"); #endif return sprite; } } /// /// 异步加载配置文件(UniTask 版本)。 /// AB 模式使用 YooAsset RawFile 异步加载,非 AB 模式直接读文件。 /// public async UniTask LoadConfigAsync(string name, bool needExt = true, CancellationToken ct = default) { // AB 模式(含 WebGL): 使用 YooAsset 加载配置文件 if (AssetSource.isUseAssetBundle) { // 判断一下是否原来已经包含.txt 配合 needExt 做兼容,避免调用方传入重复后缀导致路径错误 if (name.EndsWith(".txt") && needExt) { name = name.Substring(0, name.Length - 4); } var location = $"Assets/ResourcesOut/Config/{name}" + (needExt ? ".txt" : ""); try { #if UNITY_WEBGL // WebGL: WebServerFileSystem 不支持 LoadRawFileAsync,改用 LoadAssetAsync // .txt 文件在 Unity 中以 TextAsset 形式导入,WebGL 支持此加载方式 var asset = await ProjSG.Resource.YooAssetService.Instance.LoadAssetAsync( location, typeof(UnityEngine.TextAsset), 0, ct) as UnityEngine.TextAsset; if (asset != null && !string.IsNullOrEmpty(asset.text)) return asset.text.Split(new[] { "\r\n", "\n" }, System.StringSplitOptions.None); #else var textAsset = await ProjSG.Resource.YooAssetService.Instance.LoadAssetAsync(location, 0, ct); if (textAsset != null && !string.IsNullOrEmpty(textAsset.text)) return textAsset.text.Split(new[] { "\r\n", "\n" }, System.StringSplitOptions.None); #endif } catch (System.Exception ex) { UnityEngine.Debug.LogError($"[ResManager] LoadConfigAsync YooAsset failed for '{name}': {ex.Message}"); } return System.Array.Empty(); } // 非 AB 模式: 直接读文件(Editor 开发模式) #if UNITY_EDITOR string path = ResourcesPath.CONFIG_FODLER + "/" + name + (needExt ? ".txt" : ""); return await UniTask.RunOnThreadPool(() => File.ReadAllLines(path)); #else return System.Array.Empty(); #endif } }