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<ResManager>
|
{
|
|
|
//不下载时本地安卓测试路径
|
// 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<Type, string> fileExtensionDict = new Dictionary<Type, string>()
|
{
|
{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<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("\\", "/");
|
// 特殊处理 因为有一层图集的关系 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<T>(directory, name, needExt);
|
}
|
|
//needExt 是否需要函数内部添加后缀
|
private T LoadAssetInternal<T>(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<T>(path);
|
#endif
|
}
|
else
|
{
|
// 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)
|
{
|
Debug.LogErrorFormat("LoadAsset() => 加载不到资源: {0}", path);
|
}
|
|
return asset;
|
}
|
|
[System.Obsolete("US2: Use LoadConfigAsync returning UniTask<string[]> 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<SpriteAtlas>("Sprite", atlasName.Replace("Sprite/", ""));
|
#pragma warning restore CS0618
|
if (null == atlas)
|
{
|
return null;
|
}
|
return atlas.GetSprite(spriteName);
|
}
|
else
|
return LoadAssetInternal<Sprite>(atlasName, spriteName);
|
}
|
|
//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("\\", "/");
|
name = name.Replace("\\", "/");
|
|
// 特殊处理 因为有一层图集的关系 directory要传入的应该是atlas的名字
|
if (typeof(T) == typeof(Sprite))
|
{
|
LoadSpriteAsync<T>(directory, name, callBack);
|
return;
|
}
|
|
LoadAssetAsyncInternal<T>(directory, name, callBack, needExt);
|
}
|
|
private void LoadSpriteAsync<T>(string atlasName, string spriteName, Action<bool, UnityEngine.Object> callBack) where T : UnityEngine.Object
|
{
|
if (!AssetSource.isUseAssetBundle)
|
{
|
// Editor 模式下可直接加载 sprite
|
LoadAssetAsyncInternal<T>(atlasName, spriteName, callBack);
|
}
|
else
|
{
|
// AB 模式下直接加载单独的 Sprite 文件(YooAsset 自动处理 SpriteAtlas 依赖)
|
LoadAssetAsyncInternal<Sprite>(atlasName, spriteName, (isLoaded, sprite) =>
|
{
|
callBack?.Invoke(isLoaded, sprite);
|
});
|
}
|
}
|
|
private void LoadAssetAsyncInternal<T>(string directory, string name, Action<bool, UnityEngine.Object> 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<T>(path);
|
callBack?.Invoke(asset != null, asset);
|
#endif
|
}
|
else
|
{
|
// US1: Route through YooAssetService async
|
CoLoadViaYooAsset<T>(path, callBack).Forget();
|
}
|
}
|
|
private async UniTaskVoid CoLoadViaYooAsset<T>(string path, Action<bool, UnityEngine.Object> callBack, CancellationToken ct = default) where T : UnityEngine.Object
|
{
|
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)
|
{
|
// 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
|
// ====================================================================
|
|
/// <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
|
}
|
|
}
|