본문 바로가기

유니티 개발 정보/개념

코루틴(Coroutine)++


목적


만약 코루틴에 대해서 확실하게 모르거나, 원하는 작업을 하고자 할 때 문제가 발생했다면 당신은 반드시 이 튜토리얼을 읽어야 한다.


코루틴은 다음과 같은 특성을 가진다 :

  • 특정 작업을 단계적으로 발생하게 한다.

  • 시간이 흐름에 따라 발생하는 루틴을 작성할 수 있다.

  • 다른 연산이 완료될때까지 기다리는 루틴을 작성할 수 있다.
예를 들어, 컷 씬 시퀀스를 조직하거나 또는 단순히 적이 죽는 애니메이션을 기다린 다음, 재생성을 할 수도 있다.

코루틴은 유니티의 매우 강력한 부분이지만, 종종 초보자들은 코루틴을 이해하는데 많은 어려움을 겪는다. 이 튜토리얼은 당신이 강력하고 융통성있는 코루틴을 파악하고, 그들이 어떻게 작동하는지 이해하는데 도움을 줄 것이다.


만약 단지 몇 초 동안 특정 연산을 지연시키기를 원한다면, Invoke를 사용하는 것을 고려해봐라. 물론 코루틴을 사용할 수는 있지만, 초보자들에게는 Invoke가 더 쉽다.

쓰레드와 코루틴


우선, 이들에 대해서 명확하게 하자, 코루틴은 쓰레드가 아니다. 코루틴은 비동기가 아니다
(not asynchronous).
(역자 주 : 비동기가 아니다라는 말은, 쉽게 말해서 동시에 발생하지 않는다는 뜻이다.)
쓰레드의 비동기와 동기의 개념이 잘 이해가 안된다면

쓰레드는 프로그램에서 다른 쓰레드와 함께 비동기로 실행된다. 멀티프로세서 기계에서, 쓰레드는 실제 다른 쓰레드와 함께 동시에 코드가 실행될 수 있다. 여러 쓰레드를 사용하는 멀티쓰레드 프로그래밍은 코드를 이해하기 복잡하게 만드는데, 왜냐하면 하나의 쓰레드는, 다른 쓰레드가 특정 부분을 읽고 있는 동시에, 그것을 변경할 수 있기 때문이다.
이 때문에, 공유 메모리영역을 만들지 않거나, 공유된 자원을 읽거나 변경시켜야되는 경우라면, 공유된 자원으로부터 다른 쓰레드를 잠궈버림으로써, 이런 상황이 발생하지 않도록 해야할 것이다.

그래서 코루틴이 무엇인가?

앞에서 분명하게 코루틴은 쓰레드가 아니라는 것을 확실하게 했다. 이는 한번에 오직 하나의 코루틴이 실행되고, 게임의 메인 쓰레드에서 코루틴이 실행된다는 것을 의미한다. 사실, 한때 게임의 핵심 코드로 실행되는 것들 중 하나에 불과하다.

코루틴을 프로그래밍할 때, 절대 동기화와 특정 값을 잠금하는 것에 대해서 걱정할 필요가 없다. 당신의 코드에서 yield를 실행하기 전까지 완전한 통제를 할 수 있기 때문이다.
그래서 여기 코루틴에 대한 정의가 나와있다:

코루틴은 부분적으로, 그리고 특정한 상황이 맞아 떨어졌을 때 실행되는 함수로써,
그 작업이 완료가 되기전까지, 미래의 어느시점에 재개될 수 있다.

유니티는 코루틴을 하나 혹은 여러개를 가지는 모든 객체에 대해서 게임의 모든 프레임마다 코루틴을 처리한다.
이 처리는 대부분의 yield 문에 대해서 Update가 끝나고, LateUpdate가 시작하기전에발생하는데, 그러나 여기 특별한 경우가 있다:

코루틴이 활성화되었을 때, 바로 다음 yield 문까지 실행될 것이고, 그것은 다시 시작될 때까지 일시정지할 것이다. 
위에 다이어그램을 통해서, yield한 것을 기반으로 어디에서 재시작되는지 알 수 있을 것이다.


밑에 간단한 코루틴을 보자:
1
2
3
4
5
6
7
8
IEnumerator TestCoroutine()
{
      while(true)
      {
           Debug.Log(Time.time);
           yield return null;
      }
}

이 코루틴은 영원히 작동하는 루프를 가지고 있다. 이 코루틴은 현재 시간으로 로그를 남기고, 그 다음 yield한다. 다시 재개될 때, 다시 루프를 돌아서 로그를 남기고, 한번 더 yield될 것이다.

루프안에 있는 코드는 정확하게 Update 함수와 같다. 단지 Update 루틴이 실행된 후에(만약 Update문이 있다면), 모든 프레임마다 한번 실행이 되는 것이다.


StartCoroutine(TestCoroutine())를 호출할 때, 코드는 첫번째 yield를 만날때까지 즉시 실행되며,
유니티 프로세스가 이 객체에 대해서 코루틴을 처리할 때, 다시 재개된다.


Start, Update, OnCollisionEnter함수에서처럼 게임 객체의 처리 앞부분에 코루틴을 시작한다면, 코루틴은 즉시 첫번째 yield까지 실행될 것이고, 만약 yield return null을 사용했다면 같은 프레임 동안 다시 재개가 될 것이다. 만약 당신이 이것을 이해하지 못한다면 때로 이상한 효과를 초래할 수도 있다.

Start와 OnColiisionEnter를 코루틴으로 만드는 것은 문제가 되지 않는다.

한계를 넘어

자 이제 한가지 더 - 우리의 테스트 코루틴에서의 명백한 무한 루프는 무한하지 않다는 것이 판명되었다. 

만약 게임 객체에 대해서 코루틴을 중단하는 함수를 호출하거나, 객체가 파괴가 된다면, 이 무한 루프는 절대 다시 실행이 되지 않을 것이다. 또한 스크립트가 직접적으로 또는 SetActiveResursively(false)를 통해서 비활성화(disable)되면, 코루틴은 멈추게 될 것이다.

I Yield Sir

나는 마지막 섹션에 문장을 하나 만들었는데, 당신은 아마 의문이 들 것이다. 


하나 혹은 여러개의 코루틴을 가지는 게임내 모든 객체에 대해서 유니티는 매프레임마다 코루틴을 처리한다.
당신은 아마, 만약 yield return new WaitForSeconds(1) 같은 문장을 사용한다면, 그것은 1초동안 어떤 것도 처리하지 않기때문에  "오, 그렇지 않다" 라고 생각할지도 모른다. 

실제로 유니티는 매 프레임마다 올바른 시간이 경과되었는지 검사를 하면서, 코루틴을 처리한다.
- 이것은 당신의 코드에서 처리하지 않는다. 그러나 당신의 코드를 둘러 싸고 있는 랩퍼인 코루틴을 처리한다.

몇가지 값들을 yield함으로써 우리의 코드를 효과적으로 멈출수 있다는 것을 알았다. 여기에는 우리가 yield할 수 있는 것들이다.

  • null - Update구문의 수행이 완료될 때까지 대기한다.

  • WaitForEndOfFrame - 현재 프레임의 렌더링 작업이 끝날 때까지 대기한다.

  • WaitForFixedUpdate - FixedUpdate구문의 수행이 완료될 때까지 대기한다.

  • WaitForSeconds - 지정한 초만큼 대기한다.

  • WWW - 웹에서 데이터의 전송이 완료될 때까지 대기한다.
    (WaitForSeconds or null처럼 재시작한다.)

  • Another coroutine - 새로운 코루틴은 yielder가 재시작되기 전에 완료 될 것이다.


또한 yield break; 명령어를 실행할 수 있다. 이는 즉시 코루틴을 멈춘다.


WaitForEndOfFrame때문에,모든 카메라가 렌더링과 GUI의 출력이 완료되었을 때,  코루틴은 render texture로부터 정보를 얻는데 사용될 수 있다.
만약 Time.timeScale이 0으로 설정되어 있다면, yield return new WaitForSeconds(x)를 사용하는 것은 절대 재시작 되지 않는다.
물론, 이 모든 것들에 대해 가장 좋은 것은 특정 기간 동안 실행되는 코드를 작성하거나, 혹은 약간의 외부 이벤트가 일어나기를 기다리는 코드를 작성하고, 그것들을 당신의 코드를 훨씬 더 읽기 쉽게 만드는 하나의 함수 안에서 관리하는 것이다.(만약 여러 개의 함수를 작성해야 했거나, 상태를 확인하기 위해서 많은 코드를 작성해야 했다면)

요약

  1. 코루틴은 시간에 따라 혹은 외부의 처리가 완료되었을 때, 연산이 일어나는 순서를 정하는 정말 멋진 방법이다.

  2. 코루틴은 쓰레드가 아니다. 그리고 비동기가 아니다(동시에 일어나지 않는다.)

  3. 코루틴이 실행되고 있을 때, 다른 어떤 것들도 실행되지 않는다.

  4. 코루틴은 yield 문의 조건을 만족시켰을 때, 재개될 것이다.

  5. 코루틴은 스크립트가 비활성화(disabled)되거나, 객체가 파괴되었을 때, 비활성화 된다.

  6. yield return new WaitForSeconds는 Time.timeScale에 의해 영향을 받는다.

코루틴의 활용


바라건대, 우리는 코루틴이 무엇인지 그리고 그들이 어떻게 작동하는지 알아봤다. 우리의 상급 튜토리얼은 그들 뒤에 있는 기술에 대해서 살펴볼 것이다.

어떤 일을 하는 코루틴을 사용해보자. 몇몇 간단한 헬퍼 함수는 실제로 코루틴을 사용해서 쉽게 컷 시퀀스를 만드는 것을 도와준다.

우리는 물체를 목표 위치와, 회전값으로 움직이게하는 코루틴을 작성할 수 있다. 또 우리는 애니메이션이 일정 비율에 도달 했을 때까지 기다리는 코루틴을 작성할 수도 있다. 이 두가지 도구를 사용하여, 우리는 쉽게 하나의 함수로 전체 컷 시퀀스를 읽기 쉽게 스크립트 할 수 있을 것이다. 


코루틴에서 어떤 것을 움직일 때를 기다리는 것은, 동시에 객체의 위치를 조작하는 다른 코루틴 또는 업데이트 함수간의 충돌이 없다는 것을 보장한다.

당신은 오직 한번에 하나의 객체에 영향을 미치는 코루틴을 만들어야하고, 객체를 움직이게 하는 Update는 비활성화 시켜야 한다.

여기 애니메이션이 특정 부분 완료되기까지를 기다리는 코루틴의 예가 있다:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
//Wait for an animation to be a certain amount complete
IEnumerator WaitForAnimation(string name, float ratio, bool play)
{
    //Get the animation state for the named animation
    var anim = animation[name];
    //Play the animation
    if(play) animation.Play(name);
 
    //Loop until the normalized time reports a value
    //greater than our ratio.  This method of waiting for
    //an animation accounts for the speed fluctuating as the
    //animation is played.
    while(anim.normalizedTime + float.Epsilon + Time.deltaTime < ratio)
        yield return new WaitForEndOfFrame();
 
}

당신은 애니메이션을 기다리는 코루틴을 다음과 같이 작성할 수 있다.
01
02
03
04
05
06
07
08
09
10
11
IEnumerator Die()
{
       //Wait for the die animation to be 50% complete
       yield return StartCoroutine(WaitForAnimation("die",0.5f, true));
       //Drop the enemies on dying pickup
       DropPickupItem();
       //Wait for the animation to complete
       yield return StartCoroutine(WaitForAnimation("die",1f, false));
       Destroy(gameObject);
 
}

비디오는 당신에게 더 많은 것들을 보여준다. 예제 프로젝트는 여기서 얻을 수 있다.

비디오


만약 강력한 코루틴 도구를 어떻게 만들고, easing 함수를 적용하고, 코루틴을 다음 단계로 개발하는 방법을 알고싶다면, 반드시 이 비디오를 봐야한다.
이 비디오는 꽤 길지만, 그것은 정말 멋지다. 코루틴을 사용해서 전체의 컷 시퀀스를 만드는 방법을 배워라. 그리고 어떻게 easing 함수에 yield 문을 적용하는지를 봐라.

비디오의 끝 무렵에, 우리는 어떻게 고급 코루틴 도구를 만드는지와 멋진 효과를 만들기위해 그것들을 사용하는 방법에 대해 알게 될 것이다. 다운로드한 프로젝트는 비디오에서 보여주었던 모든 부분을 담고 있다.