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<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
|
// {
|
// // 已禁用同步加载,强制业务全部走异步API。
|
// throw new NotSupportedException("同步资源加载已禁用,请使用异步接口");
|
// }
|
|
// [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);
|
// }
|
|
|
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
|
{
|
return await LoadAssetAsync<T>(directory, name, needExt, CancellationToken.None);
|
}
|
|
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);
|
}
|
}
|
|
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
|
// ====================================================================
|
|
/// <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("\\", "/");
|
// 音频文件扩展名统一转小写,避免 .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<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("\\", "/");
|
// 音频文件扩展名统一转小写,避免 .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<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("\\", "/");
|
var sprite = await YooAssetService.Instance.LoadAssetAsync<Sprite>(path, ct: ct);
|
#if UNITY_WEBGL
|
if (sprite == null)
|
Debug.LogWarning($"[ResManager][WebGL-Diag] Sprite load returned NULL: path={path}");
|
#endif
|
return sprite;
|
}
|
}
|
|
/// <summary>
|
/// 异步加载配置文件(UniTask 版本)。
|
/// AB 模式使用 YooAsset RawFile 异步加载,非 AB 模式直接读文件。
|
/// </summary>
|
public async UniTask<string[]> 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<TextAsset>
|
// .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<TextAsset>(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<string>();
|
}
|
|
// 非 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<string>();
|
#endif
|
}
|
|
}
|