코루틴 (Coroutine)
코루틴은 각 프레임 시퀸스에 대해서 명령을 처리해야 하는 경우에 Update 메서드 대신 사용하는 작업입니다.
void Fade() {
SpriteRenderer spriteRenderer;
for(float a = 1f; a >= 0f; a -= 0.05f) {
Color color = spriteRenderer.color;
color.a = a;
spriteRenderer.color = color;
}
}
일반적으로 업데이트를 처리하는 메서드를 호출하면 한 번에 모든 코드가 실행되어 spriteRenderer.color의 알파 값은 한 번에 0이 됩니다. 작성자의 의도는 시간에 따른 알파 값의 자연스러운 변화이기 때문에 이러한 경우에 Coroutine을 이용합니다.
IEnumerator Fade() {
SpriteRenderer spriteRenderer;
for(float a = 1f; a >= 0f; a -= 0.05f) {
Color color = spriteRenderer.color;
color.a = a;
spriteRenderer.color = color;
yield return null;
}
}
다음과 같이 IEnumerator를 반환하는 메서드를 작성하여
StartCoroutine(Fade());
다음과 같이 호출하면 프레임마다 알파 값이 1에서 0까지 자연스럽게 갱신하는 작업을 수행합니다.
또 코루틴은 일반적인 코드 흐름과 다른 흐름을 만들어 내기도 합니다.
void PlayFadeAnimations()
{
SpriteRenderer sp1;
SpriteRenderer sp2;
SpriteRenderer sp3;
StartCoroutine(Fade(sp1));
StartCoroutine(Fade(sp2));
StartCoroutine(Fade(sp3));
}
IEnumerator Fade(SpriteRenderer spriteRenderer) {
for(float a = 1f; a >= 0f; a -= 0.05f) {
Color color = spriteRenderer.color;
color.a = a;
spriteRenderer.color = color;
yield return null;
}
}
다음 코드의 경우에는 Fade 메서드에서 각 프레임 시퀸스마다 알파 값을 변화시키기 때문에 단일 코드 흐름에서는 여러 Fade 메서드를 동시에 실행할 수 없습니다. StartCoroutine을 사용하면 새로운 코드 흐름을 생성하여 sp1, sp2, sp3에 대한 Fade 메서드가 동시에 실행하게 할 수 있습니다.
Coroutine
코루틴은 객체이므로 캐싱할 수 있습니다.
Coroutine coroutine;
또한 코루틴이 중복 실행되는 것을 막기위해서 다음과 같이 처리할 수 있습니다.
if(coroutine != null) {
StopCoroutine(coroutine);
}
coroutine = StartCoroutine(IEnumerator_method);
다음 코드는 coroutine 실행중에 새로운 Coroutine 실행 요청 시에 기존 coroutine을 중단하고 새로운 Coroutine을 실행합니다.
StartCoroutine(c)
코루틴을 실행합니다.
StartCoroutine(코루틴)
StartCoroutine(IEnumerator_method)
StartCoroutine("IEnumerator_method") // 권장하지 않는 방식입니다.
StopCoroutine(c)
코루틴을 종료합니다.
StopCoroutine(코루틴)
StopCoroutine(IEnumerator_method)
StopCoroutine("IEnumerator_method") // 권장하지 않는 방식입니다.
yield 키워드 활용
IEnumerator 메서드에서 yield 키워드는 코드 진행을 지연시킬 때 사용합니다. 일반적으로 yield return 문을 사용할 때마다 한 프레임이 진행됩니다.
yield break
IEnumerator 메서드를 빠져나갑니다.
(일반 메서드의 return과 같습니다.)
yield break;
IEnumerator Counting() {
for(int n = 1; n <= 100; n++) {
if(n <= 10) {
Debug.Log(n);
} else {
yield break;
}
}
}
다음과 같은 Coroutine을 실행하면 1~10까지 출력하고 n이 11이 되면 yield break에 의해서 메서를 빠져나갑니다.
yield return null
한 프레임 동안 지연시킵니다.
yield return null;
IEnumerator Fade() {
SpriteRenderer spriteRenderer;
for(float a = 1f; a >= 0f; a -= 0.05f) {
Color color = spriteRenderer.color;
color.a = a;
spriteRenderer.color = color;
yield return null;
}
}
다음과 같은 Coroutine을 실행하면 yield return null에서 한 프레임 지연되어 알파 값이 1에서 0까지 자연스럽게 감소하는 것처럼 보이게 됩니다.
yield return new WaitForSeconds()
한 프레임을 소모하고 지정한 시간만큼 대기시킵니다.
(WaitForSeconds는 객체이기 때문에 반복되어 사용하면 상당한 메모리를 차지합니다. 따라서 캐싱하여 사용하는 것이 좋습니다.)
yield return new WaitForSeconds(secondValue);
캐싱하여 사용할 때에는 다음과 같이 사용합니다.
WaitForSeconds wfs = new WaitForSeconds(1f);
yield return wfs;
IEnumerator Counting() {
for(int n = 1; n <= 100; n++) {
Debug.Log(n);
yield return new WaitForSeconds(1f);
}
}
다음 Counting 메서드에 대한 코루틴을 실행하면 yield return new WaifForSeconds(1f)에서 1초간 대기하여 100초간 카운트하는 작업을 구현할 수 있습니다.
yield return new WaitForSecondsRealtime()
한 프레임을 소모하고 지정한 Time.timeScale에 영향을 받지 않는 시간만큼 대기시킵니다.
yield return new WaitForSecondsRealtime(secondValue);
yield return new WaitUntil()
한 프레임을 소모하고 대리자가 true를 반환할 때까지 대기합니다.
(WaitUntil 또한 객체 이므로 캐싱하여 사용하는 것이 좋습니다.)
yield return new WaitUntil(predict<bool>);
bool isContinuable = false;
bool IsContinuable()
{
return isContinuable;
}
IEnumerator Counting() {
for(int n = 1; n <= 100; n++) {
Debug.Log(n);
yield return new WaitUntil(IsContinuable);
}
}
다음 Counting 메서드에 대한 코루틴을 실행하면 yield return new WaitUntil(IsContinuable)에서 isContinuable이 true가 될 때까지 매 프레임마다 확인하고 isContinuable가 true 값을 반환할 때 카운트를 이어가는 모습을 확인할 수 있습니다.
yield return new WaitWhile()
WaitWhile은 한 프레임을 소모하고 대리자가 false를 반환할 때까지 대기합니다.
(WaitWhile 또한 객체 이므로 캐싱하여 사용하는 것이 좋습니다.)
yield return new WaitWhile(predict<bool>);
bool isContinuable = false;
bool IsNotContinuable()
{
return !isContinuable;
}
IEnumerator Counting() {
for(int n = 1; n <= 100; n++) {
Debug.Log(n);
yield return new WaitWhile(IsNotContinuable);
}
}
다음 Counting 메서드에 대한 코루틴을 실행하면 yield return new WaitUntil(IsNotContinuable)에서 isContinuable이 false가 될 때까지 매 프레임마다 확인하고 isContinuable가 false 값을 반환할 때 카운트를 이어가는 모습을 확인할 수 있습니다.
yield return StartCoroutine()
해당 코루틴이 끝나기 전까지 대기합니다.
yield return StartCoroutine(IEnumerator_method); // 실행 후 끝날 때까지 대기
yield return IEnumerator_method(); // 실행 후 끝날 때까지 대기
yield return coroutine_cached; // 끝날 때까지 대기
IEnumerator PlayFadeAnimations()
{
SpriteRenderer sp1;
SpriteRenderer sp2;
SpriteRenderer sp3;
yield return Fade(sp1);
yield return Fade(sp2);
yield return Fade(sp3);
}
IEnumerator Fade(SpriteRenderer spriteRenderer) {
for(float a = 1f; a >= 0f; a -= 0.05f) {
Color color = spriteRenderer.color;
color.a = a;
spriteRenderer.color = color;
yield return null;
}
}
다음 PlayFadeAnimations 메서드에 대한 코루틴을 실행하면 sp1, sp2, sp3에 대한 Fade 코루틴을 실행하고 이 코루틴이 끝날 때까지 대기하면서 순차적으로 작업이 진행되는 것을 확인할 수 있습니다.
유의사항
단일 코드 흐름에서 코루틴을 실행했을 때, 코루틴 실행 라인의 하위 라인보다 코루틴 코드 흐름이 더 느릴 수 있습니다.
void NormalMethod()
{
StartCoroutine(CoroutineMethod());
Debug.Log("NormalMethod End");
}
IEnumerator CoroutineMethod() {
Debug.Log("CoroutineMethod Start");
yield return null;
Debug.Log("CoroutineMethod End");
}
예를 들어 다음과 같은 코드에서 NormalMethod 메서드를 실행한다면
CoroutineMethod Start
NormalMethod End
CoroutineMethod End
StartCoroutine(CoroutineMethod()) 하위 라인인 NormalMethod의 끝을 알리는 코드가 CoroutineMethod의 끝을 알리는 코드보다 더 먼저 실행되었습니다.
일반적으로 어느 정도 예상 가능한 결과를 내놓지만 실제로 더 복잡한 코루틴을 작성할 때는 코드의 흐름 파악이 쉽지 않기 때문에 코루틴 내부에서 코드 흐름에 의존한 코드를 작성하는 것은 지양해야 합니다. (코루틴 내부에 초기화 코드 작성 등)
또한 Coroutine 내부에서 yield return 문을 사용한 수만큼 프레임을 지연시킵니다.
bool isStopped = false;
IEnumerator Timer(float sec)
{
float timer = sec;
while(sec > 0f)
{
timer -= Time.deltaTime;
yield return new WaitWhile(() => isStopped);
yield return null;
}
}
위 코드에서 yield return new WaitWhile(() => isStopped) 라인에서 한 프레임, yield return null 라인에서 한 프레임, 총 두 프레임간 대기 상태에 한 프레임의 간격 시간을 의미하는 Time.deltaTime만큼 타이머가 진행되어 타이머는 0.5배의 속도로 진행되게 됩니다.
timer += Time.deltaTime * 2;
다음과 같이 Time.deltaTime을 2배하거나 yield return 문 중 하나를 제거해야 합니다.