티스토리 뷰

🎨/Unity

[Unity] Coroutines 코루틴

eungding 2022. 2. 20. 19:54
반응형

[Unity] NPC와 대화하기 (1) 에서 코루틴을 간단하게 살펴봤는데요, 조금 더 살펴보려고 합니다!
영어문서 (Coroutines) 가 더 내용이 자세하므로 이 문서를 기반으로 보겠습니다.

그리고 이 블로그 글을 추천합니다 👍 이 글 읽고 이해가 많이 되었어요!

 

[1] 코루틴 이란?

coroutine은 실행을 일시정지하고 제어권을 유니티에 넘겼다가
다음 프레임에 정지했던 지점부터 다시 코드를 실행할 수 있는 메소드 입니다.

In Unity, a coroutine is a method that can pause execution and return control to Unity but then continue where it left off on the following frame.


일반 메소드는 한 프레임 내에 실행을 완료하고 값을 반환하는데
코루틴은 조금 특별한 메소드 라고 할 수 있겠네요
(Swift의 Asynchronous Function과 비슷)

 

[2] 코루틴 사용하면 좋은 곳

HTTP 전송, asset 로딩 같이 완료될 때 까지 기다려야하는 긴 비동기 작업(long asynchronous operation)을
처리해야 하는 경우 코루틴을 사용하는 것이 좋습니다.

또한 특정 메소드를 호출해서
시간 경과에 따른 일련의 이벤트나 절차를 진행하고 싶을 때 코루틴을 사용하는 것이 좋습니다.
(예를 들어 시간이 지남에 따라 점점 알파값이 변하는 작업)

 

[3] 코루틴 오해 금지

coroutine 은 thread가 아닙니다.
코루틴 안에서 실행되는 동기 연산(Synchronous operation) 은 여전히 메인 스레드에서 실행됩니다.
메인 스레드에서 소비되는 CPU 시간을 줄이려면 다른 스크립트 코드와 마찬가지로 코루틴 내에서 blocking operations 을 피하는 게 중요합니다.

Unity 내에서 멀티 스레드 코드를 사용하려면 C# Job System 을 고려하세요

[4] 코루틴 문법

코루틴은 반환 타입이 IEnumerator 이고
body 어딘가에 yield return statement 를 포함하는 함수입니다.

IEnumerator Fade()
{
    Color c = renderer.material.color;
    for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
    {
        c.a = alpha;
        renderer.material.color = c;
        yield return null;
    }
}


yield는 '양보하다, 넘겨주다' 라는 의미를 가지고 있는데요
말그대로 yield return 문을 만나면 일시정지하고 유니티한테 제어권을 넘겨주게 됩니다.
그리고 다음 프레임 때 일시정지 했던 곳부터 다시 실행 됩니다.

그래서 위의 코드에서는 for문이 한번 실행되고 yield 문을 만나서 실행이 일시정지 되고..
다음 프레임에서 두번째 반복을 실행하고 yield 문을 만나서 다시 일시정지 되고..
이를 반복합니다.

이렇게 10번 반복되는 for문이 10개의 프레임에 걸쳐 각각 실행되고
알파값이 0.1 씩 점차 줄어들게 보이는 효과를 냅니다.

이게 가능한 이유는 yield에  의해 일시정지될 때는
코루틴 함수 내에 정의된 지역변수와 전달 받은 매개 변수가 다음 프레임 까지 보존되기 때문입니다.
그래서 Fade 함수 내의 loop counter가 코루틴의 라이프타임 동안 계속 보존될 수 있는 것!

위에서 '시간 경과에 따른 일련의 이벤트나 절차를 진행하고 싶을 때 코루틴을 사용하는 것이 좋습니다.' 라고 했는데
딱 적절한 예시가 문서에 나와있는 것 같아요 

 

사실 매 프레임마다 불리는 update 함수를 이용해서 동일한 효과를 낼 수도 있습니다. 
하지만 위처럼 코루틴 함수를 이용하는게 훨씬 편리합니다. 

 

[5] 코루틴 타임 딜레이

위에서 살펴본 것 처럼 기본적으로 유니티는 yield 문을 만나면 코루틴을 정지시켰다가
다음 프레임에 코루틴을 다시 실행시킵니다.
하지만 WaitForSeconds 를 이용해서 time delay를 둘 수 도 있습니다.
일시정지 후 원하는 시간이 지난 시점의 프레임에서 다시 작업이 재개되게 할 수 있는 것 입니다.

아래 코드는 일시정지 후, 다음 프레임이 아니라 0.1초 후의 프레임에 작업이 재개됩니다.

IEnumerator Fade()
{
    Color c = renderer.material.color;
    for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
    {
        c.a = alpha;
        renderer.material.color = c;
        yield return new WaitForSeconds(.1f);
    }
}


예를들어 적이 가까이 왔을 때 플레이어에게 경고해주는 알람을 만든다고 할 때,
every frame 마다 적이 가까이 왔는 지 체크하는 함수를 부른다면 overhead 가 있을 것 입니다.
그 때 코루틴을 이용하여 적당한 타임딜레이를 둘 수 있습니다.
그로 인해 게임플레이에 큰 영향을 주지 않고 유니티가 수행해야하는 체크 횟수를 줄일 수 있습니다.

IEnumerator DoCheck()
{
    for(;;)
    {
        if (ProximityCheck())
        {
            // Perform some action here
        }
        yield return new WaitForSeconds(.1f);
    }
}

 

또한 시간 및 프레임 속도 관리  문서를 보면 

이와 같은 시간 기반 액션을 다룰 때 기억해야 할 중요한 점은 게임의 프레임 속도가 일정하지 않으며 
Update 함수 콜 사이의 시간 간격 역시 일정하지 않다는 점입니다.

그 예로 한번에 한 프레임씩 서서히 오브젝트를 전진시키는 일을 생각해 봅시다. 
처음에는 각 프레임마다 고정 거리만큼 오브젝트를 옮기면 될 것처럼 보입니다.

그러나 프레임 시간이 일정하지 않기 때문에 오브젝트는 불규칙적인 속도로 움직이는 것처럼 보일 것입니다. 
프레임 시간이 10ms이면 이 오브젝트는 distancePerFrame 에 의해 초당 100회 전진합니다.
그러나 프레임 시간이 (CPU 로드 등으로 인해) 25ms로 증가하면 이 오브젝트는 초당 40회만 전진하게 되며 따라서 더 적은 거리만큼 이동합니다. 
이를 해결하려면 움직임의 크기를 Time.deltaTime에서 읽어올 수 있는 프레임 시간에 맞추어 스케일하면 됩니다.

 

프레임 시간 간격이 일정하지 않기 때문에

일정 시간 타임 딜레이를 세팅하는 게 좋을 것 같아보여요 (제가 잘 이해한거겠죠..?..)

 

또한 WaitForSeconds 뿐만아니라 

WaitWhile, WaitForEndOfFrame 등등 wait 관련 함수들이 여러개 있는데 문서를 참고해주세요

 

[6] 코루틴 return type

현재까지 yield return 문으로 두가지 타입을 살펴봤는데요,

yield return null;
yield return wait 관련 함수;

 

문서를 보면 YieldInstruction 을 상속받는 것들은 모두  yield return 문에 사용할 수 있는 것 같아요!

예를들어 AsyncOperation 도  YieldInstruction  을 상속받기 때문에 

이렇게 yield return 문에 사용될 수 있습니다.  

yield return AsyncOperation();

 

[Unity] UnityWebRequest / JsonUtility 글에서 사용하였던 

SendWebRequest 도 UnityWebRequestAsyncOperation 타입이기 때문에 

yield return 문과 함께 사용될 수 있는 것입니다.  (UnityWebRequestAsyncOperation은 AsyncOperation을 상속받는 클래스)

    IEnumerator Get(string url, Action<UnityWebRequest> callback)
    {
        UnityWebRequest request = UnityWebRequest.Get(url);
        yield return request.SendWebRequest();
        callback(request);
    }

 

 

[7] 코루틴 시작, 종료 시키기


# Start
StartCoroutine 을 호출해서 코루틴을 실행시킬 수있습니다.

void Update()
{
    if (Input.GetKeyDown("f"))
    {
        StartCoroutine(Fade());
    }
}

 


# Stop

코루틴 함수 내부에서는

- yield break 로 코루틴을 종료시킬 수 있습니다. (일반 함수의 return 같은 개념이라고 생각하면 됩니다.)

 

코루틴 함수 외부에서는 

1. StopCoroutine 과  StopAllCoroutines 을 호출해서 코루틴을 멈출 수 있습니다.
2. SetActive 를 false로 해서 GameObject를 비활성화 시킬때, GameObject에 attached 되어있던 코루틴이 멈춥니다.
3. Destroy(example) (example은 MonoBehaviour instance입니다) 을 호출하면 OnDisable 이 즉각 trigger 되고
유니티는 코루틴을 멈춥니다.

 

하지만 enabled 을 false로 세팅해서 MonoBehaviour 을 비활성화 시킨 경우, 유니티는 코루틴을 멈추지 않습니다.
(If you’ve disabled a MonoBehaviour by setting enabled to false, Unity doesn’t stop coroutines.)


 

반응형
댓글