유니티에서 메인 앱과 콘텐츠를 나눠서 개발할 때(APK 용량을 줄이고 리소스를 따로 빼는 작업) 주로 어셋 번들로 만들어서 다운로드하고, 로드하는 형식으로 많이 사용하였다
어드레서블 에셋 시스템은 우리가 그 동안 익숙했던 어셋 번들을 좀 더 자동화 및 관리시스템이 추가되어 새로운 패키지 형태로 나온 개념이라고 이해하면 좋다.
모든 동작이 비동기 시스템으로 구현되어있다.
어드레서블은 콘텐츠를 빌드하고, 로딩하고 관리하는 시스템이며, 주소를 통해서 관리하기 때문에 Addressable이란 이름으로 불린다.
개발 초기에는 유니티의 Resources 폴더나 StreammingAssets 폴더에 리소스들은 넣어 놓고, 동적으로 Load해서 사용하는 방식을 대부분 이용하였다. 이런 방식은 로드 시점에 많은 CPU를 소모하게 만들고, 앱 용량(APK, IPA)을 비대하게 만드는 가장 큰 원인이다.
최근 게임 및 앱의 범위 및 리소스 사용량이 늘어남에 따라 메인 앱과 콘텐츠 영역의 분리는 필수적이다.
메인 앱을 가볍게 만들고, 콘텐츠를 다운로드하여 구동하는 방식으로 개발 설계를 하기 위해서는 에셋번들(AssetBundle)이나 어드레서블(Addressable)을 사용해야 한다.
1) 대규모 프로젝트에서 메모리를 효율적으로 관리하고 싶을 때
2) 서버에서 추가 에셋 다운로드가 필요할 때
콘텐츠를 분리하여 동적으로 로드하고, 관리하는 개념으로는 어셋 번들과 어드레서블은 같은 개념이다.
어드레서블은 에셋 번들을 한번 더 래핑 하여, 사용자가 좀 더 편하게 사용하고, 메모리 관리에 대한 부분에 대한 걱정을 덜 수 있게해 준 패키지라고 생각하면 좋다.
에셋 번들은 다른 에셋들과의 종속성이 있는 상황에서 무조건 로드하여 메모리에 올린다면, 메모리에 중복적으로 올라가는 상황이 있다.
에셋 번들 시스템은 이런 종속성을 생각하면서 관리 코드(로드 및 해제)를 제작해야 하지만, 어드레서블은 이런 메모리 종속성 관리까지 해주기 때문에 코드도 간략해지고, 관리도 편해진다.
어드레서블은 알아서 의존관계를 확인하여 리소스를 로드해주기 때문에, 어셋 번들처엄 의존 관계에 따른 로드를 개발자가 판단하지 않아도 된다.
패키지 매니저에서 Addressables를 설치

먼저 아래의 사진처럼 Groups에 들어가 Addressbles Settings를 만들어 줍니다.



로컬 및 서버의 주소를 설정해줄 수 있습니다.


Cache Clear Behavior -> Clear When When New Version Loaded로 선택
이유 : 변경사항이 있는 번들을 다운받을 때 이전버전을 갱신해줍니다.(선택 안할 시 새롭게 다운받기 때문에 용량이 늘어납니다.)

Inspect Top Level Settings를 눌러 Addressable Asset Settings를 열어줍니다.
Diagnostics의 Send Profiler Events를 Check
Catalog의 Build Remote Catalog를 Check후 Build & Load Paths를 Remote로 변경(필수)
Build의 Unique Bundle IDs를 Check



Default Local Group을 더블클릭하여 인스펙터창을 띄워줍니다 이후 Content Packing & Loading의 Build & Load Paths를 Remote로 변경합니다.


prefab이나 이미지등의 인스펙터창에 있는 Addressable을 Check
빨간 창이나 더블클릭하여 이름을 변경해줄 수 있습니다.




라벨을 통해 구별이 가능합니다 => tag또는 layer와 비슷한 역활

예시
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;
public class AddressableManager : Singleton<AddressableManager>
{
protected AddressableManager() { }
AsyncOperationHandle<ItemDB> _itemDB;
private DBManager _dbManager;
//private List<GameObject> _gameObjectList = new List<GameObject>();
private List<AudioClip> _bgmList = new List<AudioClip>();
private List<AudioClip> _uiSFXList = new List<AudioClip>();
private void Awake()
{
_dbManager = DBManager.Instance;
StartCoroutine(IninAddressable());
}
// Addressables.InitializeAsync라는 초기화를 실행(굳이 안해줘도 되지만 버그 및 오류를 방지하기 위해서 한번 실행)
IEnumerator IninAddressable()
{
var init = Addressables.InitializeAsync();
yield return init;
}
public void Button_SpawnObject()
{
Addressables.LoadAssetAsync<ItemDB>("Assets/ItemExcelSheet/ItemDB.asset").Completed += (obj) =>
{
_itemDB = obj;
_dbManager.SetItemDateSO(obj.Result);
};
LoadAudioClips("BGM", AddressablesBGMOnCompleteDic);
LoadAudioClips("UISFX", AddressablesUISFXOnCompleteDic);
}
public void Button_Release()
{
Addressables.Release(_itemDB);
for (int i = _bgmList.Count - 1; i >= 0; i--)
{
Addressables.Release(_bgmList[i]);
_bgmList.RemoveAt(i);
}
for (int i = _uiSFXList.Count - 1; i >= 0; i--)
{
Addressables.Release(_uiSFXList[i]);
_uiSFXList.RemoveAt(i);
}
}
private void AddressablesBGMOnCompleteDic(Dictionary<string, AudioClip> values)
{
_dbManager.SetBGM(values);
_bgmList = values.Select(n => n.Value).ToList();
}
private void AddressablesUISFXOnCompleteDic(Dictionary<string, AudioClip> values)
{
_dbManager.SetUISFX(values);
_uiSFXList = values.Select(n => n.Value).ToList();
}
public static void LoadAudioClips(string labelName, Action<Dictionary<string, AudioClip>> callback)
{
Dictionary<string, AudioClip> clips = new Dictionary<string, AudioClip>();
AsyncOperationHandle<IList<IResourceLocation>> labelOperation = Addressables.LoadResourceLocationsAsync(labelName);
labelOperation.Completed += (labelResponse) => {
int totalCount = labelResponse.Result.Count;
foreach (IResourceLocation item in labelResponse.Result)
{
AsyncOperationHandle<AudioClip> resourceOperation = Addressables.LoadAssetAsync<AudioClip>(item.PrimaryKey);
resourceOperation.Completed += (result) =>
{
totalCount--;
switch (labelResponse.Status)
{
case AsyncOperationStatus.Succeeded:
clips.Add(result.Result.name, result.Result);
Addressables.Release(resourceOperation);
break;
case AsyncOperationStatus.Failed:
Debug.LogError("Failed to load audio clips.");
break;
default:
break;
}
// When we've finished loading all items in the directory, let's continue
if (totalCount == 0)
{
callback(clips);
}
};
}
};
}
}
아래의 코드처럼 코드를 사용하여 로드
Addressables.LoadAssetAsync<@@@>("경로").Completed += (obj) =>
{
필요한 객체에 할당
이후 메모리 해제를 위해 변수에 저장
};
폴더안에 있는 대량의 사운드 클립을 불러오기 위한 메서드 => 응용하여 다른코드도 작성가능
public static void LoadAudioClips(string labelName, Action<Dictionary<string, AudioClip>> callback)
{
Dictionary<string, AudioClip> clips = new Dictionary<string, AudioClip>();
AsyncOperationHandle<IList<IResourceLocation>> labelOperation = Addressables.LoadResourceLocationsAsync(labelName);
labelOperation.Completed += (labelResponse) => {
int totalCount = labelResponse.Result.Count;
foreach (IResourceLocation item in labelResponse.Result)
{
AsyncOperationHandle<AudioClip> resourceOperation = Addressables.LoadAssetAsync<AudioClip>(item.PrimaryKey);
resourceOperation.Completed += (result) =>
{
totalCount--;
switch (labelResponse.Status)
{
case AsyncOperationStatus.Succeeded:
clips.Add(result.Result.name, result.Result);
Addressables.Release(resourceOperation);
break;
case AsyncOperationStatus.Failed:
Debug.LogError("Failed to load audio clips.");
break;
default:
break;
}
// When we've finished loading all items in the directory, let's continue
if (totalCount == 0)
{
callback(clips);
}
};
}
};
}
할당되어 있는 메모리를 해재하기 위한 코드 Addressables.Release(변수명);을 사용해 해제
public void Button_Release()
{
Addressables.Release(_itemDB);
for (int i = _bgmList.Count - 1; i >= 0; i--)
{
Addressables.Release(_bgmList[i]);
_bgmList.RemoveAt(i);
}
for (int i = _uiSFXList.Count - 1; i >= 0; i--)
{
Addressables.Release(_uiSFXList[i]);
_uiSFXList.RemoveAt(i);
}
}
유니티블로그 - 어드레서블 에셋 시스템
앤디가이 블로그
eungding의 블로그
유니티 어드레서블 사용 방법 (로컬) - 더블엘 DoubleL(유튜브)
어드레서블 로드(Addressable Load) (1) - LoadAssetAsync() - 감귤오렌지(유튜브)
어드레서블 로드(Addressable Load) (2) - InstantiateAsync() - 감귤오렌지(유튜브)