본문 바로가기

유니티 개발 정보/개념

상급 코루틴 개념(Advanced Coroutines)

원문은 이곳에서 보실수 있습니다.
제 능력에 한해서 최대한 쉽게 쓰려고 노력했습니다만 이해가 잘 안되는 부분이 있을 것입니다.
여러분의 잘못이 아니기에 좌절하지 마시고, 이해가 안되시는 부분만 원문을 참고하시는 것도 
좋을 것 같습니다. 본문에 나와있는 소스코드를 보면서 읽으시면 조금 더 이해가 쉬울 것입니다. 



코루틴이 어떻게 작동하는지 궁금한가?
yield 명령문의 뒤에서 도대체 무슨 일이 일어나는가?
한번 알아보자.

목적


코루틴이 화면 밖에서는 어떤 일들이 일어나는지 알고 싶다면 반드시 이 글을 읽어야 한다.
만약 당신이 FSM 튜토리얼을 따라하고 있거나, 후에 다시 재시작 가능한 개입 중단 가능한(interruptable) 코루틴을 어떻게 만들 수 있는지에 대한 세부사항을 알고 싶다면  이 튜토리얼은 매우 유용할 것이다.

이 글은 상급 주제이며, 단지 코루틴의 사용만을 원한다면 이것을 알 필요는 없다.
만약 시스템과 프레임워크를 빌딩하고 있다면, 혹은 궁금하다면 읽어봐라!


코루틴이 호출될 때, 이들이 강력한 시퀀스를 만들기 위해 어떻게 사용되는지 알고 싶다면, 반드시 코루틴 글을 읽어 봐라. 여기서도 마찬가지로 많은 유용한 정보들을 얻을 수 있다. 이 글은 괴짜스럽게 많이 상세하다!

코루틴

코루틴은 기본적으로 나중에 재시작될 수 있는 개입 중단 가능한 루틴이다. 우리는 yield문을 입력함으로써 중단되기를 원할 때나, yield 문으로부터 파생된 객체 혹은 다른 코루틴의 참조를 반환함으로써 재시작을 원할 때, 이를 시스템에 알려줄 수 있다. 

yield가 C# language의 한 부분이라는 것은 명백하다 - 그것은 무엇을 위해 존재 하는가? 나는 당신에게 원래 yield는 몬스터 게임 객체가 파괴되기 전에 2초동안 기다리게 하기 위해서 디자인 된 것이 아니라고 말할 수 있다.

Emumerators를 기반으로한 코드

수개월 전에, CLR의 디자이너들은 코드에 의해서 만들어진 것들을 열거할 수 있다면 그것은 정말 좋을 것 같다고 생각했다. 그래서 이것을 프레임워크로 내장시켰다. 그들은 만약 당신이 피보나치 수열을 계산하고 싶을 때, foreach를 사용할 수 있다면 좋을 것 같다고 생각했다. 또한, 열거가 유용한 곳에 이 기능은 매우 유용하며, 이것을 통해 배열이나 리스트를 만드는 것은 실제 필요하지 않고, 실용적이지 않다.
(또는 직접 IEmumerator 인터페이스 구현을 것이 성가실 것이다.)

예를들어, 당신은 파일안의 모든 라인마다 foreach를 실행하기를 원한다고 해보자 - yield는 원래 기본적으로 그것이 전부이다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
using System.IO;
 
...
 
void ProcessFile(string filename)
{
      //Loop through all of the lines in a file
      foreach(var line in Lines(filename))
      {
             Debug.Log(line);
      }
}
 
IEnumerator Lines(string filename)
{
    var reader = new StreamReader(filename);
    while(!reader.EndOfStream)
    {
         yield return reader.ReadLine();
    }
    reader.Close();
}


IEnumerator & 약간의 마술

기본적으로 yield는 enumerator 기반으로된 코드의 현재 값을 반환한다. 만약 IEnumberator 인터페이스를 본다면, 그것은 2가지를 한다는 것을 알 수 있다.

  • MoveNext()는 만약 더 많은 항목이 존재한다면 true, 그렇지않으면 false를 반환한다.

  • Current는 현재 단계에서 enumerator의 현재 값을 반환한다. 당신은 반드시 Current를 호출하기전에 MoveNext()를 호출해야한다.
루틴이 yield포함할 때 컴파일러는 약간의 마법을 시전한다. 코루틴은 당신의 클래스에서 평범한 함수처럼 보인다. 그렇지 않는가? 하지만 그것은 전혀 그렇지 않다. 비밀리에, 컴파일러는 IEnumerator를 구현한 dummy 클래스를 생성했다. 이 클래스는 자신의 필드를 가진다. 당신의 함수에서 정의한 모든 지역 변수에 대한 필드와  마지막으로 실행한 명령문을 참조하는 또 다른 필드를 가진다.

이 마법같은 클래스의 IEnumerator.MoveNext()가 호출되었을 때, 
코드는 실행되고,  이 클래스에 대한 this 포인터를 주는 대신에, 컴파일러는 자동적으로 당신의 클래스 인스턴스에 대한 this 포인터를 준다. 또한 함수에서 어떻게 변수를 참조하는지를 알 수 있다. 다시 말해서, 당신은 함수로 변장한, 상태 머신을 만든 것이다.
(역자 주 : 위에 말이 이해가 잘 안될 수 있을 겁니다. 저도 잘 이해가 되지 않아서 검색을 하던 도중, 내부에서 일어나는 동적을 좀 더 자세하게 설명해 놓은 글을 찾았습니다. 이해가 안되시거나, 좀 더 자세한 내용을 알고 싶으신 분들은 이 글을 읽어 보시기 바랍니다.)


코루틴의 Enumerators

그래서 유니티는 당신의 코루틴 함수에서 MoveNext를 호출하고, IEmumerator 인터페이스의 Current 속성을 조사하여, yield한 것을 알아낼 것이다. 그다음, 값을 기반으로하는 기다리고 있는 행동(behavior)을수행할 것이다. - 만약 그것이 WaitForSeconds(2)라면, 확인하고 시간이 지날때까지, 다시 MoveNext는 호출하지 않을 것이다.

Hijack!

자, 이제 우리는 코루틴을 이해했다. 우리가 해야하는 것은 IEnumerator 인터페이스를 얻고, 그것을  직접 호출하는 것이다. 우리는 IEnumerators를 외부에서 바꿀 수 있을 것이다. 그리고 효율적으로 일시정지, 재개 혹은 상태를 적당하게 바꾸는 코루틴을  얻을 수 있을 것이다.

여기에는 시간에 따라 양이 증가하는 변수와 감소하는 변수 그리고 다른 타입의 yield를 사용하는 하나의 코루틴을 가지고 있는 예제가 있다. GUI 버튼을 누르면, 바뀐 함수를 멈추지 않고, 그들을 바꿀수 있다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Collections;
 
[RequireComponent(typeof(GUIText))]
public class Hijack : MonoBehaviour {
 
    //숫자가 증가하는 코루틴을 가진다.
    IEnumerator _countUp;
    //숫자가 감소하는 코루틴을 가진다.
    IEnumerator _countDown;
    //현재 우리가 납치하고 있는 코루틴을 가지고 있다.
    IEnumerator _current;
 
    //현재 실행되고 있는 코루틴에 의해 값이 변경된다.
    int value = 0;
 
    void Start()
    {
        //증가 코루틴을 만든다.
        _countUp = CountUp();
        //감소 코루틴을 만든다.
        _countDown = CountDown();
        //현재 납치하고 있는 코루틴을 실행한다.
        StartCoroutine(DoHijack());
    }
 
    void Update()
    {
        //화면에 현재의 value값을 보여준다.
        guiText.text = value.ToString();
    }
 
    void OnGUI()
    {
        //함수를 교체한다.
        if(GUILayout.Button("Switch functions"))
        {
            if(_current == _countUp)
                _current = _countDown;
            else
                _current = _countUp;
        }
    }
 
    IEnumerator DoHijack()
    {
        while(true)
        {
            //현재 코루틴(_current)을 가지고 있는지 확인하고, 가지고 있다면
            //MoveNext를 실행하여, 다음 값이 있는지 확인한다.
            if(_current != null && _current.MoveNext())
            {
               //어떤 코루틴이 yield되었던 간에, 우리는 같은 값을 yield한다.
               yield return _current.Current;
            }
            else
                //그렇지 않으면, 다음 프레임까지 기다린다.
               yield return null;
        }
    }
 
    IEnumerator CountUp()
    {
      //지역 변수 increment를 가지고 있기때문에,
      //코루틴은 얼마나 실행이 되었는지에 따라
      //독립적으로 더 빨라질 수 있다.
        float increment = 0;
        while(true)
        {
            //'Q'키를 눌렀을 때 종료 된다.
            if(Input.GetKey(KeyCode.Q))
                break;
            increment+=Time.deltaTime;
            value += Mathf.RoundToInt(increment);
            yield return null;
        }
    }
 
    IEnumerator CountDown()
    {
        float increment = 0f;
        while(true)
        {
            if(Input.GetKey(KeyCode.Q))
                break;
            increment+=Time.deltaTime;
            value -= Mathf.RoundToInt(increment);
            //이 코루틴은 yield 명령어를 반환한다.
            yield return new WaitForSeconds(0.1f);
        }
    }
 
}
우리는 기본적으로 유니티가 현재 작동 중인 루틴을 위임하는 DoHijack 코루틴을, 실행시키는 hijack을 만들었다. 루틴을 바꾸기 위해 GUI버튼을 클릭했을 때, 마지막에 남아있는 increment 값이 얼마인지를 주목해라. 코루틴이 재활성화 되었을 때, 코루틴은 재시작하지 않으며, 단지 계속해서 실행된다.

hijacking 코루틴을 통해서 할 수 있는 많은 레벨 시스템이 있다. - FSM 튜토리얼은 분명히 그들 중 일부를 다룰 것이다.

Project

샘플 프로젝트는 여기서 다운로드 가능하다.  here.