멈추지 않고 끈질기게

[Unity] 코루틴(CoRoutine) 본문

Unity

[Unity] 코루틴(CoRoutine)

sam0308 2022. 12. 17. 13:20

※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.

※ 해당 포스팅은 Unity 2021.3.15f1 버전을 기준으로 작성되었습니다.

 

서문

유니티에서 매프레임마다 실행되어야 하는 로직은 Update()에서 호출하면 됩니다.

다만 매 프레임마다 호출할 필요는 없지만, 어느정도 주기적으로 호출되어야 하는 함수를

Update()에서 호출하는 것은 효율적인 방법이 아닙니다.

 

Invoke()를 통해 특정 함수를 일정 시간 후에 실행시키는 방법도 존재하지만, 

1회성 호출은 몰라도 반복적으로 호출해야 하는 경우에는 적합하지 않습니다.

유니티에서는 이런 경우 코루틴(Coroutine)을 사용하여 해결할 수 있습니다.

 

작성 및 사용법

코루틴 함수의 경우 System.Collections.IEnumerator 인터페이스를 반환형으로 가지는 함수의 형태로 선언하며,

반환 값을 yield return 의 형태로 반환해야 합니다.

반환 값에 따라 코루틴 함수가 얼마나 지연된 후 다시 실행될지 조절할 수 있습니다.

또한 코루틴 함수는 StartCoroutine() 함수를 통해 실행합니다.

using System.Collections;
using UnityEngine;

public class Test : MonoBehaviour
{
    Coroutine targetRoutine;
    bool done;

    void Start()
    {
        StartCoroutine("Check"); // string 매개변수로 호출(함수명)
        StartCoroutine(nameof(Check)); // string 매개변수로 호출하는 다른 방식
        StartCoroutine(Check()); // 함수를 직접 매개변수로 호출.
    }

    IEnumerator Check()
    {
    	// Update()의 다음 프레임까지 지연 후 다시 실행
        yield return null; 
        
        // FixedUpdate()의 다음 프레임까지 지연 후 다시 실행
        yield return new WaitForFixedUpdate(); 
        
        // 현재 프레임에서 카메라 및 GUI 렌더링이 완료될때까지 지연 후 다시 실행
        yield return new WaitForEndOfFrame(); 
        
        // 매개변수인 함수가 true를 반환할동안 지연, false 반환 시 다시 실행
        yield return new WaitWhile(() => done); 
        
        // 매개변수인 함수가 false를 반환할동안 지연, true 반환 시 다시 실행
        yield return new WaitUntil(() => done); 
        
        // (매개변수)초만큼 지연 후에 다시 실행(Time.timescale 영향 O)
        yield return new WaitForSeconds(5.0f); 
        
        // (매개변수)초만큼 지연 후에 다시 실행(Time.timescale 영향 X)
        yield return new WaitForSecondsRealtime(5.0f); \
        
        // 해당 라인 접근 시 코루틴 종료
        yield return break;
    }
}

 

간단한 예시로 코루틴을 통해 1초 단위로 갱신되는 타이머를 만들 수 있습니다.

이 경우 코루틴의 지연 시간은 1초로 고정되므로, WaitForSeconds 변수를 미리 캐싱하여 성능을 절약할 수 있습니다.

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class Test : MonoBehaviour
{
    [SerializeField] Text timer; // 타이머 UI
    WaitForSeconds oneSec; // WaitForSeconds 변수 캐싱
    int mm, ss;
    bool gameOver;

    void Awake()
    {
        oneSec = new WaitForSeconds(1.0f); // 1초로 설정
        gameOver = false;
        mm = 0; ss= 0;
    }

    void Start()
    {
        StartCoroutine(Timer());
    }

    IEnumerator Timer()
    {
        while(!gameOver)
        {
            timer.text = string.Format("{0:00}:{1:00}", mm, ss);
            //60초가 되면 분 단위 갱신
            if(++ss == 60)
            {
                ss = 0;
                mm++;
            }
            yield return oneSec; // 1초 지연
        }
    }
}

 

코루틴 중지

실행된 코루틴은 StopCoroutine() 또는 StopAllCoroutine() 함수를 통해 중단할 수 있습니다.

StopAllCoroutine()은 해당 스크립트에서 실행한 모든 코루틴을 중지시킵니다.

 

StopCoroutine()의 경우 매개변수로 string 타입을 넘길 경우 해당 이름의 코루틴 함수를 중지시킵니다.

다만 코루틴은 스레드와 비슷하게 동일 코루틴 함수를 동시에 여러개 실행시킬 수 있는데,

string 타입을 매개변수로 할 경우 해당 이름의 모든 코루틴을 중지시킵니다.

특정 코루틴만 중지시키고 싶을 경우 Coroutine 클래스 변수를 매개변수로 넘겨주면 됩니다.

 

사실 StartCoroutine() 은 Coroutine 클래스를 반환하는 함수입니다.

Coroutine 타입의 변수를 StartCoroutine()으로 코루틴 함수를 실행하면서 동시에 초기화 해두면,

나중에 해당 코루틴만 정지하고 싶을 때 매개변수로 사용할 수 있습니다. 

using System.Collections;
using UnityEngine;

public class Test : MonoBehaviour
{
    Coroutine targetRoutine; //코루틴 할당용 변수

    void Start()
    {
        targetRoutine = StartCoroutine(nameof(A)); // A1
        StartCoroutine(nameof(A));                 // A2
        StartCoroutine(nameof(B));                 // B
    }

    void TargetStop()
    {
        StopCoroutine(targetRoutine); // A1 중지
    }

    void CoStop()
    {
        StopCoroutine(nameof(A)); // A1, A2 중지
    }

    void AllStop()
    {
        StopAllCoroutines(); // A1, A2, B 중지
    }

    IEnumerator A()
    {
        yield return null;
    }
    IEnumerator B()
    {
        yield return null;
    }
}

 

코루틴의 특징

코루틴은 얼핏 보면 스레드와 비슷해 보이지만, 실제로는 차이가 있습니다.

다음과 같은 코드 실행 시 로그에 0 값이 나오는걸 기대할 수 있지만,

실제로는 스레드 간 동기화가 되어있지 않기 때문에 결과값이 예측 불가능합니다.

using System.Threading;
using UnityEngine;

public class Test : MonoBehaviour
{
    int limit = 100000; //최대 반복 횟수
    int total = 0;

    Thread t1, t2;

    void Start()
    {
        for (int i = 0; i < 5; i++)
            ThreadTest();
    }

    void ThreadTest()
    {
        t1 = new Thread(Plus);
        t2 = new Thread(Minus);

        t1.Start();
        t2.Start();
        // t1, t2가 끝날때까지 대기
        t1.Join(); 
        t2.Join(); 

        Debug.Log(total); //결과값 출력
        total = 0;
    }

    void Plus()
    {
        for(int i = 0;  i < limit; i++)
            total++;
    }

    void Minus()
    {
        for (int i = 0; i < limit; i++)
            total--;
    }
}

그림 1. 쓰레드(동기화 X)

하지만 위와 같은 기능의 코드를 코루틴으로 작성 시 항상 결과값이 0으로 나오는 것을 확인할 수 있습니다.

사실 코루틴은 그저 메인 루틴과 별개의 서브 루틴을 만들 뿐이며, 메인 루틴과 서브 루틴 모두 싱글스레드로 실행됩니다.

즉, 코루틴 사용 ≠ 멀티스레드 프로그래밍 이란 뜻입니다.

 

따라서 스레드와 같은 동기화 관련 이슈는 발생하지 않지만, 코루틴을 사용한다고 해서

멀티스레드 프로그래밍과 같은 성능을 기대할 수 없습니다.

 

하지만 일정시간 지연 후 실행 또는 주기적인 반복 실행이 필요한 로직의 경우,

코루틴으로 작성하여 메인 루틴을 방해하지 않도록 하며 가독성을 높일 수 있습니다.

게임의 경우 특히 지연 실행(ex:쿨타임)이나 주기적으로 반복 실행(ex:타이머)해야 하는 내용이 많기 때문에,

해당 로직을 Update()에 넣어야 할 지 코루틴으로 실행해야 할 지 잘 판단해야 하겠습니다.

 

'Unity' 카테고리의 다른 글

[Unity][UI] Content Size Fitter  (0) 2023.04.13
[Unity][UI] 레이아웃 그룹(Layout Group)  (0) 2023.03.31
[Unity][번외] Transform.SetPositionAndRotation  (0) 2023.02.15
[Unity][2D] 타겟을 향한 회전  (0) 2023.02.11
[Unity] Script Lifecycle  (0) 2022.12.16