yyl
2026-02-11 3f2cd27c5dfb3b450245bf1a37fc1b3414031c7c
Main/ResModule/ResManager.cs
@@ -6,6 +6,9 @@
using UnityEngine.Video;
using Spine.Unity;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;
using System.Threading;
using ProjSG.Resource;
@@ -127,11 +130,11 @@
#endif
    //needExt 是否需要函数内部添加后缀
    [System.Obsolete("US2: Use LoadAssetAsync<T>(directory, name, needExt) returning UniTask<T> instead.")]
    public T LoadAsset<T>(string directory, string name, bool needExt = true) where T : UnityEngine.Object
    {
        directory = directory.Replace("\\", "/");
        name = name.Replace("\\", "/");
        T asset = null;
        //  特殊处理 因为有一层图集的关系 directory要传入的应该是atlas的名字
        if (typeof(T) == typeof(Sprite))
        {
@@ -165,34 +168,10 @@
        }
        else
        {
            if (!needExt)
            {
                //外部用到的自己加后缀,内部统一去除后缀名
                name = name.Substring(0, name.LastIndexOf("."));
            }
            //TODO: 临时特殊处理打包后的路径读取
            if (directory == "UI" || directory == "UIComp" || directory.StartsWith("Sprite")
            || directory == "Battle/Prefabs" || directory == "Materials")
            {
                directory = "UI/" + directory;
            }
            else if (name == "Hero_001")
            {
                directory = "UI/Hero/SpineRes";
            }
            else if (directory.Contains("Texture"))
            {
                directory = "maps/" + name;
            }
            else if (directory.Contains("Shader"))
            {
                directory = "graphic/shader";
            }
            var assetInfo = new AssetInfo(directory.ToLower(), name.ToLower());
            asset = AssetBundleUtility.Instance.Sync_LoadAsset(assetInfo, typeof(T)) as T;
            // US1: Route through YooAssetService sync wrapper (transitional)
            #pragma warning disable CS0612, CS0618
            asset = YooAssetService.Instance.LoadAssetSync<T>(path);
            #pragma warning restore CS0612, CS0618
        }
        if (asset == null)
@@ -203,6 +182,7 @@
        return asset;
    }
    [System.Obsolete("US2: Use LoadConfigAsync returning UniTask<string[]> instead.")]
    public string[] LoadConfig(string name)
    {
        string path = string.Empty;
@@ -224,7 +204,9 @@
    {
        if (!AssetSource.isUseAssetBundle)
        {
            #pragma warning disable CS0618 // Obsolete — legacy sync fallback
            SpriteAtlas atlas = LoadAsset<SpriteAtlas>("Sprite", atlasName.Replace("Sprite/", ""));
            #pragma warning restore CS0618
            if (null == atlas)
            {
                return null;
@@ -236,6 +218,7 @@
    }
    //needExt 是否需要函数内部添加后缀
    [System.Obsolete("US2: Use LoadAssetAsync<T>(directory, name, needExt) returning UniTask<T> instead.")]
    public void LoadAssetAsync<T>(string directory, string name, Action<bool, UnityEngine.Object> callBack, bool needExt = true) where T : UnityEngine.Object
    {
        directory = directory.Replace("\\", "/");
@@ -253,22 +236,19 @@
    private void LoadSpriteAsync<T>(string atlasName, string spriteName, Action<bool, UnityEngine.Object> callBack) where T : UnityEngine.Object
    {
#if !UNITY_EDITOR
        LoadAssetAsync<SpriteAtlas>(atlasName, spriteName, (isLoaded, atlas) => {
            if (isLoaded)
        if (!AssetSource.isUseAssetBundle)
        {
            // Editor 模式下可直接加载 sprite
            LoadAssetAsyncInternal<T>(atlasName, spriteName, callBack);
        }
        else
        {
            // AB 模式下直接加载单独的 Sprite 文件(YooAsset 自动处理 SpriteAtlas 依赖)
            LoadAssetAsyncInternal<Sprite>(atlasName, spriteName, (isLoaded, sprite) =>
            {
                SpriteAtlas _atlas = atlas as SpriteAtlas;
                callBack?.Invoke(isLoaded, _atlas.GetSprite(spriteName));
            }
            else
            {
                callBack?.Invoke(false, null);
            }
        });
#else
        //  编辑器下可以直接加载没啥问题
        LoadAssetAsyncInternal<T>(atlasName, spriteName, callBack);
#endif
                callBack?.Invoke(isLoaded, sprite);
            });
        }
    }
    private void LoadAssetAsyncInternal<T>(string directory, string name, Action<bool, UnityEngine.Object> callBack, bool needExt = true) where T : UnityEngine.Object
@@ -284,24 +264,36 @@
        }
        else
        {
            var assetInfo = new AssetInfo(directory.ToLower(), name.ToLower());
            AssetBundleUtility.Instance.Co_LoadAsset(assetInfo, callBack);
            // US1: Route through YooAssetService async
            CoLoadViaYooAsset<T>(path, callBack).Forget();
        }
    }
    public void UnloadAsset(string assetBundleName, string assetName)
    private async UniTaskVoid CoLoadViaYooAsset<T>(string path, Action<bool, UnityEngine.Object> callBack, CancellationToken ct = default) where T : UnityEngine.Object
    {
        if (!AssetSource.isUseAssetBundle)
            return;
        AssetBundleUtility.Instance.UnloadAsset(assetBundleName, assetName);
        try
        {
            var asset = await YooAssetService.Instance.LoadAssetAsync<T>(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)
    {
        if (!AssetSource.isUseAssetBundle)
            return;
        AssetBundleUtility.Instance.UnloadAssetBundle(assetBundleName, unloadAllLoadedObjects, includeDependenice);
        // US1: AssetBundleUtility unload no longer effective since assets loaded via YooAsset.
    }
    public string GetAssetFilePath(string _assetKey)
@@ -315,5 +307,111 @@
        return path;
    }
    // ====================================================================
    // US1: New UniTask-based async variants
    // ====================================================================
    /// <summary>
    /// 异步加载资源(UniTask 版本,US1 新增)。
    /// </summary>
    public async UniTask<T> LoadAssetAsync<T>(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<T>(path);
#else
            return null;
#endif
        }
        return await YooAssetService.Instance.LoadAssetAsync<T>(path, ct: ct);
    }
    /// <summary>
    /// US4: 异步加载资源并走缓存层(缓存命中直接返回,未命中则加载并缓存)。
    /// </summary>
    public async UniTask<T> LoadAssetCachedAsync<T>(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<T>(path);
#else
            return null;
#endif
        }
        return await ResourceCacheManager.Instance.GetOrLoadAsync<T>(path);
    }
    private async UniTask<Sprite> LoadSpriteAsyncUniTask(string atlasName, string spriteName, CancellationToken ct = default)
    {
        if (!AssetSource.isUseAssetBundle)
        {
            var atlas = await LoadAssetAsync<SpriteAtlas>("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<Sprite>(path, ct: ct);
        }
    }
    /// <summary>
    /// 异步加载配置文件(UniTask 版本)。
    /// WebGL 平台使用 YooAsset RawFile 异步加载,其他平台使用线程池。
    /// </summary>
    public async UniTask<string[]> 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<string>();
#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
    }
}