코루틴 ( Coroutine )
유니티에서 함수의 실행을 일시중지하고 나중에 다시 이어서 실행할 수 있는 유용하고 범용적으로 사용하는 개념이다.
일반 함수의 반환인 return 대신에 yield 키워드를 사용한다.
yield 키워드 사용시 사용이 일시 중지되고 다른 코드를 실행하거나 유니티에게 제어권을 반납하고 다음 실행시 중단점부터 이어서 실행한다. 이런 동작 방식 때문에 마치 비동기적으로 작업을 처리하는 것 같지만 코루틴은 라이프 사이클 내에서 직렬적으로 일을 처리하는 동기 작업이다.
유니티 프로그래밍에서 코루틴은 굉장히 자주 사용되고 아주 중요한 개념이다.
그 만큼 사용시의 주의점 또한 아는 것이 중요하다.
보통 함수는 한 번 호출되면 제어권을 쥐고 끝까지 실행되고 종료된다.
다른 함수를 실행하고 싶어도 이전 함수가 제어권을 쥐고 있어 이전 함수가 끝날 때까지 실행할 수 없다.
하지만 코루틴은 중간에 잠깐 멈췄다가 ( yield ) 일정 조건이 되면 다시 이어서 실행할 수 있다.
#한 번에 다 처리하지 않고 , 시간을 나눠서 처리하는 함수이다.
C# 에서는 IEnumerator 타입을 반환하고 , Unity 에서는 StartCoroutine() 으로 실행한다.
코루틴의 핵심 특징
- 시간을 두고 실행 : 여러 프레임에 걸쳐 작업을 나누어 처리한다
- 중단과 재개 : yield return 을 만나면 실행을 중단하고 제어권을 유니티에 돌려준다
- 비차단적 실행 : 다른 코드의 실행을 막지 않는다
1. 작동 원리
코루틴 함수는 반드시 IEnumerrator 를 반환타입으로 가져야 한다.
// 코루틴 선언
private IEnumerator MyCoroutine()
{
// 코루틴 내용
yield return null;
}
// 잘못된 선언 - 컴파일 에러
private void MyWrongCoroutine() // 반환형 void는 안됨
{
yield return null; // 에러
}
StartCoroutine( ) 메서드를 사용해서 코루틴을 시작한다
ublic class CoroutineManager : MonoBehaviour
{
private void Start()
{
// 방법 1: 메서드 이름으로 시작
StartCoroutine("DelayedAction");
// 방법 2: 메서드 호출로 시작
StartCoroutine(DelayedAction());
// 방법 3: 코루틴 참조를 저장해서 시작 (권장)
Coroutine myCoroutine = StartCoroutine(DelayedAction());
}
private IEnumerator DelayedAction()
{
yield return new WaitForSeconds(2f);
Debug.Log("2초 후 실행됨");
}
}
IEnumerator MoveForward()
{
Debug.Log("시작");
yield return new WaitForSeconds(1f); // 1초 동안 대기
Debug.Log("1초 후 이동 시작");
transform.Translate(Vector3.forward * 5f);
yield return null; // 한 프레임 쉬고 다음 코드 실행
Debug.Log("다음 프레임 실행");
}
▲실행 과정
- StartCoroutine(MoveForward()); 호출
└ Unity 가 MoveForward() 실행 흐름을 관리하기 시작 - 첫 번째 yield return new WaitForSeconds(1f) 에 도달
└ 1초 동안 잠시 정지 - 1초 후 재개 → 다음 줄부터 다시 실행한다
- yield return null → 한 프레임 대기
- 다음 프레임에서 재개
이런 식으로 Unity 는 코루틴을 매 프레임마다 검사하면서 이제 실행해도 되는지를 확인하고
조건이 충족되면 다시 이어서 실행한다.
▼예시 코드
IEnumerator SpawnEnemy()
{
Debug.Log("적 등장 준비중...");
yield return new WaitForSeconds(3f); // 3초 기다림
Debug.Log("적 등장!");
}
void Start()
{
StartCoroutine(SpawnEnemy());
}
2. 코루틴이 필요한 이유
▼일반 함수와 코루틴의 차이점
| 상황 | 일반 함수 | 코루틴 |
| 특정 시간 기다리기 | 불가능 ( 타이머 필요 ) | yield return new WaitForSeconds() |
| 특정 조건까지 반복 | while문으로 CPU 점유 | yield return null 로 프레임마다 확인 |
| 애니메이션처럼 점진적 처리 | 한 번에 끝 | 단계적으로 처리 가능 |
| 비동기적 동작 ( 다운로드 , 이펙트 재생 등 ) | 어려움 | 자연스럽게 구현 가능 |
코루틴은 시간이 개입되는 로직이다.
3초 뒤에 폭발 , 플레이어가 다가오면 천천히 열리는 문 같은 연출용 흐름 제어에 매우 유용하다.
▼Update() 대신 코루틴을 사용하는 이유
| 상황 | Update() 사용 시 | Coroutine 사용 시 |
| 3초 후 실행 | 매 프레임 timer += Time.deltaTime; if ( timer > 3 ) 검사해야 한다. |
yield return new WaitForSeconds(3) 한 줄로 해결 |
| 반복 타이밍 제어 | 조건문과 타이머 변수가 필요하다 | while(true) { yield return new WaitForSeconds(1); } |
| 가독성 | 여러 if문 , flag 변수 복잡 | 자연스러운 순서형 코드 가능 |
3. 주요 yield return 종류
| 구문 | 의미 |
| yield return null | 다음 프레임까지 대기한다 |
| yield return new WaitForSeconds(t) | t초 대기 |
| yield return new WaitUntil(() => 조건) | 조건이 참이 될 때까지 대기한다 |
| yield return new WaitWhile(() => 조건) | 조건이 거짓이 될 때까지 대기한다 |
| yield break; | 코루틴 즉시 종료 |
4. 예시 코드
▼일정한 간격으로 탄환 발사
IEnumerator Fire()
{
while (true)
{
Shoot();
yield return new WaitForSeconds(0.5f);
}
}
▼천천히 이동하기
IEnumerator MoveTo(Vector3 target, float duration)
{
Vector3 start = transform.position;
float time = 0f;
while (time < duration)
{
time += Time.deltaTime;
transform.position = Vector3.Lerp(start, target, time / duration);
yield return null; // 다음 프레임까지 대기
}
}
주의할 점
코루틴을 사용할 때는 편리하지만 , 남용하거나 잘못 사용하면 성능 저하나 버그로 이어질 수 있다.
- 사용이 끝나면 반드시 중지할 것
코루틴을 실행하면 클래스 객체가 생성되어 메모리가 할당된다.
이를 별도로 해제하지 않는 상태로 반복 / 누적이 될 경우
메모리 누수로 이어질 수 있다. - 코루틴을 변수에 담아 사용하기
코루틴을 실행하면 클래스 객체가 생성되기 때문에 , 힙 메모리에 클래스 인스턴스가 할당된다.
주기적으로 실행되어야 하는 코루틴의 경우 변수를 선언에 미리 힙에 할당해놓고
해당 변수에 생성되는 코루틴 객체를 할당하는 방식을 사용한다면
메모리 공간을 정략하며 사용할 수 있기 때문에 메모리 단편화를 방지할 수 있다.
1. 코루틴의 생명주기 ( Lifecycle ) 주의
- 코루틴은 GameObject의 활성 상태에 따라 중단된다.
└ StartCoroutine( ) 으로 시작한 코루틴은 해당 스크립트나 GameObject 가 비활성화되면 멈춘다.
└ StopCoroutine( ) 또는 StapAllCoroutine( ) 을 호출하지 않아도 비활성화 시 자동 정지 - 따라서 오브젝트가 Destroy 될 때 실행 중인 코루틴이 중단되는 점을 고려해야 한다.
private IEnumerator FadeOut()
{
while (alpha > 0)
{
alpha -= Time.deltaTime;
yield return null; // 다음 프레임까지 대기
}
}
FadeOut( ) 도중 오브젝트가 파괴되면 , 이후 코드는 실행되지 않는다
2. StopCoroutine 사용 시 혼동 주의
- StopCoroutine("함수이름") 은 문자열 기반으로 호출되면 함수명이 바뀔 때 문제 발생
- 가장 안전한 방식은 IEnumerator 참조를 직접 저장하는 방법이다.
▼ 안좋은 예시
StartCoroutine("FadeOut");
StopCoroutine("FadeOut");
▼ 좋은 예시
private Coroutine fadeRoutine;
void StartFade()
{
if (fadeRoutine != null) StopCoroutine(fadeRoutine);
fadeRoutine = StartCoroutine(FadeOut());
}
3. 무한 루프 또는 종료 조건 누락
- while ( true ) 같은 코루틴은 조건문 없이 사용 시 무한히 돌아간다
- yield return null 로 계속 대기하더라도 CPU 부하가 커질 수 있다
└ 항상 명확한 종료 조건을 두어야한다.
▼ 주의 예시
IEnumerator BadLoop()
{
while (true)
{
transform.Rotate(Vector3.up);
yield return null;
}
}
▼ 수정 예시
IEnumerator RotateForSeconds(float time)
{
float elapsed = 0f;
while (elapsed < time)
{
transform.Rotate(Vector3.up);
elapsed += Time.deltaTime;
yield return null;
}
}
4. 중복 실행 방지
- StartCoroutine( ) 을 여러 번 호출하면 동일한 코루틴이 중복 실행될 수 있다
└ 한 번에 하나만 동작하게 관리해야 한다
5. yield 구문의 의미 오해 금지
- yield return null → 한 프레임 대기
- yield return new WaitForSeconds(x) → x초 후 재개 ( 실제 시간 기반 )
└ Time.timeScale 의 영향을 받는다
└ WaitForSecondsRealtime 을 사용하면 시간 정지 ( Pause ) 상황에서도 동작한다
6. GC 과다 방지
- 매 프레임마다 new WaitForSeconds( ) 를 생성하면 불필요한 객체 할당이 누적된다
└ 자주 쓰는 값을 미리 캐싱하는 것이 좋다
private WaitForSeconds wait = new WaitForSeconds(1f);
yield return wait;
7. 코루틴은 병렬이 아니다
- 코루틴은 멀티쓰레드가 아니다.
└ 동시에 여러 작업을 하는 것이 아니라 " 한 프레임마다 나눠서 실행 "
└ CPU - intensive 작업에는 쓰레드나 비동기 ( async / await ) 고려 필요
코루틴 동기인가 비동기인가
코루틴은 동기적이면서 논블로킹 ( Tread.Sleep 이 아닌 )인 단일 쓰레드 코드이다.
동기와 비동기 , 블로킹 / 논블로킹은 서로 다른 개념이다.
코루틴의 특성
- 동기적 ( Synchronous ) : 코루틴 내에서는 순차적으로 실행된다.
- 논블로킹 ( Non - Blocking ) : yield return 을 만나면 메인 쓰레드를 차단하지 않고 즉시 제어권 반환
- 단일 쓰레드 : 메인 쓰레드에서만 실행되며 , 별도의 쓰레드를 생성하지 않는다
- 협력적 멀티태스킹 : 여러 코루틴이 하나의 쓰레드에서 번갈아가며 실행
private void Start()
{
Debug.Log("1. Start 시작");
StartCoroutine(TestCoroutine());
Debug.Log("2. StartCoroutine 호출 직후 (즉시 실행됨)");
}
private IEnumerator TestCoroutine()
{
Debug.Log("3. 코루틴 시작");
yield return null; // 여기서 제어권 반환 (논블로킹)
Debug.Log("4. yield 후 재개 (다음 프레임)");
}
// 출력 결과:
// 1. Start 시작
// 3. 코루틴 시작
// 2. StartCoroutine 호출 직후 (즉시 실행됨)
// (다음 프레임) 4. yield 후 재개
동기 / 비동기 vs 블로킹 / 논블로킹의 차이
- 동기 / 비동기 : 작업의 실행 순서 ( 순차적 vs 동시적 )
- 블로킹 / 논블로킹 : 쓰레드 점유 방식 ( 대기 vs 즉시 반환 )
코루틴은 "동기적이면서 논블로킹"인 방식이다.
코루틴 내부에서는 순차적으로 실행되지만 , yield 를 만나면 메인 쓰레드를 차단하지 않고 제어권을 즉시 반환한다.
참고 자료
https://docs.unity3d.com/kr/2022.3/Manual/Coroutines.html
'🧊Unity Basic > 코루틴&이벤트' 카테고리의 다른 글
| 유니티 액션 ( Unity Action ) (0) | 2025.10.20 |
|---|---|
| 유니티 이벤트 ( Unity Event ) (0) | 2025.10.20 |
| Coroutine 핵심 키워드 (0) | 2025.10.18 |
| WaitForSeconds vs WaitForSecondsRealtime (0) | 2025.10.18 |
| IEnumerable vs IEnumerator (0) | 2025.10.18 |