코루틴을 사용하면 작업을 다수의 프레임에 분산할 수 있습니다. Unity에서 코루틴은 실행을 일시 정지하고 제어를 Unity에 반환하지만 중단한 부분에서 다음 프레임을 계속할 수 있는 메서드입니다.
대부분의 경우 메서드를 호출하면 실행을 완료한 뒤 호출한 메서드에 제어와 선택적 반환 값을 반환합니다. 즉 메서드 내에서 발생한 모든 행동은 단일 프레임 업데이트 내에서 발생해야 합니다.
시간의 흐름에 따른 이벤트의 시퀀스나 절차상의 애니메이션을 포함하기 위해 메서드 콜을 사용하고자 하는 상황에서 코루틴을 사용할 수 있습니다.
하지만 코루틴은 스레드가 아니라는 점을 명심해야 합니다. 코루틴의 동기 작업은 여전히 메인 스레드에서 실행됩니다. 메인 스레드에 소요되는 CPU 시간을 줄이려면 다른 스크립트 코드에서와 마찬가지로 코루틴의 작업 차단을 방지하는 것이 중요합니다. Unity 내에서 다중 스레드 코드를 사용하려면 C# 잡 시스템을 고려하십시오.
코루틴은 HTTP 전송, 에셋 로드, 파일 I/O 완료 등을 기다리는 것과 같이 긴 비동기 작업을 처리해야 하는 경우 코루틴을 사용하는 것이 가장 좋습니다.
한 가지 예로 오브젝트의 알파(불투명도) 값을 다음과 같이 보이지 않을 때까지 점차 줄이는 작업을 고려해 볼 수 있습니다.
void Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
}
}
이 예제에서 Fade 메서드는 기대했던 효과를 내지 못합니다. 페이딩을 보이게 하려면 Unity가 렌더링하는 중간 값을 표시하기 위해 프레임의 시퀀스에 페이드 알파를 줄여야 합니다. 하지만 이 예제의 메서드는 단일 프레임 업데이트 내에서 전체를 실행합니다. 중간 값은 절대 표시되지 않으며 오브젝트는 즉시 사라집니다.
이 상황을 해결하려면 코드를 프레임 단위로 페이드를 실행하는 Update
함수에 추가할 수 있습니다. 하지만 이런 종류의 작업에는 코루틴을 사용하는 것이 더 편리합니다.
C#에서 다음과 같이 코루틴을 선언합니다.
IEnumerator Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
yield return null;
}
}
코루틴은 Ienumerator 반환 타입과 바디 어딘가에 포함된 yield 반환문으로 선언하는 메서드입니다. yield return null
라인은 실행이 일시 정지되고 다음 프레임에서 다시 시작되는 지점입니다. 코루틴 실행을 설정하려면 다음과 같이 StartCoroutine 함수를 사용해야 합니다.
void Update()
{
if (Input.GetKeyDown("f"))
{
StartCoroutine("Fade");
}
}
Fade 함수의 루프 카운터는 코루틴의 수명 동안 올바른 값을 유지하며 다른 변수나 파라미터는 yield
문 간에 보존됩니다.
기본적으로 Unity는 yield
문 다음에 프레임에 코루틴을 다시 시작합니다. 시간 지연을 도입하려면 다음과 같이 WaitForSeconds를 사용합니다.
IEnumerator Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
yield return new WaitForSeconds(.1f);
}
}
WaitForSeconds
를 사용하여 한동안 효과를 전파할 수 있고 Update
메서드 안의 작업을 포함하는 대신으로 사용할 수 있습니다. Unity는 초당 여러 번 Update
메서드를 호출하며 작업을 자주 반복할 필요가 없는 경우 코루틴에 넣어 정기적으로 업데이트를 할 수는 있지만 모든 단일 프레임을 업데이트할 수는 없습니다.
예를 들어 애플리케이션에 다음의 코드를 사용하여 적이 근처에 있을 경우 경고를 하는 알람을 넣을 수도 있습니다.
function ProximityCheck()
{
for (int i = 0; i < enemies.Length; i++)
{
if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance) {
return true;
}
}
return false;
}
적이 많은 경우 이 함수를 모든 프레임에 호출하면 상당한 오버헤드가 발생할 수 있습니다. 하지만 코루틴을 사용하여 10분의 1초마다 호출할 수 있습니다.
IEnumerator DoCheck()
{
for(;;)
{
ProximityCheck();
yield return new WaitForSeconds(.1f);
}
}
이렇게 하면 게임플레이에 눈에 띄는 영향 없이 수행되는 검사 수를 줄입니다.
StopCoroutine과 StopAllCoroutines을 사용하여 코루틴을 정지할 수 있습니다. 코루틴에 연결된 게임오브젝트를 비활성화하기 위해 SetActive를 false
로 설정하면 코루틴이 정지됩니다. Destroy(example)
(여기서 example
은 MonoBehaviour
의 인스턴스)를 호출하면 OnDisable을 즉시 트리거하며 Unity는 코루틴을 처리하여 효과적으로 정지시킵니다. 마지막으로 OnDestroy
는 프레임 끝에서 호출됩니다.
참고: 활성화에서 false
로 설정하여 MonoBehaviour
를 비활성화한 경우에는 코루틴이 정지되지 않습니다.
코루틴은 다른 스크립트 코드와 다르게 실행됩니다. 대부분의 Unity 스크립트 코드는 단일 위치의 성능 트레이스 내, 특정 콜백 호출 아래에 나타납니다. 반면, 코루틴의 CPU 코드는 항상 트레이스의 두 곳에서 나타납니다.
코루틴의 모든 시작 코드(코루틴 메서드의 시작부터 첫 번째 yield문까지)는 Unity가 코루틴을 시작할 때마다 트레이스에 나타납니다. 시작 코드는 StartCoroutine 메서드가 호출될 때마다 종종 나타납니다. Unity 콜백(IEnumerator
를 반환하는 Start
콜백 등)에서 생성된 코루틴은 각 Unity 콜백에서 최초로 나타납니다.
코루틴의 나머지 코드(즉 다시 시작하는 시점에서부터 실행이 종료될 때까지의 코드)는 Unity 메인 루프에 있는DelayedCallManager
라인에서 나타나게 됩니다.
이는 Unity가 코루틴을 실행하는 방법 때문에 발생합니다. C# 컴파일러는 코루틴을 지원하는 클래스 인스턴스를 자동으로 생성합니다. 그런 다음 Unity는 이 오브젝트를 사용하여 단일 메서드를 여러 번 호출하는 동안 코루틴의 상태를 추적합니다. 코루틴에서 로컬 범위 변수가 yield
호출이 진행되는 동안 유지되어야 하기 때문에 Unity는 로컬 범위 변수를 생성된 클래스로 옮깁니다. 이렇게 하면 코루틴이 작동되는 동안 힙에 할당된 상태로 남아있습니다. 이 오브젝트는 또한 코루틴의 내부 상태를 추적하여 yield 호출 이후에 코루틴이 코드의 어느 부분부터 다시 시작해야 하는지를 기억합니다.
그렇기 때문에 코루틴을 시작할 때 메모리 사용량은 고정된 오버헤드 할당에 로컬 범위 변수의 크기를 합한 양과 동일합니다.
코루틴을 시작하는 코드는 오브젝트를 생성하고 호출하며 그 이후 Unity의 DelayedCallManager
가 코루틴의 yield
조건이 만족될 때마다 다시 오브젝트를 호출합니다. 코루틴은 보통 다른 코루틴의 외부에서 시작하기 때문에 이는 yield
호출과 DelayedCallManager
사이에서 실행 오버헤드를 나눕니다.
Unity 프로파일러를 사용하여 애플리케이션에서 코루틴을 실행하는 부분을 검사하고 이해할 수 있습니다. 이렇게 하려면 모든 스크립트 코드를 프로파일링하고 모든 함수 호출을 기록하는 세부 프로파일링을 활성화하여 애플리케이션을 프로파일링합니다. 그런 다음 CPU 사용 프로파일러 모듈을 사용하여 애플리케이션에서의 코루틴을 조사할 수 있습니다.
일련의 작업을 최대한 적은 수의 개별 코루틴으로 압축하는 것이 가장 좋습니다. 중첩 코루틴은 코드 명료성과 유지관리에 용이하지만 코루틴이 오브젝트를 추적하기 때문에 더 많은 메모리가 소모됩니다.
코루틴이 매 프레임마다 실행되고 오래 실행되는 작업에서 yield
되지 않는 경우 Update
또는 LateUpdate
콜백으로 대체하는 것이 더 효과적입니다. 이는 오래 실행되거나 무한 루프되는 코루틴의 경우에 유용합니다.