Unity 분석

코루틴의 작동방식

지영7130 2023. 1. 6. 00:00

코루틴은 작업을 다수의 프레임에 분산시켜서 실행할 수 있는 문법이다. 여기에서 작업을 다수에 프레임에 분산 시킨다는 것은 한 함수를 해당 프레임에서 호출했을 때 일반적으로는 그 프레임에서 모두 실행을 한다. 하지만 코루틴은 이러한 작업을 그 함수를 실행한 프레임에서 한번에 실행시키는 것이 아니라 여러 프레임에 나누어서 실행시킬 수 있다.

https://docs.unity3d.com/kr/2021.3/Manual/Coroutines.html

 

코루틴 - Unity 매뉴얼

코루틴을 사용하면 작업을 다수의 프레임에 분산할 수 있습니다. Unity에서 코루틴은 실행을 일시 정지하고 제어를 Unity에 반환하지만 중단한 부분에서 다음 프레임을 계속할 수 있는 메서드입니

docs.unity3d.com

 

using System.Collections;
using UnityEngine;

public class Coroutine : MonoBehaviour
{
    private void Start()
    {
        StartCoroutine(Func1());
        
        Func2();
    }

    IEnumerator Func1()
    {
        Debug.Log(1);

        yield return null;

        Debug.Log(2);
    }
    
    private void Func2()
    {
        Debug.Log(3);

        Debug.Log(4);
    }
}


위 코드에서 Func1()을 Start 함수에서 실행시켰을 때는 해당 프레임에서 Debug.Log(1) 이 실행된다. 이후 yield return null;을 만나서 해당 프레임에서 실행을 멈추고 다음 프레임에서 Debug.Log(2)을 실행시킨다. 반면에 Func2()가 Start 함수에서 실행될때는 해당 프레임에서 Debug.Log(3)이랑 Debug.Log(4)를 모두 실행한다. 그래서 해당 코드를 실행하면 결과가 다음과 같이 나타난다. 분명 Start 함수에서 Func1을 먼저 실행시켰지만 Func1의 Debug.Log(2)는 가장 마지막에 실행되는 것을 확인할 수 있다.


코루틴을 사용하면 이렇게 작업을 여러 프레임에 분산시켜 실행할 수 있다. 유니티 공식문서에서는 이러한 방법으로 한 프레임에 많은 작업을 해야하는 경우 코루틴을 이용해 작업을 분산시켜서 실행시킬 수 있다고 나와있다.

IEnumerator DoCheck()
{
    for(;;)
    {
        if (ProximityCheck())
        {
            // Perform some action here
        }
        yield return new WaitForSeconds(0.1f);
    }
}

bool ProximityCheck()
{
    for (int i = 0; i < enemies.Length; i++)
    {
        if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance) {
                return true;
        }
    }

    return false;
}


해당 코드에서는 ProximityCheck() 에서 적과 나의 거리를 측정해 적과 거리가 가까워지면 // Perform some action here 에 있는 내용을 실행하도록 되어있다. 만약 이 작업을 매 프레임마다 실행했을 때 적이 너무 많아지면 해당 프레임에 실행되는 다른 기능들 까지 느려지는 프레임드랍이 생길 수 있다. 하지만 위 코드에서는 yield return new WaitForSeconds(0.1f); 을 이용해 0.1초마다 한번씩 실행되도록 하고있다.

코루틴을 이용하면 이렇게 매 프레임마다 함수를 실행시키지 않고 기능을 구현할 수 있다. 밑에 다른 예시를 봐보자. 만약 코루틴을 사용하지 않고 5초뒤에 함수를 실행시키려면 어떻게 해야할까?

using UnityEngine;

public class Coroutine : MonoBehaviour
{
    private float time = 0;
    private bool b = true;

    private void Update()
    {
        if (!b) return;

        if(time >= 5.0f)
        {
            Func();
            b = false;
        }
        else
        {
            time += Time.deltaTime;
        }
    }

    private void Func()
    {
        //내용
    }
}


이런 다소 복잡한 코드를 사용해야 할것이다. 우리가 원하는건 5초 뒤에 Func라는 함수를 실행시키는 것이지만 이를 위해 매 프래임마다 time에 Time.deltaTime을 더하고 time이 5를 넘었는지를 계속 확인해줘야 한다. 이 문제는 코루틴을 사용하면 간단히 해결할 수 있다.

using System.Collections;
using UnityEngine;

public class Coroutine : MonoBehaviour
{
    private void Start()
    {
        StartCoroutine(Func());
    }

    IEnumerator Func()
    {
        yield return new WaitForSeconds(5.0f);

        //내용
    }
}


코루틴은 이렇게 작업을 프레임별로 분리시킬 수 있고 원하는 시간만큼 지연시킬 수 있다. 위에서도 잠깐 언급했지만 반복문에 코루틴을 사용하면 해당 반복문이 실행되는 속도를 통제할 수 있게된다. 이러한 방법으로 일정한 시간마다 한번씩 실행되는 내용을 간단하게 구현할 수 있다.

IEnumerator DoCheck()
{
    While(true)
    {
        //내용
        
    	yield return new WaitForSeconds(1.0f);
    }
}


하지만 이러한 코루틴도 단점은 있다. 시간지연시키는 코루틴을 살펴보면 new WaitForSeconds(1.0f); 로 객체를 생성하는 것을 볼 수 있다. 유니티 공식문서를 보면 코루틴은 "중단한 부분에서 다음 프레임을 계속할 수 있는 메서드입니다." 라고 나와있다. 즉 코루틴이 실행될 때 필요한 데이터들은 모두 힙 영역에 저장된다는 것이다. 또한 이러한 힙 영역에 저장된 값을 처리하기 위해서는 GC를 호출해야 한다.

유니티 공식문서에서 코루틴의 작동방식을 살펴보면 이와 같은 내용을 확인할 수 있다.


이 내용을 요약하면 코루틴이 작동하는 동안에는 코루틴은 힙에 할당된 상태로 남아있는다. 그리고 이 할당된 내용을 추적하여 코루틴의 어느 부분부터 다시 시작해야하는지를 기억한다. 이렇게 코루틴이 힙에 할당되는 이유는 다음 프레임에서 코루틴을 시작할 때 어느부분에서 부터 시작해야 하는지를 기록해두어야 이 코루틴을 다시 실행시켰을 때 그부분 부터 다시 실행시킬 수 있기 때문이다. 프로그램이 실행되면서 데이터를 기록할 수 있는 영역은 힙 영역과 스택영역이다. 하지만 스택영역은 해당 코드블럭이 끝나면 모두 날라간다. 그렇기 때문에 코루틴에 대한 데이터들이 힙에 저장된다고 추측된다.

이렇게 코루틴은 매 프레임마다 실행시키지 않아도 되는 작업들을 처리하는데에 큰 도움을 주지만 사용할 때 GC를 호출한다. 따라서 매 프레임마다 실행돼야 하는 작업들을 코루틴으로 실행하면 오히려 역효과가 날 수 있다. 유니티 공식문서 에서는 이렇게 매 프레임마다 실행되고 오래 실행되는 작업들은 Update와 LateUpdate로 대체하는 것이 더 효과적이라고 나와있다.

또한 코루틴은 최대한 적은 수의 개별 코루틴으로 압축하는 것이 가장 좋다고 한다. 중첩 코루틴은 코드가 깔끔해지고 유지보수에 좋지만 메모리가 많이 소비된다고 한다.

'Unity 분석' 카테고리의 다른 글

MonoBehaviour  (0) 2023.01.08
Enum의 활용  (0) 2023.01.07
메모리 구조와 유니티 - GC에 대한 심층적 이해  (0) 2023.01.05
GameObject.Find()  (0) 2023.01.04
물체가 이동하면서 얇은 벽을 통과하는 경우  (0) 2023.01.04