UniTask 는 async 와 await 를 유니티에서 더 사용하기 편리하도록 만든 통합 패키지입니다. UniTask 는 Coroutine 에 비해 메모리 사용량, 성능, try-catch 사용 가능 및 return 을 사용할 수 있어서 Coroutine 대신 UniTask 를 이용하도록 권장하고 있는 상황입니다.
이 글에서는 같은 기능을 Coroutine 과 UniTask 를 이용해 각각 구현할 때 어떤 차이가 있는지에 대해서 알아보도록 하겠습니다.
▶ 설치
UniTask 는 기본으로 지원되는 기능이 아니기 때문에 Package Manager 를 이용해 설치해 줘야 합니다.
Package Manager 를 실행하고 위와 같이 [Add package from git URL...] 을 선택하고 나타나는 주소입력 창에 아래의 주소를 입력하고 [Add] 버튼을 클릭합니다.
https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask
▶ 예제
코루틴과 UniTask 를 비교하기 위해 다음과 같은 프로그램을 만들어 보도록 하겠습니다.
● 마우스 왼쪽 버튼을 클릭하면 0.5 초마다 sphere 생성
● 오른쪽 버튼을 클릭하면 종료
● 종료 후 생성된 sphere 수를 생성자 id 와 함께 출력
async void Update()
{
if (Input.GetMouseButtonDown(0))
{
if (Input.GetKey(KeyCode.LeftControl))
await SphereGenByUniTask();
else
SphereGenByCoroutine();
}
else if (Input.GetMouseButtonDown(1))
{
if (Input.GetKey(KeyCode.LeftControl))
{
cts.Cancel();
cts.Dispose();
}
else
stopGen = true;
}
}
유니티 스크립트를 생성하면 자동으로 생성되는 Update 함수 앞에 async keyword 가 있습니다. async 가 있으면 Update 함수가 비동기 함수로 동작해서 await 키워드를 사용할 수 있게 됩니다.
▶ 코루틴 이용
void SphereGenByCoroutine()
{
stopGen = false;
StartCoroutine(SC_SphereGen(++coroutine_spawn_id, (r) => {
print("SpawnerID: " + r.id.ToString() + " / Spawn Count: " + r.count.ToString());
}));
print("SphereGenByCoroutine Done");
}
코루틴을 이용하면 코루틴의 결과는 System.Action 과 Lambda Expression (람다식) 을 이용해 처리해야 합니다.
코드의 실행 순서는
- stopGen
- StartCoroutin(...)
- print("SphereGenByCoroutine done")
- 함수 종료
- stopGen이 true가 된 후에 람다식 -> print("SpawnerID: " ...);
이렇게 됩니다.
IEnumerator SC_SphereGen(int id, System.Action<SpawnInfo> done)
{
int spawned = 0;
float time = 0f;
while(true)
{
Vector3 p = UnityEngine.Random.insideUnitSphere * 5;
Vector3 pos = new Vector3(p.x, p.y, p.z);
var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
spawned++;
sphere.transform.position = pos;
time = 0f;
while (time < 0.5f && stopGen == false)
{
time += Time.deltaTime;
yield return null;
}
if (stopGen == true) break;
}
done.Invoke(new SpawnInfo() { id = id, count = spawned });
}
stopGen 이 false 인 동안에는 0.5 초마다 sphere 를 생성하는 코루틴 함수입니다. 여기서 0.5 초를 기다리는 코드를 yield return new WaitForSeconds(0.5f) 를 사용하지 않은 이유는 이 코드를 사용하면 중간에 취소시킬 방법이 없어 무조건 0.5초를 기다려야하기 때문입니다.
코루틴 함수가 종료될 때 인자로 받은 done 을 이용해 결과를 리턴합니다.
▶ UniTask 이용
async UniTask SphereGenByUniTask()
{
if (cts.IsCancellationRequested)
cts = new CancellationTokenSource();
var r = await SpawnSphere(++unitask_spawn_id);
print("SpawnerID: " + r.id.ToString() + " / Spawn Count: " + r.count.ToString());
print("SphereGenByUniTask Done");
}
UniTask 를 이용해 코루틴 함수를 대체하는 코드입니다. 코루틴과 다르게 await SpawnSphere() 함수가 종료될 때까지 코드가 다음으로 진행되지 않습니다. 코드는 처음부터 순차적으로 진행됩니다.
async UniTask<SpawnInfo> SpawnSphere(int id)
{
int spawned = 0;
try
{
while(true)
{
Vector3 p = UnityEngine.Random.insideUnitSphere * 5;
Vector3 pos = new Vector3(p.x, p.y, p.z);
var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
spawned++;
sphere.transform.position = pos;
await UniTask.Delay(500, cancellationToken: cts.Token);
if (cts.IsCancellationRequested) break;
}
}
catch (Exception e) { }
return new SpawnInfo() { id = id, count = spawned };
}
코루틴 함수에서는 0.5 초를 기다리다가 중간에 cancel 하는 것을 구현하기 위해 while 문을 이용했지만 UniTask 는 await UniTask.Dely(...) 에 cancellationToken 을 이용하는 것으로 간단하게 구현할 수 있습니다. 그리고 코루틴과 다르게 return 도 사용할 수 있습니다.
▶ 결론
처음에 언급했듯이 많은 실험에 의해서 UniTask 가 코루틴에 비해 메모리 사용과 성능면에서 더 좋은 결과를 내고 있다는 것은 입증된 사실입니다. 아래의 링크를 참조하시면 도움이 될 것 같습니다.
위의 예제에서 보았듯이 코루틴에서 UniTask 를 이용한 방식으로 구현하는 것도 크게 어렵지 않기 때문에 가급적 UniTask 로 대체하는 게 좋아 보입니다. 아래는 코루틴에서 사용하는 yield return... 을 대체하는 UniTask 함수들입니다.
yield return new WaitForSeconds/WaitForSecondsRealtime | await UniTask.Delay |
yield return null | await UniTask.Yield await UniTask.NextFrame |
yield return WaitForEndOfFrame | await UniTask.WaitForEndOfFrame |
new WaitForFixedUpdate | await UniTask.WaitForFixedUpdate |
yield return WaitUntil | await UniTask.WaitUntil |
이 외에도 UniTask 는 더 많은 기능을 제공하고 있습니다.
다만 코루틴은 StartCoroutine 호출 후 바로 다음 코드가 진행되기 때문에 로직에 따라 이 방식이 더 유리한 경우도 있을 수 있습니다. 메모리와 성능상의 문제가 크게 발생하지 않는다면 자신이 만든 로직에 따라 두 방식을 선택할 수 있는 유연성도 필요할 것입니다.
다음의 unitypackage 는 예제의 전체 코드입니다.
'게임 프로그래밍 > 유니티 활용' 카테고리의 다른 글
[유니티 활용] ScriptableObject 를 이용한 Item Drop Table 만들기 (0) | 2023.04.25 |
---|---|
[유니티 활용] ScriptableObject를 이용한 Data 관리 예제 - 몬스터 (0) | 2023.04.24 |
[유니티 활용] ParticleSystem 으로 초간단 총알 패턴 만들기 (10) | 2023.01.18 |
[유니티 활용] 액션 RPG 스타일의 실시간 전투 구현하기 (0) | 2023.01.14 |
[유니티 활용] StateMachine 을 활용한 3D 몬스터 AI 구현 (5) | 2023.01.12 |
댓글