일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 게임 수학
- 개인 바이트
- Cartoon Rendering
- Windows Build
- Specular
- 가상 바이트
- C언어
- Cell Shader
- Toon Shader
- 3d
- working set
- 벡터
- 메모리 누수
- Rim Light
- ColorGradingLutPass
- URP
- 작업 집합
- Three(Two) Tone Shading
- 프로그래밍 기초
- URP로 변경
- VR
- Cell Look
- Private Bytes
- AppSW
- Virtual Byte
- OculusMotionVectorPass
- ASW(Application SpaceWarp)
- Today
- Total
WinCNT
UniTask를 인스톨하고 살짝 사용해봤다! 본문
서론
유니티에는 비동기 처리를 위한 몇 가지 기법들이 존재한다(멀티 스레드인지는 차치하고)
대표적인 것이 바로 Coroutine, Task, UniTask이다
정말 단순하게 설명하자면 Coroutine는 유니티에서 제공하는 비동기 처리 기법이고, Task는 C#(5.0 이상)에서 제공하는 비동기 처리 기법이다
UniTask는 Task를 Unity에서 효율적으로 사용할 수 있게 Cysharp(Cygames의 자회사)에서 만든 라이브러리라고 보면 될 것 같다
이번에는 UniTask를 설치하고 간단히 사용해본 내용을 정리해보고자 한다
근데 굳이 UniTask를 써야 함?
결론부터 말하자면 굳이 UniTask를 쓰는 게 낫다
필자도 이번에 조사하면서 알게 되었는데 여러 메리트가 있었다
우선 UniTask는 코루틴을 대체할 수 있으면서 퍼포먼스, 메모리 사용이 더 뛰어나다
(유니티에 최적화되어 있기 때문에 Task보다도 퍼포먼스가 좋음)
그리고 MonoBehaviour 없이도 작동하며 return, try-catch 등을 사용할 수 있다
8 Reasons to Ditch Coroutine for Async - Prog.World
설치하기
UniTask를 설치하는 방법은 크게 2가지가 있다
하나는 UniTask의 release에서 unitypackage 파일을 다운로드하고 프로젝트에 넣는 방법이다
https://github.com/Cysharp/UniTask/releases/tag/2.4.1
다른 하나는 Package Manager의 Add package from git URL에 다음을 입력하는 것이다
<https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask>
GitHub - Cysharp/UniTask: Provides an efficient allocation free async/await integration for Unity.
Coroutine를 UniTask로 바꿔보기
예를 들어 게임 오브젝트의 색을 빨강색에서 초록색으로 점점 바꾸는 Coroutine이 있다고 하자
using System.Collections;
using UnityEngine;
public class CoroutineScript : MonoBehaviour
{
public GameObject target;
private Material _material;
void Start()
{
_material = target.GetComponent<Renderer>().material;
_material.color = Color.red;
StartCoroutine(Run(2));
}
private IEnumerator Run(float seconds)
{
Color startColor = _material.color;
Color endColor = Color.green;
var elapsedTime = 0.0f;
while (elapsedTime < seconds)
{
elapsedTime += Time.deltaTime;
float d = elapsedTime / seconds;
float r = Mathf.Lerp(startColor.r, endColor.r, Mathf.Clamp01(d));
float g = Mathf.Lerp(startColor.g, endColor.g, Mathf.Clamp01(d));
float b = Mathf.Lerp(startColor.b, endColor.b, Mathf.Clamp01(d));
_material.color = new Color(r, g, b);
yield return new WaitForEndOfFrame();
}
}
}
위의 스크립트를 UniTask로 바꿔보면 다음과 같다
using Cysharp.Threading.Tasks;
using UnityEngine;
public class UniTaskScript : MonoBehaviour
{
public GameObject target;
private Material _material;
void Start()
{
_material = target.GetComponent<Renderer>().material;
_material.color = Color.red;
Run(2).Forget();
}
private async UniTask Run(float seconds)
{
Color startColor = _material.color;
Color endColor = Color.green;
var elapsedTime = 0.0f;
while (elapsedTime < seconds)
{
elapsedTime += Time.deltaTime;
float d = elapsedTime / seconds;
float r = Mathf.Lerp(startColor.r, endColor.r, Mathf.Clamp01(d));
float g = Mathf.Lerp(startColor.g, endColor.g, Mathf.Clamp01(d));
float b = Mathf.Lerp(startColor.b, endColor.b, Mathf.Clamp01(d));
_material.color = new Color(r, g, b);
// await UniTask.WaitForEndOfFrame();
await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate);
}
}
}
다음은 참고 사이트에서 발췌한 표이다
Coroutine | UniTask |
yield return new WaitForSeconds WaitForSecondsRealtime | await UniTask.Delay |
yield return null | await UniTask.Yieldawait UniTask.NextFrame |
yield return WaitForEndOfFrame | await UniTask.WaitForEndOfFrame await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate) |
new WaitForFixedUpdate | await UniTask.WaitForFixedUpdate |
yield return WaitUntil | await UniTask.WaitUntil |
참고로 await UniTask.WaitForEndOfFrame()는 제대로 작동하지 않은 버전이 있었는데 UniTask 2.3.1 이후부터 고쳐졌다고 한다
UnityTestRunnerでUniTaskを使おう! - Qiita
하지만 UniTask 2.4.1에서는 await UniTask.WaitForEndOfFrame()가 레거시가 되었으니 await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate)를 사용하도록 하자
await와 Forget()
위의 코드에서 Forget()이란 메소스들 사용한 이유는 그냥 async의 함수만 쓰면 경고가 발생하기 때문이다
그렇다고 한 번 더 await로 받으면 Coroutine과 다른 동작을 하게 된다
예를 들어 다음과 같은 Coroutine를 UniTask가 있다고 하자
// Coroutine
void Start()
{
Debug.Log("1");
StartCoroutine(Run());
Debug.Log("2");
}
private IEnumerator Run()
{
yield return new WaitForEndOfFrame();
Debug.Log("End Run");
}
// UniTask
async void Start()
{
Debug.Log("1");
await Run();
Debug.Log("2");
}
private async UniTask Run()
{
await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate);
Debug.Log("End Run");
}
비슷해보이는 코드지만 실제 움직임은 그렇지 않다
Coroutine의 경우는 Console 창에 1 > 2 > End Run 순으로, UniTask의 경우는 1 > End Run > 2 순으로 출력된다
즉, Coroutine은 yield를 만나면 StartCoroutine 다음의 처리가 실행되지만, await의 경우는 Run()이 끝날 때까지 기다린다
따라서 Coroutine와 똑같이 동작하게 하고 싶으면 await Run() 대신에 Run().Forget()를 사용해야 한다
주의! UniTask와 IEnumerator를 같이 사용하면 경고가 발생한다
음…뭐 사실 이것이 이 글을 작성하게 된 계기이다
사실 특정 타이밍에서 다음과 같은 경고가 엄청나게 발생한다는 이슈가 있었다
yield WaitForEndOfFrame is not supported on await IEnumerator or IEnumerator.ToUniTask(), please use ToUniTask(MonoBehaviour coroutineRunner) instead.
더블 클릭해도 Warning 메시지를 출력하는 코드로 이동할 뿐…
그래도 메시지 출력 처리에 브레이크 포인트를 걸고 재현하니 어디서 발생하는지에 대한 정보를 볼 수 있었다
범인은 바로 화면을 Fade하는 스크립트!
세세한 부분은 생략하고 문제가 된 부분만 발췌하면 다음과 같다
public async UniTask FadeOutAsync()
{
await Fade(0f, 1f);
}
public void FadeOut()
{
FadeOutAsync().Forget();
}
private IEnumerator Fade(float startAlpha, float endAlpha)
{
var elapsedTime = 0.0f;
while (elapsedTime < _fadeTime)
{
elapsedTime += Time.deltaTime;
_currentAlpha = Mathf.Lerp(startAlpha, endAlpha, Mathf.Clamp01(elapsedTime / _fadeTime));
SetMaterial();
yield return new WaitForEndOfFrame();
}
_currentAlpha = endAlpha;
SetMaterial();
}
즉 FadeOutAsync나 FadeOut에서는 UniTask를 쓰고 있으면서, Fade는 IEnumerator에 WaitForEndOfFrame로 구현되고 있었다…어흐흑
이 스크립트와 비슷한 걸 Coroutine으로 필자가 샘플로 만든 적이 있는데, 아마 다른 사람이 그걸 참고하면서 UniTask로 바꾸던 중에 조금 실수한 것으로 보인다
다행히 수정하는 것은 매우 간단해서 IEnumerator와 WaitForEndOfFrame를 마저 바꿔주면 된다
private async UniTask Fade(float startAlpha, float endAlpha)
{
var elapsedTime = 0.0f;
while (elapsedTime < _fadeTime)
{
// ...생략...
await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
}
_currentAlpha = endAlpha;
SetMaterial();
}
마무리
일단 정리는 이걸로 끝!
근데 그래픽스 관련 업무만 맡겨지다 보니, 비동기 처리를 필자가 사용할 기회는 거의 없을 거 같단 느낌이 든다
그래도 언젠간 다시 쓰겠지?
참고 사이트
UniTask入門 | Unity Learning Materials
'Unity > Unity 관련' 카테고리의 다른 글
Unity의 EditorWindow를 한 번 알아보자 그 첫번째! 덤으로 Reflection과 PropertyDrawer로 List 요소만 표시해보자! (0) | 2024.03.25 |
---|---|
Unity에서 커스텀 ShaderGUI를 구현해보자! 그 두번째 (0) | 2024.02.09 |
Unity 2022.2부터 Navigation이 AI Navigation로 바뀌었다길래 사용해봤다 (0) | 2023.11.14 |
Github Actions 사용해보기! (0) | 2023.10.31 |
Unity에서 커스텀 ShaderGUI를 구현해보자! 그 첫번째 (0) | 2023.10.11 |