Package Manager에서 Addressables을 임포트한다.
메뉴바의 Window - Asset Management - Addressables - Group을 눌러 Addressables Groups창을 연다.
Addressables Groups 창의 Profile - Manage Profiles를 선택해 Addressables Profiles창을 연다.
해당 창에서는 서버에서 에셋을 다운로드 할 때 사용할 서버 주소를 입력할 수 있다.
Addressables Groups의 기본 로컬 그룹(Defalult Local Group(Default)) 인스펙터 창에서 Cache Clear Behaviour를 Clear When New Version Loaded 선택
이후 Inspect Top Level Settings를 클릭해 다음과 같이 세팅
Unique Bundle IDs 체크(기존에 변경점이 없는 번들들은 무시하게 됨)Send Profiler Events 체크(이벤트 뷰어를 활성화해 상황 확인 가능)Build Remote Catalog 체크(카탈로그 사본을 생성해 서버에 업로드)Build & Load Paths를 Remote로 설정다시 기본 로컬 그룹에 들어와 다음과 같이 세팅
Build & Load Paths를 Remote로 설정
프리팹과 해당 프리팹이 사용하는 머터리얼들의 Addressable을 체크하여 어드레서블 애셋으로 등록

그룹 및 라벨로 에셋 분류 가능
원하는 오디오 파일 혹은 이미지 파일의 Addressable을 체크하여 어드레서블 에셋으로 등록 가능

스크립트에 AssetReference 형식으로 프리팹들을 참조할 수 있다.
AssetRefernceGameObject : 게임 오브젝트 형식AssetReferenceSprite : 2D SpriteAssetReferenceT<> : 제네릭 형식이외에 다양한 형식이 존재한다.

이후 인스펙터 창에서 원하는 프리팹을 목록 중 선택, 그룹창, 폴더에서 드래그 등으로 넣는다.
public class AddressableMananger : MonoBehaviour
{
[SerializeField] private AssetReferenceGameObject cubeObj;
[SerializeField] private AssetReferenceGameObject[] buildingObjs;
[SerializeField] private AssetReferenceT<AudioClip> soundBGM;
[SerializeField] private AssetReferenceSprite flagSprite;
// 추후 Release해줄 때 사용하기 위한 오브젝트 레퍼런스 리스트
private List<GameObject> _objects = new List<GameObject>();
[SerializeField] private AudioSource bgmObj;
[SerializeField] private Image flagImage;
// Start is called before the first frame update
void Start()
{
StartCoroutine(InitAddressable());
}
private IEnumerator InitAddressable()
{
var init = Addressables.InitializeAsync();
yield return init;
}
public void Button_SpawnObject()
{
// 단일 게임오브젝트 생성
cubeObj.InstantiateAsync().Completed += (obj) =>
{
_objects.Add(obj.Result);
};
// 다중 게임오브젝트 생성
for (int i = 0; i < buildingObjs.Length; i++)
{
buildingObjs[i].InstantiateAsync().Completed += (obj) =>
{
_objects.Add(obj.Result);
};
}
soundBGM.LoadAssetAsync().Completed += (clip) =>
{
bgmObj.clip = clip.Result;
bgmObj.loop = true;
bgmObj.Play();
};
flagSprite.LoadAssetAsync().Completed += (img) =>
{
flagImage.sprite = img.Result;
};
}
}
Addressables Groups창에서 Build 0 New Build - Default Build Script 실행
빌드를 마친 후 Play Mode Script - Use Asset Database 선택
게임을 실행한 후 Button_SpawnObject() 함수 실행 시 미리 지정해둔 오브젝트를 실행할 수 있음
위의 클래스에 Release함수를 추가해준다.
public void Button_Release()
{
// LoadAssetAsync로 로드한 애셋을 Release해준다
soundBGM.ReleaseAsset();
flagSprite.ReleaseAsset();
if (_objects.Count == 0)
return;
var index = _objects.Count - 1;
for (int i = _objects.Count - 1; i > -1; i--)
{
// InstantiateAsync로 생성한 오브젝트들을 Release해준다
Addressables.ReleaseInstance(_objects[i]);
_objects.RemoveAt(i);
}
}
미리 로컬에서 위와 같이 에셋을 추가 및 설정한 후 서버를 적용할 수 있다

1.AWS S3의 버킷을 만든다.
2.버킷 페이지에 들어가 다음과 같이 설정해준다.
*를 입력, 'Actions'에서는 'GetObject'선택"Resource"구문의 마지막에 /*추가3.아무 파일을 하나 업로드한 후 해당 파일의 객체 URL 주소를 복사한다.
1.Remote Profile을 다음과 같이 설정한다.

Addressable Profile의 Remote.LoadPath에 붙여넣은 후 파일 이름을 [BuildTarget]으로 변경해준다.2.Addressable Group들의 Build & Load Paths가 Remote인지, Path Preview로 보이는 경로가 올바른지 확인한다. 시스템 세팅에서도 변경되었는지 확인한다.
3.AWS S3에 올린 파일을 삭제한다.
1.Addressable Groups의 Build - Clear Build Cache - All을 선택해 이전 빌드 캐시 파일들을 삭제한다.
2.프로젝트의 파일 탐색기에서 Assets - ServerData내에 있는 Standalone...파일을 삭제한다.
3.Addressable Groups의 Build - New Build - Default Build Script로 재빌드
파일 탐색기의 Assets - ServerData내에 있는 Standalone...폴더를 AWS S3에 추가
Addressable Groups의 Play Mode Script를 Use Existing Build로 선택해 서버에서 데이터를 받아오게 변경
C:\Users\유저_이름\AppData\LocalLow\Unity\프로젝트_이름에 다운로드 된다.
테스트를 위해 다운로드 파일을 삭제하고 싶을 시 해당 폴더를 삭제하면 된다.
어드레서블 에셋의 다운로드 필요 여부를 확인하고 만약 필요하면 다운로드를 실행하는 씬의 매니저
public class DownloadManager : MonoBehaviour
{
[Header("UI")]
public GameObject waitMessage;
public GameObject downMessage;
public Slider downSlider;
public TMP_Text sizeText;
public TMP_Text downValText;
[Header("Label")] // 다운로드받을 애셋들의 라벨
public AssetLabelReference defaultLabel;
public AssetLabelReference matLabel;
// 다운받을 애셋들의 파일 크기
private long _patchSize;
// 각각의 애셋들의 파일 크기
private Dictionary<string, long> _patchMap = new Dictionary<string, long>();
// Start is called before the first frame update
void Start()
{
downMessage.SetActive(false);
waitMessage.SetActive(true);
StartCoroutine(InitAddressable());
StartCoroutine(CheckUpdateFiles());
}
// 어드레서블을 초기화해주는 코루틴
private IEnumerator InitAddressable()
{
var init = Addressables.InitializeAsync();
yield return init;
}
// 업데이트할 파일을 체크하는 코루틴
// 각 라벨별로 다운로드할 데이터를 파악한 후 추가 파일이 있으면 다운로드 버튼 활성화
// 아니면 로딩 씬을 거쳐 샘플신을 로드하게끔 실행
private IEnumerator CheckUpdateFiles()
{
var labels = new List<string>() { defaultLabel.labelString, matLabel.labelString };
_patchSize = default;
foreach (var label in labels)
{
var handle = Addressables.GetDownloadSizeAsync(label);
yield return handle;
_patchSize += handle.Result;
if (_patchSize > Decimal.Zero)
{
waitMessage.SetActive(false);
downMessage.SetActive(true);
sizeText.text = "" + GetFileSize(_patchSize);
}
else
{
downValText.text = "100%";
downSlider.value = 1f;
yield return new WaitForSeconds(2f);
LoadingManager.LoadScene("SampleScene");
}
}
}
public void Button_Download()
{
StartCoroutine(PatchFiles());
}
// 새 파일에 대한 패치를 실시하는 코루틴
// 각 라벨에 대해 다운로드 여부를 파악한 후 다운로드할 파일이 있으면 다운로드 코루틴 시작
// 직후 다운로드 상태 코루틴 시작
private IEnumerator PatchFiles()
{
var labels = new List<string>() { defaultLabel.labelString, matLabel.labelString };
_patchSize = default;
foreach (var label in labels)
{
var handle = Addressables.GetDownloadSizeAsync(label);
yield return handle;
if (handle.Result != Decimal.Zero)
{
StartCoroutine(DownLoadFromLabel(label));
}
}
yield return CheckDownloadStatus();
}
// 각각의 라벨에 대해 다운로드하게끔 하는 코루틴
// 해당 라벨에 대해 다운로드 데이터를 받아온 후 핸들을 Release
private IEnumerator DownLoadFromLabel(string label)
{
_patchMap.Add(label, 0);
var handle = Addressables.DownloadDependenciesAsync(label);
while (!handle.IsDone)
{
_patchMap[label] = handle.GetDownloadStatus().DownloadedBytes;
yield return new WaitForEndOfFrame();
}
_patchMap[label] = handle.GetDownloadStatus().TotalBytes;
Addressables.Release(handle);
}
// 다운로드 현황을 체크하는 코루틴
// 로딩바와 진행도를 갱신해주고 완료시 로딩신 후 샘플신으로 넘겨주기
private IEnumerator CheckDownloadStatus()
{
var total = 0f;
downValText.text = "0 %";
while (true)
{
total += _patchMap.Sum(tmp => tmp.Value);
var perValue = total / _patchSize;
downSlider.value = perValue;
downValText.text = string.Format("{0:##.##}", perValue * 100) + " %";
if (total.Equals(_patchSize))
{
LoadingManager.LoadScene("SampleScene");
break;
}
total = 0f;
yield return new WaitForEndOfFrame();
}
}
// 파일 사이즈에 따라 용량에 대한
public string GetFileSize(long byteCnt)
{
string size = "0 Bytes";
if (byteCnt >= 1073741824.0)
{
size = string.Format("{0:##.##}", byteCnt / 1073741824.0) + " GB";
}
else if (byteCnt >= 1048576.0)
{
size = string.Format("{0:##.##}", byteCnt / 1048576.0) + " MB";
}
else if (byteCnt >= 1024.0)
{
size = string.Format("{0:##.##}", byteCnt / 1024.0) + " KB";
}
else if (byteCnt > 0 && byteCnt < 1024.0)
{
size = string.Format("{0:##.##}", byteCnt) + " KB";
}
return size;
}
}
한 씬에서 다른 씬으로 로딩할 때 중간에 거쳐가는 신 매니저
public class LoadingManager : MonoBehaviour
{
// 로딩할 신의 이름
public static string nextScene;
public Slider loadingBar;
// Start is called before the first frame update
void Start()
{
StartCoroutine(StartLoading());
}
// 지정된 신으로 로딩을 시작하는 코루틴
private IEnumerator StartLoading()
{
yield return null;
AsyncOperation op = SceneManager.LoadSceneAsync(nextScene);
op.allowSceneActivation = false;
float timer = 0f;
while (!op.isDone)
{
yield return null;
timer += Time.deltaTime;
if (op.progress < 0.9f)
{
loadingBar.value = Mathf.Lerp(loadingBar.value, op.progress, timer);
if (loadingBar.value >= op.progress)
{
timer = 0f;
}
}
else
{
loadingBar.value = Mathf.Lerp(loadingBar.value, 1f, timer);
if (loadingBar.value.Equals(1f))
{
yield return new WaitForSeconds(2f);
op.allowSceneActivation = true;
yield break;
}
}
}
}
// 한 씬에서 다른 씬으로 넘길 때 사용하는 씬 로딩 함수
public static void LoadScene(string sceneName)
{
nextScene = sceneName;
SceneManager.LoadScene("LoadingScene");
}
}
에셋 파일 변경(파일의 이름은 그대로 하는 것을 권장) 후 Addressables Groups 에서Build - Update a Previous Build를 선택해 빌드
.bin파일을 선택해 빌드프로젝트 파일의 ServerData에 들어가서 파일의 수정 날짜 및 시간이 빌드한 날짜인 파일들을 선택.
이들이 새로 변경된 파일들이다.
AWS S3에 진입해 .json, .hash, 에셋을 변경한 로컬 그룹의 Bundle파일을 삭제한 후 변경된 파일을 업로드한다.
Addressable 2.6.0 버전에 존재하는 Analyzer를 사용해 중복 애셋을 한 곳에 몰아넣는 방식을 사용한다.
이 때 중복 애셋을 몰아넣기 위해 자동으로 생성된 그룹은 옵션만 바꿔주고 안의 내용물을 추가하거나 삭제하면 안 된다. (연결되어 있는 모든 번들이 새로 빌드되어 데이터 낭비가 심해짐)
Prevent Updates 항목을 체크해두는 습관을 두자. 빌드를 시도할때마다 거슬리게 그룹이 하나씩 생성될거지만 모든 번들이 다시 빌드되는것보다는 나으니까...
어드레서블에 로드되는 프리팹을 씬에 배치하게 되면 씬 데이터와 애셋 번들에 중복되게 저장된다.
씬 데이터는 .Unity 형식으로 애플리케이션 데이터로 저장되고, 애셋 번들은 따로 추출되어 서버에 업로드되기 때문이다.
따라서 어드레서블에 추가한 프리팹은 씬에 배치하지 말고 스크립트로 가져와 생성해야 한다.