Unity/정보

[Unity] UniTask 유니티 비동기 프로그래밍

달시_Dalsi 2024. 12. 19. 00:55

UniTask란?

UniTask는 유니티 환경에 특화된 경량 비동기 라이브러리입니다. Task와 비슷한 기능을 제공하면서도 유니티 게임 개발에 적합한 최적화와 기능을 포함하고 있습니다. 특히 다음과 같은 장점을 가지고 있습니다:

  • 메모리 할당 최소화: Task는 비동기 작업을 수행할 때 메모리를 많이 사용하지만, UniTask는 GC(가비지 컬렉션) 부담을 줄여줍니다.
  • 유니티 API 제공: await UniTask.Delay()나 await UniTask.NextFrame()처럼 유니티 개발에 특화된 메서드를 지원합니다.

 

UniTask의 기본 사용법

UniTask를 사용하려면 UniTask GitHub에서 패키지를 설치해야 합니다.

 

https://github.com/Cysharp/UniTask#upm-package

 

GitHub - Cysharp/UniTask: Provides an efficient allocation free async/await integration for Unity.

Provides an efficient allocation free async/await integration for Unity. - Cysharp/UniTask

github.com

 

https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask

 

 

설치 후, 아래와 같은 방식으로 사용할 수 있습니다.

// 기본적인 UniTask 사용
using Cysharp.Threading.Tasks;
using UnityEngine;

public class UniTaskExample : MonoBehaviour
{
    async void Start()
    {
        Debug.Log("Start 작업 시작");
        await UniTask.Delay(2000); // 2초 대기
        Debug.Log("2초 후 작업 실행");
    }
}
  • UniTask.Delay()는 Task.Delay()보다 효율적입니다.
  • async 메서드에서 await를 사용하여 비동기 작업을 실행합니다.

 

Task와 UniTask의 차이점

UniTask를 이해하려면 먼저 Task와 어떤 점에서 차이가 있는지 알아야 합니다.

  Task UniTask
메모리 관리 비동기 작업 시 새로운 메모리 할당 (GC 부담 높음) 할당 없는 구조로 GC 부담 감소
유니티 친화성 유니티에 특화된 API 부족 UniTask.DelayFrame(), UniTask.Yield() 등 지원
사용 용이성 C# 표준 API이므로 유니티 외의 환경에서 유용 유니티 전용으로 설계
성능 고성능 환경에서 적합하나, 유니티 환경에서는 오버헤드 유니티 환경에서 경량화된 성능 제공

 

// Task 예제
using System.Threading.Tasks;

async void TaskExample()
{
    await Task.Delay(2000); // 2초 대기
    Debug.Log("Task 완료");
}

// UniTask 예제
using Cysharp.Threading.Tasks;

async void UniTaskExample()
{
    await UniTask.Delay(2000); // 2초 대기
    Debug.Log("UniTask 완료");
}
  • Task.Delay()는 GC에 부담을 줄수있고 유니티 환경에 최적화되지 않습니다.
  • 반면 UniTask.Delay()는 유니티에서 성능을 최적화한 API로 더 적합합니다.

 

UniTask의 기능

프레임 딜레이 및 업데이트 루프 관리

유니티의 Update()와 비동기 작업을 잘 사용하려면 UniTask.Yield()와 UniTask.NextFrame()을 활용합니다.

 

예제: 특정 프레임마다 작업 실행 - UniTask.NextFrame()

async UniTaskVoid PerformEveryFrame()
{
    while (true)
    {
        Debug.Log("현재 프레임: " + Time.frameCount);
        await UniTask.NextFrame();
    }
}

이 방법은 애니메이션이나 상태 업데이트를 프레임 단위로 처리할 때 유용합니다.

 

예제: 특정 조건에서 작업을 지연시키기 - UniTask.Yield()

async UniTaskVoid PerformWithYield()
{
    int count = 0;

    // 특정 조건에 따라 작업을 지연시키면서 반복
    while (count < 5)
    {
        Debug.Log("작업 실행: " + count);
        count++;

        // 현재 프레임에서 작업을 잠시 멈추고, 다음 프레임에서 이어서 실행
        await UniTask.Yield();
    }

    Debug.Log("작업 완료");
}
  • UniTask.Yield()는 현재 비동기 작업을 다음 프레임에서 이어서 실행하도록 합니다.
  • 이 방식은 특정 조건에 맞는 작업을 프레임마다 나누어 처리하고 싶을 때 유용합니다.
  • 예시에서는 count 값이 5가 될 때까지 UniTask.Yield()를 사용하여 작업을 매 프레임마다 실행하도록 했습니다.

 

UniTask.Yield()와 UniTask.NextFrame()의 차이

  • UniTask.Yield()는 프레임 단위로 작업을 일시 중단하고, NextFrame()은 현재 프레임 후의 첫 번째 프레임에 작업을 실행합니다.
  • UniTask.Yield()는 좀 더 유연하게 사용될 수 있으며, UniTask.NextFrame()은 한 프레임 딜레이를 확실히 사용할 때 사용됩니다.

 

 

씬 전환 및 로드 관리

씬 전환 시 UnityEngine.SceneManagement.SceneManager를 ToUniTask()로 변환하여 사용할 수 있습니다.

예제: 씬 로딩

using Cysharp.Threading.Tasks;
using UnityEngine.SceneManagement;

async UniTask LoadNewScene(string sceneName)
{
    Debug.Log("씬 로딩 시작");
    await SceneManager.LoadSceneAsync(sceneName).ToUniTask();
    Debug.Log("씬 로딩 완료");
}

이 방법은 로딩 화면이나 프로그레스 바를 추가할 때 사용할 수 있습니다.

 

 

병렬 및 연속 작업 처리

예제: 병렬 작업

여러 비동기 작업을 동시에 실행하고 모든 작업이 완료될 때까지 기다립니다.

async UniTask PerformParallelTasks()
{
    var task1 = UniTask.Delay(1000);
    var task2 = UniTask.Delay(2000);
    var task3 = UniTask.Delay(3000);

    await UniTask.WhenAll(task1, task2, task3);
    Debug.Log("모든 작업 완료");
}

 

예제: 연속 작업

async UniTask PerformSequentialTasks()
{
    await UniTask.Delay(1000);
    Debug.Log("첫 번째 작업 완료");

    await UniTask.Delay(2000);
    Debug.Log("두 번째 작업 완료");

    await UniTask.Delay(3000);
    Debug.Log("세 번째 작업 완료");
}​

 

UniTask 활용

NPC 애니메이션 관리

NPC가 대화, 이동, 상호작용을 순서대로 수행하도록 만듭니다.

async UniTask ControlNPC(NPC npc)
{
    await npc.PlayAnimation("Talk");
    await UniTask.Delay(2000);

    await npc.MoveTo(new Vector3(10, 0, 5));
    await UniTask.Delay(1000);

    await npc.PlayAnimation("Idle");
}

 

 

아이템 생성 및 처리

아이템이 생성된 후 일정 시간 뒤에 사라지도록 구현합니다.

async UniTask SpawnItem(GameObject itemPrefab, Vector3 position)
{
    var item = GameObject.Instantiate(itemPrefab, position, Quaternion.identity);
    Debug.Log("아이템 생성");
    
    await UniTask.Delay(5000); // 5초 후
    
    GameObject.Destroy(item);
    Debug.Log("아이템 소멸");
}

 

 

멀티플레이어 네트워크 대기

서버에서 응답을 받을 때까지 대기하도록 구현합니다.

async UniTask WaitForServerResponse(Server server)
{
    while (!server.HasReceivedResponse)
    {
        await UniTask.Delay(100);
    }
    Debug.Log("서버 응답 받음");
}

 

UniTask 사용 시 유의점

  • GC 부담 최소화: UniTask는 효율적이지만, 비동기 작업을 과도하게 사용하면 여전히 성능 문제가 발생할 수 있습니다.
  • 코드 가독성: 비동기 작업이 많아지면 코드가 복잡해질 수 있으므로 주의해야합니다.

 

마무리

UniTask는 유니티 환경에 최적화된 비동기 프로그래밍 라이브러리로, 기존의 Task보다 더 효율적으로 작동합니다. 특히 유니티 게임 개발에서 자주 사용하는 작업을 간단하고 빠르게 처리할 수 있어 생산성을 크게 높여줍니다. 비동기 프로그래밍을 이해하고 UniTask를 적극 활용하면 더 최적화된 게임을 개발할 수 있을 것입니다.