Unity Addressables 비동기 로딩 심화 가이드
Addressables는 Unity의 에셋 관리 시스템으로, Resources 폴더와 AssetBundle의 한계를 극복합니다. 비동기 로딩, 콘텐츠 업데이트, 메모리 최적화를 표준화된 API로 제공합니다.
1. 기본 비동기 로딩
섹션 제목: “1. 기본 비동기 로딩”using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;
public class AssetLoader : MonoBehaviour{ // 에디터에서 에셋 참조 설정 [SerializeField] private AssetReference prefabRef;
private AsyncOperationHandle<GameObject> _handle;
// async/await 방식 (권장) async void Start() { try { GameObject prefab = await Addressables.LoadAssetAsync<GameObject>( "player_prefab").Task;
Instantiate(prefab); } catch (Exception ex) { Debug.LogError($"로딩 실패: {ex.Message}"); } }
// 콜백 방식 void LoadWithCallback() { _handle = Addressables.LoadAssetAsync<GameObject>("player_prefab"); _handle.Completed += OnLoadComplete; }
private void OnLoadComplete(AsyncOperationHandle<GameObject> handle) { if (handle.Status == AsyncOperationStatus.Succeeded) Instantiate(handle.Result); else Debug.LogError("로딩 실패"); }
void OnDestroy() { // 반드시 해제 — 미해제 시 메모리 누수 if (_handle.IsValid()) Addressables.Release(_handle); }}2. 인스턴스 직접 생성
섹션 제목: “2. 인스턴스 직접 생성”// InstantiateAsync: 로딩 + 인스턴스화 한 번에async void SpawnEnemy(Vector3 position){ var handle = Addressables.InstantiateAsync("enemy_prefab", position, Quaternion.identity);
GameObject enemy = await handle.Task;
// 오브젝트 삭제 시 Release까지 자동 처리 // Addressables.ReleaseInstance(enemy);}3. 레이블 기반 배치 로딩
섹션 제목: “3. 레이블 기반 배치 로딩”// 같은 레이블을 가진 에셋 전체 로드async Task<IList<T>> LoadByLabel<T>(string label){ var handle = Addressables.LoadAssetsAsync<T>( label, asset => Debug.Log($"로드됨: {asset}"), // 각 에셋 로드 시 콜백 Addressables.MergeMode.Union);
return await handle.Task;}
// 사용var allEnemies = await LoadByLabel<GameObject>("Enemies");var allMusic = await LoadByLabel<AudioClip>("BGM");4. 다운로드 크기 확인 및 다운로드
섹션 제목: “4. 다운로드 크기 확인 및 다운로드”public class ContentDownloader : MonoBehaviour{ [SerializeField] private Slider progressBar; [SerializeField] private Text sizeText;
async void Start() { // 다운로드 크기 확인 var sizeHandle = Addressables.GetDownloadSizeAsync("level_1"); long size = await sizeHandle.Task;
if (size > 0) { sizeText.text = $"다운로드 필요: {size / 1024 / 1024}MB"; await DownloadWithProgress("level_1"); } }
async Task DownloadWithProgress(string key) { var downloadHandle = Addressables.DownloadDependenciesAsync(key);
while (!downloadHandle.IsDone) { progressBar.value = downloadHandle.PercentComplete; await Task.Yield(); }
progressBar.value = 1f; Addressables.Release(downloadHandle); }}5. 씬 로딩
섹션 제목: “5. 씬 로딩”using UnityEngine.ResourceManagement.ResourceProviders;
public class SceneManager : MonoBehaviour{ private AsyncOperationHandle<SceneInstance> _sceneHandle;
async Task LoadSceneAsync(string sceneKey, bool additive = false) { var mode = additive ? UnityEngine.SceneManagement.LoadSceneMode.Additive : UnityEngine.SceneManagement.LoadSceneMode.Single;
_sceneHandle = Addressables.LoadSceneAsync(sceneKey, mode);
// 진행률 표시 while (!_sceneHandle.IsDone) { float progress = _sceneHandle.PercentComplete; Debug.Log($"씬 로딩: {progress:P0}"); await Task.Yield(); } }
async Task UnloadSceneAsync() { if (_sceneHandle.IsValid()) { await Addressables.UnloadSceneAsync(_sceneHandle).Task; } }}6. 의존성 사전 로드 (Preloading)
섹션 제목: “6. 의존성 사전 로드 (Preloading)”// 씬 전환 전 에셋 사전 로드public class LoadingScreen : MonoBehaviour{ async Task PreloadAssets() { var keys = new List<string> { "player_prefab", "ui_hud", "level_01_environment" };
// 모든 의존성 동시 다운로드 var handles = keys.Select(k => Addressables.DownloadDependenciesAsync(k)).ToList();
// 전체 완료 대기 while (handles.Any(h => !h.IsDone)) { float totalProgress = handles.Average(h => h.PercentComplete); UpdateProgressUI(totalProgress); await Task.Yield(); }
foreach (var handle in handles) Addressables.Release(handle); }}7. AssetReference를 통한 타입 안전 참조
섹션 제목: “7. AssetReference를 통한 타입 안전 참조”// AssetReference: 인스펙터에서 에셋을 끌어다 놓는 방식[Serializable]public class GameConfig{ public AssetReference PlayerPrefab; public AssetReferenceT<AudioClip> BgmClip; // 타입 안전 public AssetReferenceSprite UIIcon; // Sprite 전용}
public class GameManager : MonoBehaviour{ [SerializeField] private GameConfig config;
async void Start() { // 타입 안전 로딩 var player = await config.PlayerPrefab.InstantiateAsync().Task; var bgm = await config.BgmClip.LoadAssetAsync<AudioClip>().Task; }}8. 메모리 관리 패턴
섹션 제목: “8. 메모리 관리 패턴”// 핸들 추적으로 안전한 해제public class AssetCache : IDisposable{ private readonly Dictionary<string, AsyncOperationHandle> _handles = new();
public async Task<T> LoadAsync<T>(string key) { if (_handles.TryGetValue(key, out var existing)) return (T)existing.Result;
var handle = Addressables.LoadAssetAsync<T>(key); _handles[key] = handle; return await handle.Task; }
public void Unload(string key) { if (_handles.TryGetValue(key, out var handle)) { Addressables.Release(handle); _handles.Remove(key); } }
public void Dispose() { foreach (var handle in _handles.Values) Addressables.Release(handle); _handles.Clear(); }}| API | 용도 |
|---|---|
LoadAssetAsync<T>() | 에셋 로드 |
InstantiateAsync() | 에셋 로드 + 인스턴스화 |
LoadAssetsAsync(label) | 레이블 기반 배치 로드 |
LoadSceneAsync() | 씬 로드 |
DownloadDependenciesAsync() | 다운로드 |
Release() | 에셋 해제 (필수) |