오늘은 스파르타 코딩 클럽 unity 게임 개발 과정 34 & 35일차 땜빵( Addressable을 이용하여 리소스매니저 만들기)
목요일은 외출을 해서 아예 한게 없으니 til을 작성하지 못헀다.
금요일은 밤낮을 바꾸기 위해 밤을 새웠더니 그냥 골아떨어져서 작성하지 못했다.
그래서 오늘 몰아서 작성하려고 한다.
이렇게 숙제인 느낌이 아니라 정말 배운 방식을 작성해야하지만..
아직 갈 길이 멀다.
우리 팀 과제의 주제는 3d 퍼즐 게임이다.
그중에서도 ALRF4 라는 게임을 벤치마킹하여 만들기로 하였다.
팀 과제에서 나는 리소스 매니저를 맡았다.
리소스 매니저에 Addressable를 사용하여 리소스 로드와 인스턴스 생성을 해 사용할 것이다.
시작하기 | 어드레서블 | 1.17.17 (unity3d.com)
Addressables의 설명은 위에있는 링크에 정확히 나와있다.
여기서 간단하게 볼 것은 일단 어떻게 만드는 것이다.
그전에 패키지 매니저에서 Addressables를 찾아서 인스톨하자
어드레서블 창 사용
Window > Asset Management > Addressables > Groups) 를 선택하여 어드레서블 그룹 창을 엽니다. 그런 다음 프로젝트 창에서 원하는 에셋을 어드레서블 그룹 창의 에셋 그룹 중 하나로 드래그합니다.
Addressables Groups(어드레서블 그룹) 창에서 자산을 Addressable(어드레서블)로 표시합니다.
일단 이런식으로 만들 수 있다.
난 대충 이런식인데 아직 추가할 것이 많다.
아래는 아직 작성중인 리소스매니저의 코드다.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public enum LABEL
{
}
public class ResourceManager : MonoBehaviour
{
#region Fields
private Dictionary<string, UnityEngine.Object> \_resources = new();
#endregion
// 리소스 비동기 로드 메서드
#region Asynchronous Loading
/// <summary>
/// 콜백을 처리하는 제네릭 핸들러입니다.
/// </summary>
private void AsyncHandelerCallback<T>(string key, AsyncOperationHandle<T> handle, Action<T> callback) where T : UnityEngine.Object
{
handle.Completed += operationHandle =>
{
\_resources.Add(key, operationHandle.Result);
callback?.Invoke(operationHandle.Result);
};
}
/// <summary>
/// 비동기 방식으로 리소스를 로드하고 콜백을 호출합니다.
/// </summary>
public void LoadAsync<T>(string key, Action<T> callback = null) where T : UnityEngine.Object
{
string loadKey = key;
if (\_resources.TryGetValue(loadKey, out UnityEngine.Object resource))
{
callback?.Invoke(resource as T);
return;
}
if (loadKey.Contains(".sprite"))
{
loadKey = $"{key}\[{key.Replace(".sprite", "")}\]";
AsyncOperationHandle<Sprite> handle = Addressables.LoadAssetAsync<Sprite>(loadKey);
AsyncHandelerCallback(loadKey, handle, callback as Action<Sprite>);
}
else
{
AsyncOperationHandle<T> handle = Addressables.LoadAssetAsync<T>(loadKey);
AsyncHandelerCallback(loadKey, handle, callback);
}
}
/// <summary>
/// 특정 라벨에 속한 모든 리소스를 비동기 방식으로 로드하고 콜백을 호출합니다.
/// </summary>
public void AllLoadAsync<T>(string label, Action<string, int, int> callback) where T : UnityEngine.Object
{
var operation = Addressables.LoadResourceLocationsAsync(label, typeof(T));
operation.Completed += operationHandle =>
{
int loadCount = 0;
int totalCount = operationHandle.Result.Count;
foreach (var result in operationHandle.Result)
{
LoadAsync<T>(result.PrimaryKey, obj =>
{
loadCount++;
callback?.Invoke(result.PrimaryKey, loadCount, totalCount);
});
}
};
}
/// <summary>
/// 특정 라벨에 속한 모든 리소스를 비동기 방식으로 언로드합니다.
/// </summary>
public void UnloadAllAsync<T>(string label, Action<string, int, int> callback) where T : UnityEngine.Object
{
var operation = Addressables.LoadResourceLocationsAsync(label, typeof(T));
operation.Completed += operationHandle =>
{
int loadCount = 0;
int totalCount = operationHandle.Result.Count;
foreach (var result in operationHandle.Result)
{
if (\_resources.TryGetValue(result.PrimaryKey, out UnityEngine.Object resource))
{
Addressables.Release(resource);
\_resources.Remove(result.PrimaryKey);
Debug.Log($"{resource} 언로드");
}
}
};
}
#endregion
// 리소스 동기 로드&언로드 메서드
#region Synchronous Loading
/// <summary>
/// 리소스를 동기적으로 로드합니다.
/// </summary>
public T Load<T>(string key) where T : UnityEngine.Object
{
if (!\_resources.TryGetValue(key, out UnityEngine.Object resource))
{
Debug.LogError($"키를 찾을 수 없습니다. : {key}");
return null;
}
return resource as T;
}
/// <summary>
/// 리소스를 동기적으로 언로드합니다.
/// </summary>
public void Unload<T>(string key) where T : UnityEngine.Object
{
if (\_resources.TryGetValue(key, out UnityEngine.Object resource))
{
Addressables.Release(resource);
\_resources.Remove(key);
}
else
{
Debug.LogError($"키를 찾을 수 없습니다. : {key}");
}
}
#endregion
// 프리펩 인스턴스화 메서드
#region Instantiation
/// <summary>
/// 프리팹을 인스턴스화하고 생성된 인스턴스를 반환합니다.
/// </summary>
public GameObject InstantiatePrefab(string key, Transform transform = null)
{
GameObject resource = Load<GameObject>(key);
GameObject instance = Instantiate(resource, transform);
if (instance == null)
{
Debug.LogError($"리소스를 인스턴스화하지 못했습니다.: { key}");
return null;
}
return instance;
}
#endregion
}
아래는 다른 클래스에서 사용할때의 모습이다.
public class OtherClass : MonoBehaviour
{
private void Start()
{
// 비동기로 리소스 로드하기
Main.Resource.LoadAsync<Sprite>("spriteKey", (sprite) =>
{
// 로드된 스프라이트를 사용하는 코드
});
// 비동기로 라벨에 있는 리소스 로드하기
Main.Resource.AllLoadAsync<GameObject>("preLoad", (result, loadCount, totalCount) =>
{
Debug.Log($"얼만큼 되었나 : {loadCount}/{totalCount} 로드 된 것: {result}");
});
// 동기적으로 리소스 로드하기
var loadedPrefab = Main.Resource.Load<GameObject>("prefabKey");
var loadedSprite = Main.Resource.Load<Sprite>("prefabKey");
// 동기적으로 리소스 언로드하기
Main.Resource.Unload<Sprite>("prefabKey");
// 프리팹 인스턴스화하기
var instancePrefab = Main.Resource.InstantiatePrefab("prefabKey");
// 부모있는 프리팹 인스턴스화하기
var instancePrefab2 = Main.Resource.InstantiatePrefab("prefabKey", transform);
}
}
아직 비동기와 동기 부분에 대해 정확한 이해가 더 필요하고 어드레서블의 이해가 조금 부족한 상황이다.
언로드 부분에서 조금 더 손봐야 할 것같다.