유니티 UniTask 정리

    목차
728x90

먼저 UniTask란?

기존에 있던 Coroutine의 단점을 극복하고자 나온 라이브러리이다.

 

Coroutine하고 UniTask는 다음과 같이 정리해볼수 있다.

  • 실행 방식
    • 코루틴: Unity 가 주기적으로 실행
    • UniTask: 실행 주기 독립적이며, 즉시 실행
  • 성능
    • 코루틴: 매 프레임마다 실행되어 오버헤드 발생
    • UniTask: 실행 주기 제한 없어 효율적
  • 동시성
    • 코루틴: 단일 스레드에서 실행되어서 경쟁 상태 발생 가능
    • UniTask: 병렬 실행을 위한 도구들을 제공
  • 에러 처리
    • 코루틴: try-catch로 처리, 복잡할 수 있음
    • UniTask: ContinueWith 및 예외 처리 함수로 간편
  • 사용 용이성
    • 코루틴: 익숙하고 접근성 좋음
    • UniTask: 함수형 스타일로 약간의 학습 비용 필요

코루틴은 먼저 매 프레임마다 호출되므로 오버헤드가 발생하는게 제일 크다. 

특히 모바일 같은 환경이면 더욱 더 단일코어인 코루틴이면 버벅이는 현상이 심해질 수 있다.

다만 코루틴에 비해 UniTask는 자료도 생각보다 없고 정리가 잘 안되어 있어서 아무래도 배우기 어렵다.

 

그래서 해당 글은 UniTask관련 함수들과 사용법들을 자세히 알아볼 예정이다.

//먼저 using문을 선언해주어야 한다.
using Cysharp.Threading.Tasks;
//코루틴도 예시로 들거니까 유니티 엔진하고 시스템도 선언해준다.
using UnityEngine;
using System.Collections;

void Start()
{
    //UniTask 호출 방법
    TestUni().Forget();
    //코루틴 호출 방법
    TestCoroutine();
}

async UniTaskVoid TestUni()
{
	
    //첫 프레임 스킵
    await UniTask.Yield();
    //1초 대기
    await UniTask.Delay(1000);
    //1초 대기
    await UniTask.Delay(TimeSpan.FromSeconds(1f)); 
    //Func1Async(), Func2Async()가 모두 완료될때까지 대기
    await UniTask.WhenAll(Func1Async(), Func2Async());
    // Func1과 Func2 중 하나 완료 시에 실행
    await UniTask.WhenAny(Func1Async(), Func2Async());
    // FuncAsync 완료 후 실행
    await FuncAsync().ContinueWith(_ => {
    	//쓰고 싶은 메서드나 함수등... 
	});
    // 오류 발생 시 최대 3번 재시도
    await UniTaskAsyncEnumerable.Repeat(FuncAsync, 3)
          .AsyncRetry();
}
IEnumerator TestCoroutine()
{
    //첫 프레임 스킵
    yield return new WaitForEndOfFrame();
    //1초 대기
    yield new WaitForSeconds(1.0f);
    
    
    //코루틴 끝내기
    yield break;
}

 

코루틴은 예제도 많고 간단해서.. 간단하게 써보았다.

그 외에도 웹에서 호스팅도 가능하고 또한 Dotween과 같이 사용도 가능하다.

다만 Dotween이랑 같이 사용할려면 

Edit -> Project Settings->Player->Other Settings->Scripting Define Symbols 에서

UNITASK_DOTWEEN_SUPPORT를 선언해 주어야 한다.

 

추가로 UniTask를 기준으로 간단한 로딩씬을 구현한 스크립트를 제작해보았다.

이미지의 FillAmount를 기준으로 로딩정도를 차오르게 만들것이다.

using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using TMPro;
using Cysharp.Threading.Tasks;
public class LoadingSceneManager : MonoBehaviour
{
    public static string nextScene;
    [SerializeField] Image ProgressBar;
    [SerializeField] Image OtherImage;
    [SerializeField] float fillSpeed = 0.1f;
    [SerializeField] float otherImageFillSpeed = 0.2f;
    private bool fillingOtherImage = true;
    void Start()
    {
        LoadSceneAsync().Forget();
    }

    public static void LoadScene(string sceneName)
    {
        nextScene = sceneName;
        SceneManager.LoadScene("Loading Scene");
    }
    async UniTaskVoid LoadSceneAsync()
    {
        await UniTask.Yield(); // 첫 프레임 스킵

        AsyncOperation operation = SceneManager.LoadSceneAsync(nextScene);
        operation.allowSceneActivation = false;
        float timer = 0.0f;
        float targetFillAmount = 0.9f; // 목표로 하는 fillAmount 값 (90%)
        float timeToFill = 5.0f; // ProgressBar를 채우는 데 걸리는 시간

        while (!operation.isDone)
        {
            await UniTask.Yield();

            timer += Time.deltaTime;

            // ProgressBar를 천천히 차게 보이도록 보간 처리
            float progress = Mathf.Lerp(ProgressBar.fillAmount, operation.progress, timer / timeToFill);
            ProgressBar.fillAmount = progress;

            OtherImage.fillAmount = Mathf.Clamp01(OtherImage.fillAmount + (fillSpeed * Time.deltaTime));
            if (!(ProgressBar.fillAmount >= targetFillAmount)) continue;
            // ProgressBar가 목표치에 도달하면 나머지 시간을 기다린 후 씬 전환
            if (timer >= timeToFill)
            {
                operation.allowSceneActivation = true;
                break;
            }
            if (fillingOtherImage)
            {
                OtherImage.fillAmount += otherImageFillSpeed * Time.deltaTime;
                // OtherImage가 1에 도달하면 빠르게 비우도록 상태 변경
                if (!(OtherImage.fillAmount >= 1.0f)) continue;
                fillingOtherImage = false;
                OtherImage.fillAmount = 1.0f; // 최대로 채운 후 빠르게 비우도록
            }
            else
            {
                OtherImage.fillAmount -= otherImageFillSpeed * Time.deltaTime;
                // OtherImage가 0에 도달하면 다시 채우도록 상태 변경
                if (!(OtherImage.fillAmount <= 0.0f)) continue;
                fillingOtherImage = true;
                OtherImage.fillAmount = 0.0f; // 최저로 비운 후 빠르게 채우도록
            }
        }
    }
}
728x90