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; #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 { T asset = null; var path = ($"Assets/ResourcesOut/{directory}/{name}" + (needExt ? GetExtension(typeof(T)) : "")).Replace("//", "/").Trim().Replace("\\", "/"); if (!AssetSource.isUseAssetBundle) { #if UNITY_EDITOR asset = UnityEditor.AssetDatabase.LoadAssetAtPath(path); #endif } else { // US1: Route through YooAssetService sync wrapper (transitional) #pragma warning disable CS0612, CS0618 asset = YooAssetService.Instance.LoadAssetSync(path); #pragma warning restore CS0612, CS0618 } if (asset == null) { Debug.LogErrorFormat("LoadAsset() => 加载不到资源: {0}", path); } return asset; } [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); } private Sprite LoadSprite(string atlasName, string spriteName) { if (!AssetSource.isUseAssetBundle) { #pragma warning disable CS0618 // Obsolete — legacy sync fallback SpriteAtlas atlas = LoadAsset("Sprite", atlasName.Replace("Sprite/", "")); #pragma warning restore CS0618 if (null == atlas) { return null; } return atlas.GetSprite(spriteName); } else return LoadAssetInternal(atlasName, spriteName); } //needExt 是否需要函数内部添加后缀 [System.Obsolete("US2: Use LoadAssetAsync(directory, name, needExt) returning UniTask instead.")] public void LoadAssetAsync(string directory, string name, Action callBack, bool needExt = true) where T : UnityEngine.Object { directory = directory.Replace("\\", "/"); name = name.Replace("\\", "/"); // 特殊处理 因为有一层图集的关系 directory要传入的应该是atlas的名字 if (typeof(T) == typeof(Sprite)) { LoadSpriteAsync(directory, name, callBack); return; } LoadAssetAsyncInternal(directory, name, callBack, needExt); } 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); } } [System.Obsolete("US1: Use YooAssetService.ReleaseHandle or UnloadUnusedAssetsAsync instead.")] public void UnloadAsset(string assetBundleName, string assetName) { // US1: AssetBundleUtility unload no longer effective since assets loaded via YooAsset. // Proper unload handled via YooAssetService handle-based release. } [System.Obsolete("US1: Use YooAssetService.UnloadUnusedAssetsAsync instead.")] public void UnloadAssetBundle(string assetBundleName, bool unloadAllLoadedObjects, bool includeDependenice) { // US1: AssetBundleUtility unload no longer effective since assets loaded via YooAsset. } 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("\\", "/"); 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("\\", "/"); 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("\\", "/"); return await YooAssetService.Instance.LoadAssetAsync(path, ct: ct); } } /// /// 异步加载配置文件(UniTask 版本)。 /// WebGL 平台使用 YooAsset RawFile 异步加载,其他平台使用线程池。 /// public async UniTask LoadConfigAsync(string name, CancellationToken ct = default) { #if UNITY_WEBGL && !UNITY_EDITOR // WebGL 不支持多线程和 File.ReadAllLines,使用 YooAsset RawFile try { var text = await ProjSG.Resource.YooAssetService.Instance.LoadRawFileTextAsync($"config/{name}", ct); if (!string.IsNullOrEmpty(text)) { return text.Split(new[] { "\r\n", "\n" }, System.StringSplitOptions.None); } } catch (System.Exception ex) { UnityEngine.Debug.LogError($"[ResManager] LoadConfigAsync WebGL fallback failed for '{name}': {ex.Message}"); } return System.Array.Empty(); #else #pragma warning disable CS0618 // LoadConfig is obsolete — used here as thread-pool fallback for non-WebGL return await UniTask.RunOnThreadPool(() => LoadConfig(name)); #pragma warning restore CS0618 #endif } }