TH1/Unity/Assets/Scripts/TH1_AOT/ResourceLoader.cs
2026-06-11 00:34:50 +08:00

319 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using YooAsset;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace TH1Resource
{
public static class ResourceLoader
{
public const string BundleResourceRoot = "Assets/BundleResources";
private static readonly Dictionary<string, AssetHandle> Handles = new Dictionary<string, AssetHandle>();
private static readonly string[] DefaultExtensions =
{
".prefab",
".asset",
".png",
".jpg",
".jpeg",
".bytes",
".txt",
".json",
".csv",
".mat",
".anim",
".controller",
".wav",
".WAV",
".mp3",
".MP3",
".ogg",
".OGG",
".spriteatlas",
".renderTexture",
".shader",
".compute"
};
public static T Load<T>(string location) where T : UnityEngine.Object
{
var normalizedLocation = NormalizeLocation(location);
if (string.IsNullOrEmpty(normalizedLocation))
{
Debug.LogError($"[TH1.YooAsset] Empty resource location for {typeof(T).Name}.");
return null;
}
#if UNITY_EDITOR
if (!Application.isPlaying)
{
return LoadFromAssetDatabase<T>(normalizedLocation);
}
#endif
if (!ResourceManager.IsInitialized)
{
Debug.LogError($"[TH1.YooAsset] Resource package is not initialized. location={normalizedLocation}, type={typeof(T).Name}");
return null;
}
if (!TryResolveRuntimeLocation<T>(normalizedLocation, out var resolvedLocation))
{
Debug.LogError($"[TH1.YooAsset] Missing resource location: {normalizedLocation}, type={typeof(T).Name}");
return null;
}
var key = $"{typeof(T).FullName}:{resolvedLocation}";
if (Handles.TryGetValue(key, out var cachedHandle))
{
var cachedAsset = cachedHandle.GetAssetObject<T>();
if (cachedAsset != null) return cachedAsset;
cachedHandle.Release();
Handles.Remove(key);
}
try
{
var handle = YooAssets.LoadAssetSync<T>(resolvedLocation);
var asset = handle.GetAssetObject<T>();
if (asset == null)
{
handle.Release();
Debug.LogError($"[TH1.YooAsset] Loaded null resource: {resolvedLocation}, type={typeof(T).Name}");
return null;
}
Handles[key] = handle;
return asset;
}
catch (Exception e)
{
Debug.LogError($"[TH1.YooAsset] Load failed: {resolvedLocation}, type={typeof(T).Name}\n{e}");
return null;
}
}
public static void ReleaseAll()
{
foreach (var handle in Handles.Values)
{
handle.Release();
}
Handles.Clear();
}
private static string NormalizeLocation(string location)
{
if (string.IsNullOrWhiteSpace(location)) return string.Empty;
var normalized = location.Replace('\\', '/').Trim();
if (normalized.StartsWith("Assets/BundleResources/", StringComparison.OrdinalIgnoreCase))
{
normalized = normalized.Substring("Assets/BundleResources/".Length);
}
else if (normalized.StartsWith("Assets/Resources/", StringComparison.OrdinalIgnoreCase))
{
normalized = normalized.Substring("Assets/Resources/".Length);
}
return normalized.TrimStart('/');
}
private static bool TryResolveRuntimeLocation<T>(string location, out string resolvedLocation) where T : UnityEngine.Object
{
foreach (var candidate in EnumerateRuntimeLocationCandidates<T>(location))
{
if (YooAssets.CheckLocationValid(candidate))
{
resolvedLocation = candidate;
return true;
}
}
resolvedLocation = location;
return false;
}
private static IEnumerable<string> EnumerateRuntimeLocationCandidates<T>(string location) where T : UnityEngine.Object
{
var emitted = new HashSet<string>(StringComparer.Ordinal);
foreach (var candidate in EnumerateLocationAndAssetPath(location))
{
if (emitted.Add(candidate))
{
yield return candidate;
}
}
if (Path.HasExtension(location)) yield break;
foreach (var extensionLocation in EnumerateLocationCandidates<T>(location))
{
foreach (var candidate in EnumerateLocationAndAssetPath(extensionLocation))
{
if (emitted.Add(candidate))
{
yield return candidate;
}
}
}
}
private static IEnumerable<string> EnumerateLocationCandidates<T>(string location) where T : UnityEngine.Object
{
var emitted = new HashSet<string>(StringComparer.Ordinal);
foreach (var extension in GetPreferredExtensions<T>())
{
if (emitted.Add(extension))
{
yield return location + extension;
}
}
foreach (var extension in DefaultExtensions)
{
if (emitted.Add(extension))
{
yield return location + extension;
}
}
}
private static IEnumerable<string> EnumerateLocationAndAssetPath(string location)
{
yield return location;
if (!location.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase))
{
yield return $"{BundleResourceRoot}/{location}";
}
}
private static IEnumerable<string> GetPreferredExtensions<T>() where T : UnityEngine.Object
{
var type = typeof(T);
if (type == typeof(GameObject))
{
yield return ".prefab";
}
else if (type == typeof(Sprite) || type == typeof(Texture) || type == typeof(Texture2D))
{
yield return ".png";
yield return ".jpg";
yield return ".jpeg";
}
else if (type == typeof(TextAsset))
{
yield return ".bytes";
yield return ".txt";
yield return ".json";
yield return ".csv";
}
else if (type == typeof(AudioClip))
{
yield return ".wav";
yield return ".WAV";
yield return ".mp3";
yield return ".MP3";
yield return ".ogg";
yield return ".OGG";
}
else if (type == typeof(AnimationClip))
{
yield return ".anim";
}
else if (type == typeof(Material))
{
yield return ".mat";
}
else if (type == typeof(RuntimeAnimatorController))
{
yield return ".controller";
}
else if (type == typeof(Shader))
{
yield return ".shader";
}
else if (typeof(ScriptableObject).IsAssignableFrom(type))
{
yield return ".asset";
}
}
#if UNITY_EDITOR
private static T LoadFromAssetDatabase<T>(string location) where T : UnityEngine.Object
{
if (TryResolveEditorAssetPath<T>(location, out var resolvedAssetPath))
{
return AssetDatabase.LoadAssetAtPath<T>(resolvedAssetPath);
}
var targetPath = $"{BundleResourceRoot}/{location}".Replace('\\', '/');
var directory = Path.GetDirectoryName(targetPath)?.Replace('\\', '/');
var fileName = Path.GetFileName(targetPath);
if (string.IsNullOrEmpty(directory) || !AssetDatabase.IsValidFolder(directory))
{
Debug.LogError($"[TH1.YooAsset] Editor resource folder missing: {directory}, location={location}");
return null;
}
var guids = AssetDatabase.FindAssets(fileName, new[] { directory });
foreach (var guid in guids)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
if (RemoveExtension(assetPath).Equals(RemoveExtension(targetPath), StringComparison.OrdinalIgnoreCase))
{
var asset = AssetDatabase.LoadAssetAtPath<T>(assetPath);
if (asset != null) return asset;
}
}
Debug.LogError($"[TH1.YooAsset] Editor resource missing: {location}, type={typeof(T).Name}");
return null;
}
private static bool TryResolveEditorAssetPath<T>(string location, out string assetPath) where T : UnityEngine.Object
{
var targetPath = $"{BundleResourceRoot}/{location}".Replace('\\', '/');
if (Path.HasExtension(targetPath) && File.Exists(targetPath))
{
assetPath = targetPath;
return true;
}
if (!Path.HasExtension(targetPath))
{
foreach (var candidate in EnumerateLocationCandidates<T>(targetPath))
{
if (File.Exists(candidate))
{
assetPath = candidate.Replace('\\', '/');
return true;
}
}
}
assetPath = targetPath;
return false;
}
private static string RemoveExtension(string assetPath)
{
var extension = Path.GetExtension(assetPath);
return string.IsNullOrEmpty(extension)
? assetPath.Replace('\\', '/')
: assetPath.Substring(0, assetPath.Length - extension.Length).Replace('\\', '/');
}
#endif
}
}