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);
|
}
|
|
public async UniTask<T> LoadAssetAsync<T>(string directory, string name) where T : UnityEngine.Object
|
{
|
return await LoadAssetAsync<T>(directory, name, needExt: true);
|
}
|
|
public async UniTask<T> LoadAssetAsync<T>(string directory, string name, bool needExt = true) where T : UnityEngine.Object
|
{
|
var tcs = new UniTaskCompletionSource<T>();
|
LoadAssetAsync<T>(directory, name, (isLoaded, asset) => {
|
if (isLoaded)
|
{
|
tcs.TrySetResult(asset as T);
|
}
|
else
|
{
|
tcs.TrySetException(new Exception($"Failed to load asset: {directory}/{name}"));
|
}
|
}, needExt);
|
return await tcs.Task;
|
}
|
|
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 版本)。
|
/// AB 模式使用 YooAsset RawFile 异步加载,非 AB 模式直接读文件。
|
/// </summary>
|
public async UniTask<string[]> LoadConfigAsync(string name, CancellationToken ct = default)
|
{
|
// AB 模式(含 WebGL): 使用 YooAsset RawFile 加载(配置文件在 YooAsset 沙盒中)
|
if (AssetSource.isUseAssetBundle)
|
{
|
try
|
{
|
var location = $"Assets/ResourcesOut/Config/{name}.txt";
|
var text = await ProjSG.Resource.YooAssetService.Instance.LoadRawFileTextAsync(location, ct);
|
if (!string.IsNullOrEmpty(text))
|
{
|
return text.Split(new[] { "\r\n", "\n" }, System.StringSplitOptions.None);
|
}
|
}
|
catch (System.Exception ex)
|
{
|
UnityEngine.Debug.LogError($"[ResManager] LoadConfigAsync YooAsset failed for '{name}': {ex.Message}");
|
}
|
return System.Array.Empty<string>();
|
}
|
|
// 非 AB 模式: 直接读文件(Editor 开发模式)
|
#if UNITY_EDITOR
|
string path = ResourcesPath.CONFIG_FODLER + "/" + name + ".txt";
|
return await UniTask.RunOnThreadPool(() => File.ReadAllLines(path));
|
#else
|
return System.Array.Empty<string>();
|
#endif
|
}
|
|
}
|