Unity의 코루틴이기 이전에 코루틴은 C#의 것이다.
그래서 C#에서의 코루틴을 보고서, 유니티에서의 코루틴 사용을 보겠다.
먼저 우리는 C#에서 일반 클래스를 foreach문을 사용할 수 있게끔 하는 방법을 알아보겠다.
unsing System.Collections는 IEnumerable, IEnumerator를 상속받아 구현되었다.
//
class Class : IEnumerable, IEnumerator{
// IEnumerator의 인터페이스 구현
public object Current { get; }
// IEnumerator의 인터페이스 구현
public bool MoveNext();
// IEnumerator의 인터페이스 구현
public void Reset();
// IEnumerable의 인터페이스 구현
public IEnumerator GetEnumerator();
}
각각의 인터페이스를 상속하면 구현되어야 하는 메서드들이 존재한다.
MoveNext() : 다음 요소로 위치를 이동할 수 있는지 여부로, 가능하면 true, 불가능하면 false를 리턴한다.
Reset() : 초기 상태를 정의해줘야 한다.
Current() : 현재 위치에서에 어떤 것을 리턴할지에 대한 것이다.
GetEnumerator() : MoveNext, Reset, Current를 갖고있는 IEnumrator를 리턴하여 foreach문을 만들어준다. 여기서는 같은 클래스에서 상속했으므로 this로 넘겨주면 될 것이다.
예를 보자.
using System;
using System.Collections;
namespace Study31
{
class Program
{
class AddArray : IEnumerable, IEnumerator{
int[] mArrayA;
int[] mArrayB;
private int position = -1;
public AddArray(int[] arrayA, int[] arrayB)
{
mArrayA = arrayA;
mArrayB = arrayB;
}
// IEnumerator의 인터페이스 구현
public object Current { get { return (mArrayA[position] + mArrayB[position]); } }
// IEnumerator의 인터페이스 구현
public bool MoveNext()
{
position++;
return position < (mArrayA.Length > mArrayB.Length ? mArrayB.Length : mArrayA.Length);
}
// IEnumerator의 인터페이스 구현
public void Reset()
{
position = -1;
}
// IEnumerable의 인터페이스 구현
public IEnumerator GetEnumerator()
{
return this;
}
}
static void Main(string[] args)
{
int[] arrayA = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] arrayB = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4 };
AddArray addArray = new AddArray(arrayA, arrayB);
foreach(var elemant in addArray)
{
Console.WriteLine(elemant);
}
/*
11
11
11
11
11
11
11
11
11
11
*/
}
}
}
position은 보통 -1로 잡고서 MoveNext에서 ++를해줘서 0으로 시작한다.
MoveNext의 내용은 배열 중 가장 짧은 것을 기준으로 position의 최대 값을 정의해줘서 bool로 리턴해준다.
Current는 실제 반환하는 값으로 값을 박싱해서 리턴한다.
간단히 정리하면 다음과 같을 것이다.
foreach(Object obj in IEnumerable.GetEnumerator()){
}
// ==
Object obj;
for(IEnumerator.Reset(); IEnumerator.MoveNext();){
obj = Current;
}
대충 이런 느낌으로 컴파일러가 처리해준다는 것만 알면 될 것 같다.
yield는 Inumerable과 IEnumarator를 상속받는 객체를 간단하게 구현할 수 있는 방법이다.
using System;
using System.Collections;
namespace Study32
{
class Program
{
static IEnumerable AddArray(int[] arrayA, int[] arrayB)
{
int index = 0;
while(index < (arrayA.Length > arrayB.Length? arrayB.Length : arrayA.Length))
{
yield return (arrayA[index] + arrayB[index]);
index++;
}
yield break;
}
static void Main(string[] args)
{
int[] arrayA = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] arrayB = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4 };
foreach (var elemant in AddArray(arrayA, arrayB))
{
Console.WriteLine(elemant);
}
}
}
}
위 코드는 Inumerable과 IEnumarator로 구현한 코드와 같은 결과를 나타낸다.
yield return (); 는 return Current;와 동일하고,
yield break; 는 MoveNext()가 false로 반환된 상황을 의미한다.
중요한 점은 function에서 IEnumerable을 반환해줘야 한다.
이처럼 yield는 내부적으로 컴파일러가 Inumerable과 IEnumarator를 이용해서 구현해준다.
유니티에서 코루틴을 사용하는 경우는 StartCoroutine이라는 함수를 통해서 AddArray와 비슷하지만 IEnumarator을 리턴하는 함수를 실행시킬 수 있다.
foreach처럼 var elemant부분을 한 번 갖고와서 실행시키고 넘어간다.
이는 유니티에서 큰 장점으로 작동한다. 매 update 프레임마다 elemant를 갖고와서 실행하여 함수 하나를 분할 실행할 수 있다.
이를 응용하면 가까이 오는 적들을 매 프레임마다 모두 거리를 체크하면 엄청난 부하로 이어지지만 일부씩 Coroutine을 이용해서 계산하면 성능적 이득을 챙길 수 있다.
또 코루틴의 유용한 점으로는 UnityEngine.WaitForSeconds이다.
이는 예상하기로 Time.deltaTime을 더하면서 WaitForSeconds의 인자로 넘겨준 값이 넘으면 그때서야 실행시키는 느낌인 것 같다.
즉, WaitForSeconds의 인자에 따라서 몇 초 후에 실행하는 방식을 손쉽게 구현할 수 있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
IEnumerator CoRoutineFunc()
{
Debug.Log("Start Log");
yield return new WaitForSeconds(1.5f);
Debug.Log("After 1.5 sec ...");
}
void Start()
{
StartCoroutine(CoRoutineFunc());
}
}