콘텐츠로 이동

Unity 비동기 씬/에셋 로딩 최적화

Unity에서 LoadScene()을 동기로 호출하면 프레임이 멈춥니다. LoadSceneAsync와 Addressables를 활용한 비동기 로딩으로 로딩 바, 페이드 효과, 스트리밍 레벨 등을 구현할 수 있습니다.


using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class SceneLoader : MonoBehaviour
{
public IEnumerator LoadSceneWithProgress(string sceneName)
{
AsyncOperation op = SceneManager.LoadSceneAsync(sceneName);
op.allowSceneActivation = false; // 로드 완료 후 자동 전환 방지
while (!op.isDone)
{
// progress는 0~0.9 (allowSceneActivation = false일 때)
float progress = Mathf.Clamp01(op.progress / 0.9f);
UpdateProgressBar(progress);
// 로드 완료 시 전환 허용
if (op.progress >= 0.9f)
{
yield return new WaitUntil(() => Input.anyKeyDown);
op.allowSceneActivation = true;
}
yield return null;
}
}
void UpdateProgressBar(float value) { /* UI 갱신 */ }
}

using UnityEngine;
using UnityEngine.SceneManagement;
using System.Threading.Tasks;
public class AsyncSceneManager : MonoBehaviour
{
public async Task LoadSceneAsync(string sceneName,
System.IProgress<float> progress = null)
{
var op = SceneManager.LoadSceneAsync(sceneName);
op.allowSceneActivation = false;
while (op.progress < 0.9f)
{
progress?.Report(op.progress / 0.9f);
await Task.Yield();
}
progress?.Report(1f);
op.allowSceneActivation = true;
// 씬 완전 로드 대기
while (!op.isDone)
await Task.Yield();
}
}

using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
public class AddressableLoader : MonoBehaviour
{
// 씬 로드
public async void LoadAddressableScene(string address)
{
var handle = Addressables.LoadSceneAsync(address,
UnityEngine.SceneManagement.LoadSceneMode.Additive);
while (!handle.IsDone)
{
Debug.Log($"로딩: {handle.PercentComplete * 100:F0}%");
await Task.Yield();
}
if (handle.Status == AsyncOperationStatus.Succeeded)
Debug.Log("씬 로드 완료");
else
Debug.LogError("씬 로드 실패");
}
// 에셋 로드 및 캐시
private AsyncOperationHandle<GameObject> _prefabHandle;
public async Task<GameObject> LoadPrefabAsync(string address)
{
_prefabHandle = Addressables.LoadAssetAsync<GameObject>(address);
await _prefabHandle.Task;
return _prefabHandle.Result;
}
void OnDestroy()
{
// 반드시 해제
if (_prefabHandle.IsValid())
Addressables.Release(_prefabHandle);
}
}

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class FadeSceneTransition : MonoBehaviour
{
[SerializeField] CanvasGroup fadeCanvas;
public IEnumerator TransitionTo(string sceneName)
{
// 페이드 아웃
yield return StartCoroutine(Fade(0f, 1f, 0.5f));
var op = SceneManager.LoadSceneAsync(sceneName);
op.allowSceneActivation = false;
while (op.progress < 0.9f)
yield return null;
op.allowSceneActivation = true;
yield return new WaitUntil(() => op.isDone);
// 페이드 인
yield return StartCoroutine(Fade(1f, 0f, 0.5f));
}
IEnumerator Fade(float from, float to, float duration)
{
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
fadeCanvas.alpha = Mathf.Lerp(from, to, elapsed / duration);
yield return null;
}
fadeCanvas.alpha = to;
}
}

public class AssetPreloader : MonoBehaviour
{
readonly Dictionary<string, AsyncOperationHandle> _cache = new();
public async Task PreloadAsync(IEnumerable<string> addresses)
{
var tasks = new List<Task>();
foreach (var addr in addresses)
{
if (_cache.ContainsKey(addr)) continue;
var handle = Addressables.LoadAssetAsync<Object>(addr);
_cache[addr] = handle;
tasks.Add(handle.Task);
}
await Task.WhenAll(tasks);
}
public T Get<T>(string address) where T : Object
{
if (_cache.TryGetValue(address, out var handle))
return handle.Result as T;
return null;
}
}

항목설명
Addressables.Release()로드한 에셋은 반드시 해제
UnloadUnusedAssets()씬 전환 후 호출
allowSceneActivation준비 전 화면 전환 방지
씬 Additive 모드대형 씬을 청크로 분할 로드

비동기 씬 로딩의 핵심은 allowSceneActivation = false로 전환을 제어하고 진행도를 UI에 반영하는 것입니다. Addressables를 활용하면 빌드 크기를 줄이고 런타임 스트리밍이 가능해집니다. 에셋 핸들은 사용 후 반드시 Release해 메모리 누수를 방지하세요.