Unity에서 시간차 연출이나 비동기 로딩을 구현할 때 가장 먼저 떠오르는 도구는 코루틴이다. 하지만 작업이 여러 단계로 이어지면 yield return이 중첩되고, 반환값과 예외를 다루기도 불편해진다.
Unity의 Awaitable은 이런 흐름을 C#의 async와 await 문법으로 표현한다. 다음 프레임, 물리 프레임, 지정 시간뿐 아니라 메인 스레드와 백그라운드 스레드 전환도 명시할 수 있다. 특히 로딩 화면, 절차적 데이터 생성, 복잡한 UI 연출처럼 순서가 중요한 코드에서 효과가 크다.
코루틴을 프레임 단위 Awaitable로 바꾸기
다음 코드는 오브젝트가 파괴되거나 플레이 모드가 종료될 때 자동으로 중단되는 페이드 처리다.
using System.Threading;
using UnityEngine;
public sealed class CanvasFader : MonoBehaviour
{
[SerializeField] private CanvasGroup canvasGroup;
public async Awaitable FadeToAsync(
float target,
float duration,
CancellationToken token)
{
float start = canvasGroup.alpha;
float elapsed = 0f;
while (elapsed < duration)
{
token.ThrowIfCancellationRequested();
elapsed += Time.unscaledDeltaTime;
float t = Mathf.Clamp01(elapsed / duration);
canvasGroup.alpha = Mathf.Lerp(start, target, t);
await Awaitable.NextFrameAsync(token);
}
canvasGroup.alpha = target;
}
private async Awaitable Start()
{
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
destroyCancellationToken,
Application.exitCancellationToken);
try
{
await FadeToAsync(1f, 0.5f, linkedCts.Token);
}
catch (OperationCanceledException)
{
// 종료 과정의 정상적인 취소
}
}
}
destroyCancellationToken을 연결하면 씬 전환으로 컴포넌트가 사라진 뒤에도 비동기 루프가 남는 문제를 막을 수 있다. Application.exitCancellationToken은 에디터의 플레이 모드 종료까지 처리한다.
무거운 계산만 백그라운드로 보내기
맵 높이 데이터나 대량의 룩업 테이블처럼 Unity API를 사용하지 않는 계산은 워커 스레드에서 처리할 수 있다.
private static async Awaitable<int[]> BuildNoiseAsync(
int count,
CancellationToken token)
{
await Awaitable.BackgroundThreadAsync();
int[] values = new int[count];
bool canceled = false;
for (int i = 0; i < values.Length; i++)
{
if (token.IsCancellationRequested)
{
canceled = true;
break;
}
values[i] = Hash(i) & 255;
}
await Awaitable.MainThreadAsync();
if (canceled)
token.ThrowIfCancellationRequested();
return values;
}
private static int Hash(int value)
{
unchecked
{
value ^= value >> 16;
value *= 0x45d9f3b;
value ^= value >> 16;
return value;
}
}
핵심은 계산이 끝난 뒤 MainThreadAsync로 복귀하는 것이다. GameObject, Transform, Texture2D 같은 Unity 객체는 백그라운드 스레드에서 생성하거나 변경하면 안 된다. 워커 스레드에서는 순수 데이터만 만들고, 실제 게임 오브젝트 반영은 메인 스레드에서 수행한다.
프레임 단계에 맞춰 작업 배치하기
Awaitable은 단순한 시간 대기 외에도 Unity 실행 루프의 특정 지점을 기다릴 수 있다.
private async Awaitable CaptureAsync(CancellationToken token)
{
await Awaitable.EndOfFrameAsync(token);
Texture2D shot = ScreenCapture.CaptureScreenshotAsTexture();
await SavePixelsAsync(shot.GetRawTextureData(), token);
Destroy(shot);
}
NextFrameAsync는 다음 프레임, FixedUpdateAsync는 다음 물리 갱신, EndOfFrameAsync는 현재 프레임의 렌더링 작업 이후에 재개된다. 필요한 시점이 코드에 직접 드러나므로 WaitForEndOfFrame 객체를 따로 관리할 필요도 없다.
단, EndOfFrameAsync는 메인 스레드 전용이며 에디터의 편집 모드나 배치 실행에서는 완료되지 않을 수 있다. 자동화 테스트에서는 실행 환경을 구분해야 한다. 세부 동작은 Unity Awaitable 공식 문서에서 확인할 수 있다.
실전에서 지켜야 할 경계
Awaitable은 모든 코루틴을 무조건 대체하는 도구가 아니다. 인스펙터에서 실행 흐름을 관찰해야 하는 연출이나 기존 코루틴 기반 시스템은 그대로 두는 편이 단순할 수 있다. 반대로 반환값, 취소, 예외 처리, 스레드 전환이 필요한 작업은 Awaitable이 훨씬 읽기 쉽다.
또한 백그라운드 전환 자체가 계산을 빠르게 만드는 것은 아니다. 몇백 개의 단순 반복문까지 워커 스레드로 보내면 전환 비용이 더 커질 수 있다. 프로파일러로 메인 스레드 병목을 확인한 뒤, 수 밀리초 이상 걸리는 순수 계산에만 적용하는 것이 안전하다.
결론
Unity Awaitable의 실전 핵심은 세 가지다. 프레임 대기는 NextFrameAsync, 수명 관리는 CancellationToken, 무거운 순수 계산은 BackgroundThreadAsync로 분리한다. 그리고 Unity 객체를 만지기 전에는 반드시 메인 스레드로 돌아온다. 이 경계만 명확히 지키면 코루틴보다 순서가 잘 보이고, 취소와 오류에도 강한 비동기 게임 로직을 만들 수 있다.
'Unity·Unreal 실전' 카테고리의 다른 글
| Unity 에디터 도구 설정이 재컴파일마다 초기화될 때, ScriptableSingleton 저장 위치부터 확인하세요 (0) | 2026.07.16 |
|---|---|
| Unreal Engine Details 패널 확장하기: IDetailCustomization으로 반복 설정 버튼 만들기 (0) | 2026.07.09 |
| Unreal Live Coding 실전: 에디터 재시작 없이 고쳐도 되는 코드와 안 되는 코드 구분하기 (0) | 2026.07.06 |
| Unity BuildPlayerWindow 커스텀 빌드 버튼으로 실수 없는 팀 빌드 파이프라인 만들기 (1) | 2026.07.02 |
| Unity DOTS 없이 멀티스레드로 수천 오브젝트 움직이기: TransformAccessArray 실전 (0) | 2026.06.29 |