Addressables
Addressables는 주소(키)로 에셋을 비동기 로드하는 고급 시스템입니다. DLC·CDN 스트리밍과 런타임 메모리 관리, LoadAssetAsync·Release·InstantiateAsync의 올바른 사용법을 배우는 실전 가이드입니다. 도식과 시뮬레이터로 익힙니다.
상상해 보세요...
Resources.Load()는 창고에 직접 들어가는 것——정확한 경로가 필요하고 기다립니다. Addressables는 택배 배송: 품목 이름(address)을 말하면, 서비스가 로컬에서 찾거나 CDN에서 내려받습니다. handle은 송장 번호처럼——게임이 막히지 않고, 도착하면 콜백이 옵니다. handle을 Release하면 서비스가 메모리를 회수합니다.
개념 자세히
Addressable Asset System은 Resources 폴더 한계(단일 에셋 언로드 불가, 하드코딩 경로, CDN 없음)와 원시 AssetBundles(복잡한 API, 수동 의존성 추적)를 고칩니다. Addressables는 AssetBundles 위에 앉되 대부분의 작업을 자동화합니다.
각 에셋은 Addressable로 표시되고 Address(문자열 키, 예:
“prefabs/hero_sword”)를 받습니다. 에셋은 Groups로 들어가——그룹마다 하나의 AssetBundle로
컴파일됩니다. 그룹은 Local(빌드 안) 또는 Remote(CDN 업로드)일 수
있습니다. Labels로 태그별 다수 에셋 일괄 로드가 가능합니다.
주요 API:
Addressables.LoadAssetAsync<T>(key)
는
AsyncOperationHandle<T>
를 반환합니다——await하거나
.Completed
를 구독하세요. 중요: 끝나면
Addressables.Release(handle)
를 호출하세요——그렇지 않으면 에셋이 언로드되지 않습니다(메모리 누수).
InstantiateAsync는 로드+Instantiate를 합칩니다:
Destroy 대신
Addressables.ReleaseInstance(go)
로 언로드합니다. Addressables에는 play mode scripts도 있습니다: Fast Mode
(번들 베이크 없음——빠른 반복), Virtual Mode(시뮬레이션 번들), Packed Play Mode(실제 번들).
비동기 로드 흐름
Addressables.LoadAssetAsync("heroes/knight")
이미 메모리에 있나?
로컬 bundle 또는 CDN 다운로드
압축 해제 후 메모리에 오브젝트 생성
handle.Completed → result available
RefCount-- → 0 → 메모리에서 언로드
실습 단계
Addressables 설치
Window → Package Manager → "Addressables" 검색 → Install. Window → Asset Management → Addressables → Groups로 Groups 창을 엽니다.
에셋을 Addressable로 표시
Project에서 선택 → Inspector → "Addressable" 체크 → 주소 설정(예: "heroes/knight").
Groups 구성(Local vs Remote)
Addressables Groups 창: Create New Group → Build Path 설정(Local 또는 CDN용 Custom Remote Path). 에셋을 그룹으로 드래그.
콘텐츠 빌드
Addressables Groups → Build → New Build → Default Build Script. Play 또는 실제 게임 빌드 전에 빌드하세요.
올바르게 Load와 Release
주소로 LoadAssetAsync 또는 InstantiateAsync. handle을 보관. 끝나면 Release(handle) 또는 ReleaseInstance(go).
인터랙티브 시뮬레이터
비동기 로드 흐름 시뮬——Reference Count와 Memory Pool을 지켜보세요.
Load를 눌러 시작...
Loaded
0
Total Refs
0
Memory ~
0 MB
코드 예제
기본프리팹을 비동기 로드 후 Instantiate——await(C# async/await) 사용.
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Threading.Tasks;
public class AssetLoader : MonoBehaviour
{
AsyncOperationHandle<GameObject> knightHandle;
async void Start()
{
// Async load — does not block the main thread
knightHandle = Addressables.LoadAssetAsync<GameObject>("heroes/knight");
await knightHandle.Task; // Or .Completed callback
if (knightHandle.Status == AsyncOperationStatus.Succeeded)
{
Instantiate(knightHandle.Result, transform.position, Quaternion.identity);
}
}
void OnDestroy()
{
// REQUIRED: Release to avoid a memory leak
if (knightHandle.IsValid())
Addressables.Release(knightHandle);
}
}
// Or InstantiateAsync (auto-managed lifecycle)
async void SpawnEnemy(string address, Vector3 pos)
{
var handle = Addressables.InstantiateAsync(address, pos, Quaternion.identity);
await handle.Task;
// When the enemy dies: Addressables.ReleaseInstance(go) instead of Destroy
}코드 예제
고급Label로 다수 에셋 일괄 로드, 다음 씬을 백그라운드 프리로드.
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Collections.Generic;
using System.Threading.Tasks;
public class AssetManager : MonoBehaviour
{
readonly List<AsyncOperationHandle> handles = new();
// Load every asset labeled "ui_icons" at once
async Task LoadUIIcons()
{
var handle = Addressables.LoadAssetsAsync<Sprite>(
"ui_icons",
sprite => { Debug.Log($"Loaded: {sprite.name}"); }
);
await handle.Task;
handles.Add(handle);
}
// Preload the next scene in the background while the player is in-game
AsyncOperationHandle<UnityEngine.ResourceManagement.ResourceProviders.SceneInstance> sceneHandle;
public async void PreloadNextScene(string sceneAddress)
{
sceneHandle = Addressables.LoadSceneAsync(sceneAddress,
UnityEngine.SceneManagement.LoadSceneMode.Additive,
activateOnLoad: false); // Load but do not activate yet
await sceneHandle.Task;
Debug.Log("Scene preloaded, waiting for activation");
}
public async void ActivatePreloadedScene()
{
await sceneHandle.Result.ActivateAsync();
}
void OnDestroy()
{
foreach (var h in handles)
Addressables.Release(h);
}
}📌 빠른 정리
- ▸LoadAssetAsync = 논블로킹 로드; Handle 반환
- ▸끝나면 Release(handle) 필수——누수 방지
- ▸Instantiate/Destroy 대신 InstantiateAsync + ReleaseInstance
- ▸Label = 그룹으로 다수 에셋 일괄 로드
- ▸Fast Mode: 개발(빠름); Packed Mode: 실제 번들 테스트
⚠️ 흔한 실수
❌ handle을 절대 Release하지 않음 → 심각한 메모리 누수
에셋이 RAM에 남고 reference count가 0이 되지 않음
✅ OnDestroy에서 항상 Release, handle을 항상 보관
❌ Exception: Attempting to use an invalid operation handle
Release 후 handle 사용, 또는 이중 Release
✅ Release 전 handle.IsValid() 확인; 이후 handle을 null