콘텐츠로 이동

Unity Addressables 비동기 로딩 심화 가이드

Addressables는 Unity의 에셋 관리 시스템으로, Resources 폴더와 AssetBundle의 한계를 극복합니다. 비동기 로딩, 콘텐츠 업데이트, 메모리 최적화를 표준화된 API로 제공합니다.


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);
}
}

// InstantiateAsync: 로딩 + 인스턴스화 한 번에
async void SpawnEnemy(Vector3 position)
{
var handle = Addressables.InstantiateAsync("enemy_prefab",
position, Quaternion.identity);
GameObject enemy = await handle.Task;
// 오브젝트 삭제 시 Release까지 자동 처리
// Addressables.ReleaseInstance(enemy);
}

// 같은 레이블을 가진 에셋 전체 로드
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);
}
}

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;
}
}
}

// 씬 전환 전 에셋 사전 로드
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;
}
}

// 핸들 추적으로 안전한 해제
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()에셋 해제 (필수)