[Unity] 코루틴(Coroutine)에 대해서

조재훈·2024년 2월 9일

개요

코루틴은 내가 유니티 엔진을 접하고 나서 유용하게 쓰고 있는 기능 중 하나이다.
주로 애니메이션 효과나 몇 초간의 딜레이를 줄 때 주로 사용했었는데 솔직히 사용하면서 정확히 알고 사용하지는 않았다. 그냥 경험에 의거해 코딩을 했었는데 이제는 좀 어떤 기술을 사용하더라도 정확히 알고 사용하는 것이 중요하다고 생각해 블로그에 정리하려한다.

코루틴이란?

코루틴은 유니티에서 제공하는 기능으로 코드 내에서 구문 실행 도중에 처리를 대기시키거나 함수를 병렬로 동시에 처리하도록 구현할 수 있는 기능이다.
유니티 공식 문서에 따르면

코루틴을 사용하면 작업을 다수의 프레임에 분산할 수 있다. Unity에서 코루틴은 실행을 일시 정지하고 제어를 Unity에 반환하지만 중단한 부분에서 다음 프레임을 계속할 수 있는 메서드임

대부분의 경우엔 메서드를 호출하면 실행을 완료한 뒤 호출한 메서드에 제어와 선택적 반환 값을 반환한다. 즉 메서드 내에서 발생한 모든 행동은 단일 프레임 업데이트 내에서 발생한다

하지만 코루틴은 스레드가 아니라는 점을 명심하자. 코루틴의 동기 작업은 여전히 메인 스레드에서 실행된다

예제

오브젝트의 알파값을 1에서 0(안보일때까지)으로 점점 줄여가는 코드를 작성하려 할 때

  • 코루틴을 사용하지 않으면
void Fade()
{
	Color c = renderer.material.color;
    for(float alpha = 1f; alpha >= 0; alpha -=0.1f)
    {
    	c.a = alpha;
        renderer.material.color = c;
    }
}

위 코드에서 Fade 함수는 우리가 기대한 서서히 사라지는 효과가 보이지 않고 바로 안보이게 될 것이다. 이 메서드는 단일 프레임 업데이트 내에서 전체를 실행하기 때문
쉽게 말해 for 루프에서 알파값은 빠르게 변경하긴 하지만 렌더링이 발생한 후에만 최종 결과를 볼 수 있다.
알파값이 프레임 끝에서 0으로 설정되기 때문에 점진적으로 투명해지는 현상 없이 즉시 사라지는 것 처럼 보이는 것이다

  • 코루틴 사용
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 return null을 통해 코루틴 메서드의 실행이 일시 중지되고 Unity에 제어권을 넘겨 일반 프레임 업데이트 및 렌더링 프로세스를 해서 변경된 알파값에 대해 렌더링을 진행하기 때문에 부드럽게 사라지는 듯한 효과가 날 수 있다

Update문 vs Coroutine

공통점

  • Update와 코루틴 모두 Unity에서 프레임 단위로 실행된다
  • 시간이 지남에 따라 게임 요소를 업데이트하고 수정하는 방법을 제공해준다

차이점

Update

  • 스크립트의 수명 동안 지속적으로 실행됨
  • 매 프레임마다 실행됨
  • 사용자 입력, 물리 계산 등에 사용됨

코루틴

  • 일시정지 및 재개가 가능함
  • 일반 프레임 업데이트 주기와 비동기적으로 실행됨
  • 메인 스레드를 차단하지 않고 여러 프레임에 걸쳐 작업을 처리하는 데 유용하다
  • 시간 처리, 지연 및 애니메이션에 적합함

vs Invoke

Invoke 함수도 코루틴과 같이 일정 시간 만큼 지연시켰다가 코드를 동작하게 만드는 함수인데
간단히 비교하자면 코루틴은 Update문과는 별개로 동작하는 또 다른 서브루틴을 만들 수 있고 매개변수를 넘길 수 있다는 장점이 있다

코루틴 사용

코루틴을 사용하기 위해 알아두어야 할 점이 있다

  • 반환 타입은 반드시 IEnumerator로
  • 코루틴 문 내에 반드시 yield 키워드를 사용해 return문을 작성해야 한다
    • yield return null
      • 다음 프레임에 실행된다
    • yield return new WaitForSeconds(float)
      • 매개변수로 입력한 숫자만큼 기다렸다가 실행된다
    • yield break
      • 반복문의 break 같이 코루틴을 바로 종료시킨다
  • 코루틴 문을 작성하려면 MonoBehaviour를 상속받는 객체이어야 한다
  • 코루틴에는 소유권이라는 개념이 있는데, 소유권을 가진 객체가 비활성화되거나 파괴되면 해당 객체가 소유한 모든 코루틴이 종료된다

코루틴 시작, 종료

  • 코루틴 메서드를 호출하려면 StartCoroutine 메서드를 사용해야 한다

    • 매개변수에 메서드 이름을 string으로 적거나 따옴표 없이 적으면 된다
      • string으로 전달하는 것은 호출할 메소드를 동적으로 결정할 수 있어 유연한 설계가 가능하지만 컴파일 시간에서 안전성이 부족해 성능면에서 떨어진다
      • 함수 이름을 직접 전달하는 것은 실제 메서드를 전달하므로 컴파일 안전성이 있지만 동적으로 결정할 수 없어 제한적으로 사용이 가능하다
    • 해당 메서드를 호출한 객체가 실행된 코루틴의 소유권을 가진다
    public Coroutine StartCoroutine(IEnumerator routine)
    
    // Fade라는 IEnumerator 반환 형의 메서드를 실행하려면
    StartCoroutine("Fade");
    StartCoroutine(Fade);
    // 매개변수가 필요할 때에는 쉽게
    StartCoroutine(Fade(10f));
  • 실행중인 코루틴 메서드를 종료하려면 StopCoroutine 메서드를 사용한다

    • 코루틴을 정지시키는 방법은 크게 2가지가 있다
      • 코루틴을 실행했을 때 문자열로 실행했을 때는 문자열을 매개변수로 넘겨서 똑같이 종료시킬 수 있다
      • 코루틴을 실행했을 때 함수 이름을 넘겨 실행했을 때는 코루틴 객체를 받아 그 객체를 매개변수에 넘겨준다
    • 소유권을 가진 코루틴 중 일치하는 코루틴을 종료시킨다
    public void StopCoroutine(string methodName)
    public void StopCoroutine(Coroutine routine)
    
    int main()
    {
      StartCoroutine("Fade");
      StopCoroutine("Fade");
      
      Coroutine co = StartCoroutine(Fade());
      StopCoroutine(co);
    }
    
    IEnumerator Fade();

    StopAllCoroutines

    public void StopAllCoroutines()

    이 함수는 작성된 스크립트 안에 있는 모든 코루틴을 중지하는 함수이다.

코루틴의 동작 원리

코루틴은 어떻게 하나의 메서드에서 각 작업을 여러 프레임으로 분산시키는 특징을 가지게 되었을까?
코루틴은 유니티에서 제공하는 시스템이긴 하지만, 코루틴의 모든 것을 유니티에서 만든 것은 아님. 유니티 또한 C#에서 제공하는 반복자를 활용해서 코루틴이라는 시스템을 만들었다.
C#의 반복자는 배열과 같은 컬렉션을 단계적으로 순회하기 위해 만들어진 개념이기에, 일반적으로 한 프레임에 모든 반복이 끝나는 것이 기본임
하지만 유니티에서는 한 단계가 진행될 때마다 다음 프레임에서 실행되도록 하는 방식을 채택하면서 현재의 코루틴이 탄생하게 되었다.

유니티가 어떻게 다음 프레임에서 실행되도록 만들었을까?
유니티에는 내부적으로 DelayedCallManager라는 클래스가 있는데, 특정 코루틴을 실행할 때, 해당 코루틴은 일반 메서드와 똑같이 실행되다가 첫 번째 yield return 문을 만나면 현재 실행중인 코루틴이 DelayedCallManager에 등록된다.
그러면 이후 프레임부터는 DelayedCallManager를 통해 다음 단계가 실행되기 때문에 다음 프레임부터는 코루틴이 진행되는 것임

정리

코루틴은 멀티 스레드처럼 보이는 단일 스레드이다!

코루틴은 GC가 발생하기 때문에 메모리를 많이 쓸 수 있다

유니티의 코루틴은 프레임마다 코드를 실행할 수 있게 해주는 기능이다. update와 달리 코루틴은 일시적으로 Unity에 제어권을 넘겨준 다음 실행을 재개할 수 있어서 비동기식 동작으로 인해 코루틴은 시간 종속, 애니메이션 등의 기능 구현을 위한 유용하게 쓸 수 있다.

참고

블로그1

profile
나태지옥

0개의 댓글