Programming 

코루틴(Coroutine) 다루기

 

작성 기준 버전 :: 2019.2

 

[이 포스트는 유튜브 영상을 통해서도 시청하실 수 있습니다]

 

Update 함수는 게임 오브젝트가 활성화된 상태에서 매 프레임 호출되어 수행된다. 그래서 유니티 엔진으로 게임을 만들 때는 대부분의 게임 동작을 Update 함수에서 작동하도록 구현한다.

 

그런데 Update 함수는 멈추지 않고 계속해서 동작하는 함수이기 때문에 여기서 일시적으로 돌아가는 서브 동작을 구현하는 것과 어떤 다른 동작이 처리되는 것을 기다리는 기능을 구현하기는 매우 까다롭다.

 

그리고 Update 함수에서 해당 기능을 구현하기 어렵지 않다고 하더라도, 잠시 돌아가는 기능을 Update 함수에 모두 구현하는 것은 비대한 몸집의 Update 함수를 만들어 내서 나중에 게임을 유지보수하는 것이 매우 어려워지는 결과를 낳게 된다.

 

이렇게 한 컴포넌트 내에서 Update 함수와 따로 일시적으로 돌아가는 서브 동작을 구현하거나, 어떤 다른 작업이 처리되는 것을 기다리는 기능을 구현하는데 쓰이는 것이 바로 코루틴이다.

 

Update로 구현한 공격 딜레이

 

코루틴이 필요할 법한 간단한 예시를 들기 위해서 스페이스 키를 누르면 캐릭터가 공격했다고 가정하고 딜레이를 줘서 그 시간 동안에는 다시 공격을 하지 못하게 만드는 기능을 만들어 보자.

 

public class Attacker : MonoBehaviour

{

    public bool isDelay;

    public float delayTime = 2f;

 

    float timer = 0f;

    void Update()

    {

        if (Input.GetKeyDown(KeyCode.Space))

        {

            if (!isDelay)

            {

                isDelay = true;

                Debug.Log("Attack");

            }

            else

            {

                Debug.Log("Delay");

            }

        }

 

        // 업데이트로 구현한 공격 딜레이

        if (isDelay)

        {

            timer += Time.deltaTime;

            if (timer >= delayTime)

            {

                timer = 0f;

                isDelay = false;

            }

        }

    }

}

 

위와 같이 코드를 작성하고 플레이 해보면, 처음 스페이스 키를 누르면 "Attack" 로그가 나오지만, 딜레이가 지나기 전에 다시 누르면 "Delay" 로그가 표시된다.

 

그리고 로그가 지난 이후에 스페이스 키를 눌러야 "Attack" 로그가 출력된다.

 

간단한 기능이라 구현이 그리 어렵지는 않았지만, 앞에서 이야기한 것과 같이 게임 기능이 계속해서 추가될 때마다 "공격 딜레이가 발생했을 때만" 동작하는 코드 같은 일시적 동작 코드가 Update 함수에 계속해서 늘어나면, Update 함수가 비대화되고 유지보수가 어려워진다.

 

코루틴으로 구현한 공격 딜레이

 

똑같은 기능을 이번에는 코루틴으로 구현해보자.

 

// 코루틴으로 구현한 공격 딜레이

IEnumerator CountAttackDelay()

{

    yield return new WaitForSeconds(delayTime);

    isDelay = false;

}

 

먼저 코루틴을 사용하기 위해서는 코루틴 함수를 만들어야 한다.

 

코루틴 함수를 만드는 방법은 간단하게 반환형만 IEnumerator로 만들어주면 된다.

 

그리고 yield return이란 코드를 작성해주면 된다. 이것은 코루틴에서 동작하는 제어권을 유니티에 다시 돌려준다는 뜻이다. 이 yield return 지점에 도착하면 코루틴은 반환 타입으로 정의한 만큼 코드 동작을 중지하고 제어권을 유니티에 돌려준다. 그리고 반환 타입의 조건이 충족되면 이 다음 줄부터 다시 코루틴이 동작한다.

 

코루틴이 제어권을 얼마나 양보할 지 정하는 반환 타입에는 여러 가지가 있다.

 

// 한 프레임 기다림

yield return null;

// 게임 시간으로 1초 기다림(time scale에 영향받음)

yield return new WaitForSeconds(1f);

// 실제 시간으로 1초 기다림(time scale에 영향받지 않음)

yield return new WaitForSecondsRealtime(1f);

// 다음 FixedUpdate 끝날 때까지 기다림

yield return new WaitForFixedUpdate();

// 다음 프레임의 Update와 모든 렌더링이 끝날 때까지 기다림

yield return new WaitForEndOfFrame();

 

코루틴을 모두 작성하고 나면 Update 함수를 아래와 같이 수정하면 된다.

 

void Update()

{

    if (Input.GetKeyDown(KeyCode.Space))

    {

        if (!isDelay)

        {

            isDelay = true;

            Debug.Log("Attack");

            StartCoroutine(CountAttackDelay());

        }

        else

        {

            Debug.Log("Delay");

        }

    }

 

    // 업데이트로 구현한 공격 딜레이

    //if (isDelay)

    //{

    //    timer += Time.deltaTime;

    //    if (timer >= delayTime)

    //    {

    //        timer = 0f;

    //        isDelay = false;

    //    }

    //}

}

 

코루틴 함수는 실행할 때 일반 함수처럼 호출하는 것이 아니라, StartCoroutine 함수를 이용해서 호출해야 한다.

 

코루틴으로 공격 딜레이를 구현하면 Update 함수에서 전부 구현하는 것보다 훨씬 쉽고 간단하게 똑같은 기능을 구현할 수 있다.

 

코루틴과 관련된 포스트

 

코루틴과 관련하여 추가로 확인할 만한 포스트는 아래와 같다.

 

[코루틴 내부에서 무한 루프를 사용할 때 주의점]

 

[코루틴의 호출 시점에 대한 주의점]

 

[커스텀으로 yield return 조건 만들기]

 

반응형
  1. psh 2021.11.15 02:40

    베르님 감사합니당!

Programming

-

커스텀 IEnumerator 클래스 만들기(Custon yield instruction)


코루틴(Coroutine)은 유니티에서 아주 많이 사용되는 기능들 중에 하나다. 코루틴의 대표적인 사용법은 게임의 메인 흐름과 다르게 후방에서 무언가가 돌아가야 하는 경우와 무언가가 완료되기를 기다리는 것이다.


이번 섹션에서 이야기할 방법은 주로 후방에서 돌아가는 경우보다는 무언가가 완료되기를 기다리는 경우에 더욱 유용한 것이다. 일반적으로 가장 많이 기다리게 되는 것은 일정한 시간이 지나기를 기다리는 것이다. 그 경우 사용되는 것이 바로 WaitForSeconds 클래스이다. 유니티 개발을 해본 개발자라면 다음과 같은 종류의 코드를 많이 보았을 것이다.


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CoroutineExample : MonoBehaviour
{
    private void Start()
    {
        StartCoroutine(WaitForSecondsCoroutine());
    }

    IEnumerator WaitForSecondsCoroutine()
    {
        yield return new WaitForSeconds(5.0f);
        Debug.Log("5초가 지남!");
    }
}


5초가 지난 이후에 로그 하나의 띄우는 코드이다. 위와 비슷한 방식으로 WWW 클래스를 이용해서 웹 서버에서 무언가가 전송되기를 기다린다던지, 위의 코드처럼 WaitForSeconds 클래스를 이용해서 얼마간의 시간이 지나기를 기다린다던가 할 수 있고, 이 외에도 여러가지의 것을 기다렸다가 이후의 작업을 진행할 수 있다.


유니티에서는 이렇게 원하는 동작을 기다릴 수 있는 IEnumerator 클래스를 제공한다. 하지만 유니티에서 모든 종류의 기능을 제공하는 것은 아니며, 개발자가 새로운 동작을 기다리는 기능을 만들기를 원할 수도 있다.


만약 개발자가 코루틴 내에서 Space 키를 입력하기를 기다려야 된다고 가정해보자. 하지만 기본적으로 키 입력을 기다리는 Custom yield Instruction은 존재하지 않는다. 그렇기 때문에 개발자는 이러한 기능을 직접 구현해야하는데 그 방법은 2가지가 있다.



1. CustomYieldInstruction 상속받기

첫 번째 방법은 CustomYieldInstruction을 상속받는 것이다. 다음은 그 예제이다.


using System.Collections;
using UnityEngine;

public class CustomIEnumeratorType1 : CustomYieldInstruction
{
    public override bool keepWaiting
    {
        get
        {
            return !Input.GetKeyDown(KeyCode.Space);
        }
    }
}


간단하게 새로운 클래스를 구현하고 CustomYieldInstruction을 상속 받은 뒤에 keepWaiting을 오버로딩해주고 getter에서 원하는 동작을 구현하면 된다. 이 예제에서는 Space 키의 입력을 기다릴 것이기 때문에 Input.GetKeyDown(KeyCode.Space)로 입력했다. Yield instruction의 동작은 "계속 기다릴 것인가?"를 묻고 있기 때문에 계속 기다려야 한다면 true를 반환해야하고 더 이상 기다릴 필요가 없다면 false를 반환해야 한다.



2. IEnumerator 상속받기

using System.Collections;
using UnityEngine;

public class CustomIEnumeratorType2 : IEnumerator
{
    public object Current
    {
        get
        {
            Debug.Log("대기하는 동안에 처리할 동작");
            return null;
        }
    }

    public bool MoveNext()
    {
        return !Input.GetKeyDown(KeyCode.Space);
    }

    public void Reset()
    {
    }
}


두 번째 방법은 새로운 클래스를 구현한 뒤에 IEnumerator를 상속받는 것이다. 이 방법은 CustomYieldInstruction을 상속받는 방법보다는 약간 복잡하지만,

좀 더 많은 기능을 사용할 수 있게 된다. Current를 통해서 코루틴이 대기하는 동안에도 다른 동작을 처리할 수 있고 MoveNext를 통해서 CustomYieldInstruction의 keepWaiting처럼 더 기다려야 하는지에 대한 여부를 대기중인 코루틴에 전달할 수 있다. reset 메서드의 경우는 COM의 상호운용성을 위해 제공되며 반드시 구현해야 하지만 반드시 필요하지는 않다.



위의 두 가지 방법을 이용하면 코루틴을 수행하는 도중에 원하는 동작을 기다릴 수 있게 된다. 이 기능의 유용한 한 가지 예를 들자면, 동시에 턴이 진행되고 두 사람이 둘 다 완료 버튼을 눌러야 다음 턴으로 진행되는 게임이 있다면, 턴을 넘기는 코루틴을 커스텀으로 제작된 yield 객체를 이용해서 정지시켜두고 두 사람 모두에게서 신호를 받아야 턴 코루틴이 진행되는 방식으로 만들 수 있다.

반응형

+ Recent posts