[Unity]Coroutine(코루틴)의 개념과 활용

4646 단어 UnityUnity

🚀 Coroutine(코루틴)과 Thread(쓰레드)

닷넷(.NET)은 멀티 쓰레드를 지원한다. 하지만 닷넷(.NET)을 사용하는 유니티는 단일 쓰레드의 원리로 동작한다. 멀티쓰레드의 코드는 쓰레드간 교착 상태 등을 신경써야 하며 버그 발생률이 높아진다.
그런데 단일 쓰레드의 원리로 동작하는 유니티에선 Coroutine 기능으로 멀티 쓰레드의 기능을 하게 해준다.(멀티 쓰레드가 된다는 것은 아니다.)
다른 쓰레드를 사용하지 않고 멀티 태스킹을 가능하게 해주는데 이는 코루틴 함수가 IEnumerator를 반환 하는 점으로부터 접근할 수 있다.

🚀 Coroutine(코루틴)과 서브루틴(Subroutine)

일반적으로 우리가 사용하는 함수는 진입하는 지점은 하나 존재하고 함수가 종료하는 시점은 코드가 모두 실행되거나, return 구문에 의해서 종료되는 지점을 설정할 수 있습니다.
이러한 함수를 우리는 서브루틴(Subroutine)이라 하는데 코루틴(Coroutine)은 이를 더 일반화 한 개념으로 진입하는 지점 마저 여러개를 설정할 수 있는 함수를 의미한다.

🚀 Coroutine의 필요와 개념

어떤 오브젝트의 alpha(투명도)가 완전히 투명해질 때 까지 서서히 계속 감소하는 장면을 구현하고 싶다고 가정할 때 이렇게 코드를 생각해 볼 수 있다.

void Fade() {
    for (float f = 1f; f >= 0; f -= 0.1f) {
        Color c = renderer.material.color;
        c.a = f;
        renderer.material.color = c;
    }
}

하지만 위의 코드에서 Fade함수로 우리가 원하는 기능을 구현할 수 없다.
Fade가 시각적으로 보이기 위해서는 alpha는 프레임이 지날수록 점점 감소해야하지만 위의 for문으로 이루어진 함수는 하나의 프레임에서만 실행된다.
따라서 중간값을 시각적으로 알 수 없으므로 오브젝트는 순간적으로 투명해진다.

여기서 우리는 시각적으로 서서히 사라지는 장면을 구현하기 위해 Update 함수를 이용하는 것을 생각해볼 수 있다.
하지만 Update는 Fade기능이 쓰이지 않을 때에도 무조건 프레임마다 호출이된다. 기능은 구현할 수 있지만 비효율적이라는 소리다.

이를 보완하기 위해 필요한 것이 코루틴이다.

코루틴(Coroutine)은 한 부분에서 실행을 중지하여 Unity에 제어권을 돌려주고, 계속할 때는 다음 프레임에서 중지한 곳부터 실행을 계속할 수 있는 기능이다.

IEnumerator Fade() {
    for (float f = 1f; f >= 0; f -= 0.1f) {
        Color c = renderer.material.color;
        c.a = f;
        renderer.material.color = c;
        yield return null;
    }
}

C#의 코루틴은 위의 코드와 같이 선언한다. 이것은 IEnumerator형식을 반환값으로 가지며 yield return 구문을 어디엔가 포함하고 있는 함수이다. 또한 yield return 행은 실행을 중지하고 다음 프레임에서 실행을 재개할 수 있는 지점이다.

이 코루틴을 실행하려면 StartCoroutine 함수를 사용한다.

void Update() {
    if (Input.GetKeyDown("f")) {
        StartCoroutine("Fade");
    }
}

🚀 IEnumerator란?

IEnumerator는 우리말로 "열거자"라고 한다. 데이터의 목록을 하나씩 넘겨줄 때 사용되는 인터페이스이다. 코루틴은 호출한 함수와 서로 상호작용하면서 진행하도록 설계되어 있다. 자신을 호출한 함수에게 데이터를 하나 넘겨주고 쉰 다음 호출한 함수에서는 데이터를 받고 나서 처리 후 다음 데이터를 받아야 할 때 쉬고 있던 코루틴이 일어나서 다시 데이터를 전달하는 것을 반복하는 구조로 동작한다는 것이다.
이를 위해서 데이터를 전달한 후 코루틴은 대기할 때 C#에서는 yield return을 사용한다.

🚀 Coroutine의 활용

게임에서 버프를 받는 상황이 있고, 그 버프를 받는 기능을 코루틴을 구현한다 생각해보자.

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

public class Test : MonoBehaviour
{
    float time;
    float reStartTime = 8f;

    bool buff;

    private void Start()
    {
     	buff = false;
        time = 0; 
    }

    private void Update()
    {
       if ( Time.time > time + reStartTime)
        {
            StartCoroutine(BuffStartCoroutine());
            time += reStartTime;
        }
    }

    IEnumerator BuffStartCoroutine()
    {
        buff = true;
        Debug.Log(buff);

        yield return new WaitForSeconds(10f);

        buff = false;
        Debug.Log(buff);
    }

버프를 주는 시간을 8초로 지정하고, 버프의 지속시간을 10초로 지정해보자.
코루틴이 진행될 때 (버프가 지속 될 때) 코루틴을 한번 더 시작(버프를 주는) 시키는 상황에서는 두 가지의 결과를 예상해 볼 수 있다

  1. 첫번째 BuffStartCoroutine이 시작된 이후에는 무조건 true만 출력될 것이다 (코루틴이 그 전 코루틴을 덮어쓴다)

  2. 중간중간 2초간격으로 false와 true가 반복될 것이다. (하나의 쓰레드 안에서 코루틴 여러개 실행)

결과는 위의 사진처럼 2번과 같다. 코루틴은 하나의 쓰레드 안에서 여러개가 동시에 실행이 된다는 것이다.
하지만 게임에서는 버프의 지속시간이 버프를 주는 시간보다 길다면 버프는 계속 지속되어어야 한다.
따라서 1번상황을 구현할려면 어떻게 해야할까 ??

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

public class Test : MonoBehaviour
{
    float time;
    float reStartTime = 8f;

    bool buff;

    Coroutine buffCoroutine;

    private void Start()
    {
        buff = false;
        time = 0; 
    }

    private void Update()
    {
       if ( Time.time > time + reStartTime)
        {
           if(buffCoroutine != null)
           {
              StopCoroutine((buffCoroutine));
           }
           buffCoroutine = StartCoroutine(BuffStartCoroutine());
           time += reStartTime;
        }
    }

    IEnumerator BuffStartCoroutine()
    {
        buff = true;
        Debug.Log(buff);

        yield return new WaitForSeconds(10f);

        buff = false;
        Debug.Log(buff);
    }
}

위의 코드처럼 코루틴 변수를 만들어 코루틴이 실행중인지 검사하고 실행중이라면 그 코루틴을 중지시키고 다시 코루틴을 실행시키는 것이다.

그 결과 위의 그림처럼 항상 True값이 출력된다.

좋은 웹페이지 즐겨찾기