Files
EnglewoodLAB/Assets/Scripts/UVC/Util/ResourceManager.cs

187 lines
8.8 KiB
C#

#nullable enable
using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
namespace UVC.Util
{
/// <summary>
/// Unity 리소스를 관리하고 비동기적으로 로드하는 기능을 제공합니다.
/// </summary>
/// <remarks> <see cref="ResourceManager"/> 클래스는 로드된 리소스의 내부 캐시를 유지하여
/// 후속 액세스를 최적화합니다. 리소스는 키별로 비동기적으로 로드하거나 키 목록을 사용하여 대량으로 로드할 수 있습니다.
///</remarks>
public static class ResourceManager
{
private static Dictionary<string, UnityEngine.Object> _resources = new();
/// <summary>
/// 문자열 키로 매핑된 리소스가 포함된 읽기 전용 사전을 가져옵니다.
/// </summary>
public static IReadOnlyDictionary<string, UnityEngine.Object> get => _resources;
/// <summary>
/// 지정된 경로에서 지정된 유형의 리소스를 비동기적으로 로드합니다.
/// </summary>
/// <remarks>이 메서드는 Unity의 <see cref="UnityEngine.Resources.LoadAsync{T}(string)"/>를 사용하여
/// 리소스를 비동기적으로 로드합니다. 지정된 리소스가 지정된 경로에 있는지 확인하여
/// <see langword="null"/>을 반환하지 않도록 합니다.</remarks>
/// <typeparam name="T">로드할 리소스의 유형입니다. <see cref="UnityEngine.Object"/>에서 파생되어야 합니다.</typeparam>
/// <param name="path">리소스 폴더 내 리소스 경로입니다. 이 경로는 리소스 폴더를 기준으로 하며
/// 파일 확장자를 포함하지 않아야 합니다.</param>
/// <returns>비동기 작업을 나타내는 작업입니다. 결과에는 로드된 리소스 유형이 포함됩니다.
/// <typeparamref name="T"/> (리소스가 발견되면); 그렇지 않으면 <see langword="null"/>.</returns>
public static async UniTask<T?> LoadOnlyAsync<T>(string path) where T : UnityEngine.Object
{
var request = Resources.LoadAsync<T>(path);
await request;
return request.asset as T;
}
/// <summary>
/// Unity 리소스 시스템에서 지정된 유형의 리소스를 비동기적으로 로드합니다.
/// </summary>
/// <remarks>리소스가 이미 캐시된 경우, 새로운 로드 작업을 수행하지 않고 즉시 반환됩니다.
/// 그렇지 않은 경우, 리소스는 비동기적으로 로드되어 나중에 사용할 수 있도록 캐시에 추가됩니다.
///</remarks>
/// <typeparam name="T">로드할 리소스의 유형입니다. <see cref="UnityEngine.Object"/>의 하위 클래스여야 합니다.</typeparam>
/// <param name="key">Unity 리소스 시스템에서 리소스를 찾는 데 사용되는 키 또는 경로입니다.</param>
/// <param name="callback">로드된 리소스와 함께 호출할 선택적 콜백입니다. 콜백은 로드된 리소스를
/// <typeparamref name="T"/> 또는 <see langword="null"/>(리소스를 로드할 수 없는 경우) 유형으로 받습니다.
/// <returns>비동기 작업을 나타내는 작업입니다. 작업 결과는
/// <typeparamref name="T"/> 또는 <see langword="null"/>(리소스를 로드할 수 없는 경우) 유형으로 로드된 리소스입니다.</returns>
public static async UniTask<T?> LoadAsync<T>(string key, Action<T?>? callback = null) where T : UnityEngine.Object
{
T? t = null;
if (_resources.TryGetValue(key, out var resource))
{
t = resource as T;
callback?.Invoke(t);
return t;
}
var operation = Resources.LoadAsync<T>(key);
await operation;
_resources.TryAdd(operation.asset.name, operation.asset);
t = operation.asset as T;
callback?.Invoke(t);
return t;
}
/// <summary>
/// 지정된 프리팹 경로 목록에서 모든 에셋을 비동기적으로 로드합니다.
/// </summary>
/// <remarks>이 메서드는 프리팹 경로 목록을 순차적으로 처리하여 각 에셋을
/// 비동기적으로 로드합니다. 콜백이 제공된 경우, 로드 작업의 진행 상황을 추적하는 데 사용할 수 있습니다.
///</remarks>
/// <typeparam name="T">로드할 에셋의 유형입니다. <see cref="UnityEngine.Object"/>에서 파생되어야 합니다.</typeparam>
/// <param name="prefabPath">로드할 프리팹 경로 목록입니다.</param>
/// <param name="callback">각 에셋이 로드된 후 호출되는 선택적 콜백입니다. 이 콜백은 로드된 에셋의 경로,
/// 현재 로드된 에셋의 개수, 그리고 로드할 에셋의 총 개수를 받습니다.</param>
public static async void LoadAllAsync<T>(List<string> prefabPath, Action<string, int, int>? callback = null) where T : UnityEngine.Object
{
int loadCount = 0;
int totalCount = prefabPath.Count;
// preloadKeys 배열의 주소들을 로드
foreach (var key in prefabPath)
{
await LoadAsync<T>(key);
loadCount++;
callback?.Invoke(key, loadCount, totalCount);
}
}
/// <summary>
/// 파일 시스템 경로 또는 Resources 경로에서 Sprite를 비동기적으로 로드합니다.
/// </summary>
/// <remarks>
/// 경로가 절대 경로(드라이브 문자 또는 /로 시작)인 경우 파일 시스템에서 로드하고,
/// 그렇지 않으면 Resources 폴더에서 로드합니다.
/// StreamingAssets 또는 persistentDataPath 경로를 지원합니다.
/// </remarks>
/// <param name="path">이미지 파일 경로 (파일 시스템 절대 경로 또는 Resources 상대 경로)</param>
/// <returns>로드된 Sprite, 실패 시 null</returns>
public static async UniTask<Sprite?> LoadSpriteAsync(string path)
{
if (string.IsNullOrEmpty(path)) return null;
// 파일 시스템 절대 경로인지 확인
bool isAbsolutePath = Path.IsPathRooted(path);
if (isAbsolutePath)
{
return await LoadSpriteFromFileAsync(path);
}
else
{
// 먼저 Sprite로 로드 시도
var sprite = await LoadOnlyAsync<Sprite>(path);
if (sprite != null) return sprite;
// Sprite로 로드 실패 시 Texture2D로 로드 후 변환
var texture = await LoadOnlyAsync<Texture2D>(path);
if (texture != null)
{
return Sprite.Create(
texture,
new Rect(0, 0, texture.width, texture.height),
new Vector2(0.5f, 0.5f)
);
}
return null;
}
}
/// <summary>
/// 파일 시스템에서 이미지를 로드하여 Sprite로 변환합니다.
/// </summary>
/// <param name="filePath">이미지 파일의 절대 경로</param>
/// <returns>로드된 Sprite, 실패 시 null</returns>
private static async UniTask<Sprite?> LoadSpriteFromFileAsync(string filePath)
{
if (!File.Exists(filePath))
{
Debug.LogWarning($"[ResourceManager] Image file not found: {filePath}");
return null;
}
try
{
// file:// URI로 변환
string uri = "file:///" + filePath.Replace("\\", "/");
using var request = UnityWebRequestTexture.GetTexture(uri);
await request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
Debug.LogError($"[ResourceManager] Failed to load image: {request.error}");
return null;
}
var texture = DownloadHandlerTexture.GetContent(request);
var sprite = Sprite.Create(
texture,
new Rect(0, 0, texture.width, texture.height),
new Vector2(0.5f, 0.5f)
);
return sprite;
}
catch (Exception e)
{
Debug.LogError($"[ResourceManager] Failed to load sprite from file: {e.Message}");
return null;
}
}
}
}