본문 바로가기
개발/Unity·C#

[유니티/C#] 주기적으로 반복되는 로직 구현하기 w. 코루틴(Coroutine)

by 은성. 2024. 11. 26.

 

게임을 만들다 보면, 시간마다 반복적으로 특정 함수를 실행해야 하는 경우가 있다.
예를 들어 1초마다 방치형 골드를 획득하게 한다거나, 독 디버프에 걸려 3초마다 HP를 잃어야 한다거나..
이런 반복 함수를 쉽게 구현할 수 있는 유니티의 코루틴(Coroutine) 을 살펴보자.

 

 

1. 코루틴이란? (Coroutine)

코루틴의 단어를 살펴보면 co-routine이다.
co는 '함께'라는 뜻이고 routine은 우리가 잘 아는 루틴, 특정 행동의 묶음이다.
직역하자면 특정 행동의 묶음을 '함께' 실행한다는 뜻인데 이는 다른 함수들의 실행과 협력적으로 작동한다는 의미이다.

 

코루틴은 IEnumerator 이라는 메서드로 정의되며, yield 키워드를 통해 실행 흐름을 제어한다.

 

예시 구문

using System.Collections; //해당 네임 스페이스 참조 필요

void Start() 
{
    // 코루틴은 정의된 뒤, 어딘가에서 실행시켜줘야 시작된다.
    StartCoroutine(ExecuteWithInterval());
}

IEnumerator ExecuteWithInterval()
{
    yield return new WaitForSeconds(1f); // yield return 뒤에 조건을 적어준다.
    
    // 이하 실행 내용
    Debug.Log("코루틴!");
}

 

위와 같은 예시에 따르면 1초에 한 번 "코루틴!" 이라는 로그가 찍히게 된다.

 

 

2. 반복 함수는 이렇게도 짤 수 있잖아요? - void Update()

우리가 흔히 '반복' 하면 떠올리는 유니티의 가장 기본적인 함수 중 하나는 void Update() 이다.

왜냐하면 Update 함수는, 우리 모두가 이미 알듯이 '매 프레임 반복해서 실행되는' 함수이기 때문이다.

당연히 Update 함수를 사용해서도 주기마다 특정한 실행을 반복하는 함수를 구현할 수 있다.

 

예시 구문

private float interval = 1f; // 반복 주기
private float timer = 0; 

void Update() 
{
    timer += Time.deltaTime; // 매 프레임 시간 더해주기

    if (timer >= interval)
    {
        Debug.Log("업데이트!");
        timer = 0; // 타이머 초기화
    }

}

 

이렇게 작성했을 경우에도 앞의 코루틴 예제와 동일하게 1초에 한 번 "업데이트!" 라는 로그가 찍히게 될 것이다.

 

그런데 왜 우리는 Update가 아닌 코루틴을 사용하려 하는 것일까?

 

 

3. Update와 Coroutine의 차이점

첫 번째로, Update는 매 프레임 불린다.

만약 1초에 한 번만 실행되어야 하는 함수가 있는데 Update에서 이렇게 매 프레임마다 더해주는 연산을 하게 된다면 불필요한 연산이 계속 발생하고 있다는 것이다.
반면 코루틴은 1초라는 주기를 설정했을 경우, 1초에 한 번만 발생하고 그 외의 시간에는 별도의 연산이 발생하지 않는다.
이는 최적화 면에서 Update보다 코루틴이 유리하다고 말할 수 있을 것이다.

 

두 번째로, 코루틴은 쉽게 중단이나 지연 처리를 할 수 있다.

예를 들어 1초에 한 번 실행하는 위와 같은 함수를, 딱 10번만 실행하고 그 뒤에는 더 이상 실행하고 싶지 않다고 해보자.

count 값을 정의하고 Update에서 매 주기마다 count에 1을 더해주고 count가 10 초과일 때엔 리턴을 시켜줄 수 있다.
하지만 결국 그 count가 10이 넘어가더라도 Update는 계속 실행되며 조건을 체크할 것이다.
이는 최적화 면에서 불필요한 낭비가 된다.

 

반면 코루틴을 사용한다면?
코루틴 내부에서 반복문을 사용하여 조건을 체크하고, 조건이 달성되면 코루틴을 중단시키는 방식으로 구현할 수 있다.
이렇게 되면 불필요한 반복이 발생하지 않기 때문에, 최적화 면에서 유리할 수 있다.

 

예시 구문

using System.Collections;

private float interval = 1f;
private int maxCount = 10;

void Start() 
{
    StartCoroutine(ExecuteWithInterval());
}

IEnumerator ExecuteWithInterval()
{
    for (int i = 1; i <= maxCount; i++)
    {
        yield return new WaitForSeconds(interval);

        // 이하 실행 내용 (1~10이 출력된다.)
        Debug.Log(i);
    }
}

 

 

예시 구문2 (while)

using System.Collections;

private float interval = 1f;
private int maxCount = 10;
private int count = 0;

void Start() 
{
    StartCoroutine(ExecuteWithInterval());
}

IEnumerator ExecuteWithInterval()
{
    while(true)
    {
        yield return new WaitForSeconds(interval);

        // 이하 실행 내용
        count += 1;
        Debug.Log(count); // 1~10이 출력된다.

        if (count >= maxCount)
        {
            yield break; // 반복 종료
        }
    }
}

 

 

Update와 Coroutine의 차이점을 정리하자면 이러하다.

  void Update() Coroutine (IEnumerator)
호출 방식 매 프레임마다 자동 호출 StartCoroutine()으로 수동 호출
실행 주기 프레임마다 실행 yield return으로 지연/조건 처리 가능
지연 처리 불가능 가능 (WaitForSeconds, 조건 등 사용)
적합한 작업 매 프레임 업데이트가 필요한 작업 간헐적, 지연된 작업

 

반복 주기가 너무 짧은(0.1f) 경우에는 Update를 사용하는 편이 성능 면에서 유리하다고 하니 참고하자.

 

 

4. 코루틴의 추가적인 사용법

여러 개의 코루틴을 동시에 실행하는 것도 가능하다.
이렇게 여러 코루틴을 동시에 실행할 경우 여러 코루틴이 독립적으로 작동하지만, CPU 사용에 있어서는 유니티의 단일 스레드 기반으로 효율적으로 작동한다.

 

예시 구문

using System.Collections;

void Start() 
{
    StartCoroutine(Coroutine1());
    StartCoroutine(Coroutine2());
}

IEnumerator Coroutine1()
{
    yield return new WaitForSeconds(1f);

    Debug.Log("Coroutine1");
}

IEnumerator Coroutine2()
{
    yield return new WaitForSeconds(2f);

    Debug.Log("Coroutine2");
}

 

이런 식으로 쓰게 되면 1초 후에 "Coroutine1"이, 2초 후에 "Coroutine2"가 찍히게 된다.

 

이런 코루틴의 시스템을 활용한다면 간단한 싸움 시뮬레이터를 만들 수도 있을 것이다.

예시 구문은 첨부하지 않겠지만.. 아이디어를 첨부하자면 이런 식이다.

- A는 hp가 100, 3초에 한 번 10의 hp를 회복, 2초에 한 번 5의 데미지로 공격

- B는 hp가 120, 4초에 한 번 15의 hp를 회복, 1초에 한 번 2의 데미지로 공격

 

(누가 이길까? 계산을 안 해봐서 누가 더 유리한지 모르겠다.)

 


 

참고자료

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

 

코루틴 - Unity 매뉴얼

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

docs.unity3d.com