Deff_Dev
[Unity/C#] 어드레서블 (Addressable) 사용 본문
어드레서블에 대해 공부를 하고 현재 진행 중인 프로젝트에 적용시켰다.
구현
unit 리소스들은 게임 시작시 데이터를 받아오고, ui들은 필요할 때마다 어드레서블에서 해당 리소스를 동적 생성하는 것을 구현했다.
어드레서블에 대해 이해가 잘 되지않는다면 아래의 글을 참고하길 바란다.
어드레서블과 비동기 처리에 대해 완벽하게 이해를 하고 코드를 작성한 것이 아니고 공부 목적으로 여러 방법 찾아가면서 구현한 내용이니 비 효율적인 부분이 있을 수 있다.
그룹 설정
아직 테스트 단계이므로 Unit, UI로 나눴고 Unit은 게임 시작 시, 유닛 정보들을 Data 클래스에 저장해두고, UI(팝업 창)는 필요할 때, 생성하는 것으로 구현할 것 이다.
그룹에 맞게 리소스를 넣고 해당 리소스에 맞게 Label을 설정해준다.
문제점
하지만 현재 한가지 문제점이 있다.
그룹 안에 리소스를 넣을 때, 한번에 드래그해서 넣으면 순서가 뒤죽박죽이 된다는 것이다.
찾아보니 따로 순서를 변경해주는 것은 지원하지 않는다고 하고, 아마도 라벨을 이용해서 전부 다 가져오기 때문에 순서를 신경쓰지 않는 듯 하다.
내가 구현한 방법은 위에서 아래로 순서를 맞춰서 넣어야하기 때문에 참 불편한 이슈가 존재한다.
(하나씩 반대로 넣으면 위에서 아래로 들어가긴 한다... ㅠㅠㅠ 리소스가 80개라면 80번을 해야되나...)
이 문제의 해결책을 찾는다면 다시 글을 쓰도록 하겠다.
[해결책]
스크립트 작성
먼저 스크립트에 들어가는 어드레서블 키워드들을 설명하겠다.
IResourceLocation
- 특정 라벨을 가진 리소스의 경로를 저장하는 인터페이스이다.
- IList를 이용해 IResourceLocation 리스트를 만들고 Addressables.LoadResourceLocationsAsync 메소드를 사용하여 해당 라벨을 가진 리소스들의 경로를 저장한다.
- IResourceLocation를 이용해 어드레서블에 저장된 리소스를 Load 할 수 있다.
InstantiateAsync ( )
- 어드레서블에 저장된 리소스를 Instantiate(생성) 한다.
- 리소스를 메모리에 올려놓고 해당 리소스를 게임에 실체화한다.
LoadAssetAsync <T> ( )
- 어드레서블에 저장된 리소스를 특정 타입(T)의 데이터로 불러온다
- 리소스를 메모리에 올려놓기만 하고 해당 리소스를 실체화하진 않는다.
- 게임 오브젝트 프리팹에 붙어있는 특정 컴포넌트 정보를 가져오고 싶을 땐, GameObject 타입으로 로드한 후, GetComponent를 이용해 특정 컴포넌트 정보를 받아와야 한다.
ReleaseInstance ( )
- 어드레서블에서 생성된 리소스에 할당된 메모리를 언로드한다. (삭제)
InstantiateAsync, LoadAssetAsync는 AsyncOperationHandle을 반환하기 때문에 Task를 사용할 수 있다.
스크립트
AddressableManager.cs (경로 설정 및 리소스 로드, 언로드)
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;
public enum EAddressableType
{
Unit,
UI,
}
public enum EAddressableUIType
{
HUD,
UnitStatus,
StageEffect,
Settings,
Gacha,
DamageTxt,
}
public class AddressableManager : Singleton<AddressableManager>
{
// 어드레서블의 Label을 얻어올 수 있는 필드.
public AssetLabelReference[] assetLabels;
// IResourceLocation : 리소스의 경로
public Dictionary<int, IList<IResourceLocation>> LocationDict { get; private set; } = new Dictionary<int, IList<IResourceLocation>>();
// 생성된 리소스들을 담아놓는 리스트 (메모리 해제할 때, 사용)
private List<GameObject> InstanceList;
private void Awake()
{
GetLocations();
}
public async void GetLocations()
{
// 딕셔너리에 리소스 경로 할당
await LoadLocationsAsync();
}
private async Task LoadLocationsAsync()
{
// 빌드타겟의 경로를 가져온다.
for (int i = 0; i < assetLabels.Length; i++)
{
var handle = Addressables.LoadResourceLocationsAsync(assetLabels[i].labelString);
// 비동기 작업 완료를 기다림
await handle.Task;
// 결과를 할당
LocationDict[i] = handle.Result;
}
ResourceDataManager.Instance.LoadUnitData(); // 유닛 로드
}
public async Task<GameObject> InstantiateAsync(EAddressableType type ,int idx)
{
// 해당 라벨의 idx번째 리소스를 생성한다.
var handle = Addressables.InstantiateAsync(LocationDict[(int)type][idx]);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded) // 성공
{
GameObject obj = handle.Result.gameObject;
InstanceList.Add(obj);
return obj; // 반환
}
else // 실패
{
Debug.LogError("Failed to instantiate the object.");
return default;
}
}
public async Task<T> LoadAsset <T> (EAddressableType type ,int idx) where T : MonoBehaviour
{
// 해당 라벨의 idx번째 리소스의 게임 오브젝트 정보를 저장 (LoadAsset은 생성 X, 정보만 저장)
var handle = Addressables.LoadAssetAsync<GameObject>(LocationDict[(int)type][idx]);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded) // 성공
{
GameObject loadedGameObject = handle.Result;
// 게임 오브젝트 정보를 MonoBehaviour를 상속받는 T의 정보를 저장
T component = loadedGameObject.GetComponent<T>();
if (component != null)
{
return component;
}
else
{
Debug.LogError($"The loaded GameObject does not have a component of type {typeof(T)}.");
return null;
}
}
else
{
Debug.LogError("Failed to LoadAsset the object.");
return default;
}
}
public void ReleaseInstance(GameObject asset) // 생성된 오브젝트 제거
{
GameObject releaseObj = InstanceList.Find(obj => obj == asset);
if (releaseObj != null)
{
Addressables.ReleaseInstance(releaseObj); // 메모리 해제 (리소스 삭제)
InstanceList.Remove(releaseObj);
}
else
{
Debug.LogError($"{asset.name} is not find !");
}
}
}
ResourceManager.cs (유닛 저장, UI 생성)
using System.Collections.Generic;
using UnityEngine;
public class ResourceManager : Singleton<ResourceManager>
{
[field:SerializeField]public List<PoolObject> UnitDataList { get; private set; }
public async void LoadUnitData()
{
// 유닛 Load
for (int i = 0; i < AddressableManager.Instance.LocationDict[(int)EAddressableType.Unit].Count; i++)
{
PoolObject unit = await AddressableManager.Instance.LoadAsset<PoolObject>(EAddressableType.Unit, i);
if (unit != null)
{
UnitDataList.Add(unit);
}
else
{
Debug.LogError($"{i} number of Unit Load Error");
}
}
}
// UI 생성
[ContextMenu("CreateSettingUI")]
public async void CreateSettingUI()
{
GameObject ui = await AddressableManager.Instance.InstantiateAsync(EAddressableType.UI, (int)EAddressableUIType.Settings);
// ui 사용... //
// ui 언로드
AddressableManager.Instance.ReleaseInstance(ui);
}
}
게임 오브젝트에 각각의 스크립트를 붙히고 게임을 실행보면 정상적으로 작동하는 것을 볼 수 있다.
동적 생성 같은 경우는 Enum(EAddressableUIType) 과 순서를 맞춰야 정상 작동한다.
'Unity(유니티) > 유니티 공부' 카테고리의 다른 글
[Unity/C#] 어드레서블 에셋 로드 (0) | 2024.07.11 |
---|---|
[Unity/C#] 어드레서블 그룹 순서 이슈 해결 (0) | 2024.07.10 |
[Unity/C#] 어드레서블 (Addressable) (0) | 2024.07.06 |
[Unity/C#] foreach문에서 컬렉션 수정할 때, 생기는 오류 (0) | 2024.07.04 |
[Unity/C#] 이벤트 호출 안될 때 (0) | 2024.07.04 |