본문 바로가기
- Unity/유니티

[Unity] IEnumerator (2)

by david_동근 2026. 10. 27.

IEnumerator 예제 코드 리뷰입니다.


 

이전글에서 이어집니다.

https://bulletprooves.tistory.com/105

 

[Unity] IEnumerator yield return null

프로그램이 "yield return null"을 만나면 다음 프레임으로 진행하기 전에 한 프레임을 기다립니다. IEnumerator는 Unity에서 코루틴 작성시 자주 보이는 interface(인터페이스)로,순서대로 값을 꺼내며, 현

bulletprooves.tistory.com

 

 

먼저, 예시 루틴 메서드 전체 코드 Snippet(스니펫)을 보겠습니다.

아래는 어느 토스트 메시지 UI 를 '띠용~' (펀치애니메이션? 이라고 할려나용) 연출을 위한 메서드입니다.

private IEnumerator ShowRoutine(GameObject root, Image img, CanvasGroup canvasGroup, Sprite sprite, bool isMine)
{
    if (root == null || img == null || canvasGroup == null)
        yield break;

    Transform target = root.transform;
    img.sprite = sprite;    // NOTE DAV: 스프라이트는 EmotePopupUI에서 참조

    target.localScale = startScale;
    canvasGroup.alpha = Const.IDENTITY;
    root.SetActive(true);

    // Open
    float elapsed = Const.ZEROF;

    while (elapsed < popDuration)
    {
        elapsed += Time.unscaledDeltaTime;
        float t = (popDuration > Const.ZEROF) ? Mathf.Clamp01(elapsed / popDuration) : Const.IDENTITY;

        if (t < Const.HALF)
        {
            float firstT = t / Const.HALF;
            target.localScale = Vector3.Lerp(startScale, overshootScale, firstT);
        }
        else
        {
            float secondT = (t - Const.HALF) / Const.HALF;
            target.localScale = Vector3.Lerp(overshootScale, Vector3.one, secondT);
        }

        yield return null;
    }

    target.localScale = Vector3.one;

    yield return new WaitForSecondsRealtime(displayDuration);

    // Close
    elapsed = Const.ZEROF;

    while (elapsed < closeDuration)
    {
        elapsed += Time.unscaledDeltaTime;
        float t = closeDuration > Const.ZEROF ? Mathf.Clamp01(elapsed / closeDuration) : Const.IDENTITY;

        canvasGroup.alpha = Mathf.Lerp(Const.IDENTITY, Const.ZEROF, t);
        target.localScale = Vector3.Lerp(Vector3.one, closeScale, t);

        yield return null;
    }

    root.SetActive(false);
    target.localScale = Vector3.one;
    canvasGroup.alpha = Const.IDENTITY;

    if (isMine)
        _myCoroutine = null;
    else
        _otherCoroutine = null;
}

 

어디선가 아래처럼 실행했다고 해봅시다.

_myCoroutine = StartCoroutine(ShowRoutine(root, img, canvasGroup, sprite, true));

 

그럼 ShowRoutine(어쩌구) 호출은 iteratror(이터레이터) 객체를 만들고,
StartCoroutine() 은 Unity 가 그 객체를 실행합니다.

yield 까지 코드를 진행하게 됩니다.

elapsed += Time.unscaledDeltaTime;

로 시간의 경과를 더합니다. (unscaled 라서 시간배율 영향을 받지 않아용~)

로컬변수 t 는 UI를 여는 과정의 진행률이라, 첫번째에서 크기 커졌다가 두번째에서 원래 크기로 돌아옵니다.

t = 0          t = 0.5           t = 1
startScale → overshootScale → Vector3.one

크기가 적용 되고 나면 아래에서 멈춥니다.

yield return null;

☆ 중요!

yield 에서 While 문이 끝나는 것도 아니고, 메서드가 종료된 것도 아닙니다!

현재 실행 위치랑 elapsed, target 같은 변수 상태도 유지한채 Unity에게 제어를 돌려주는 겁니다.

다음 프레임에서 Unity 가 실행을 재개하면 yield 다음부터 이어집니다.

 

위는 While 반복문 맨 끝에 도달했으니 조건을 다시 확인하고 반복문을 돌겠습니다.

 

target.localScale = Vector3.one;

yield return new WaitForSecondsRealtime(displayDuration);

저는 displayDuration 을 2.0f 로 맞춰놨기 때문에, 여기서 약 2초를 기다리겠습니다.

이번에는 null 이 아닌, 대기 객체 new WaitForSecondsRealtime(머시기) 를 내놓았습니다.

즉, Unity 는 이 객체를 해석해서 2초만큼 기다리고 실행을 이어갈 것입니다.

 

쭉 쭉 이어가다가, 같은 방식으로 yield return null 로 다음 프레임까지 실행을 넘깁니다.

 

if (isMine)
    _myCoroutine = null;
else
    _otherCoroutine = null;

 

와 같이 닫기가 끝나면 토스트 메시지 UI 를 닫는 마무리 작업을 하고,

해당 코루틴 참조를 비웁니다.

 

메서드 끝에 도달하면 이터레이터 실행도 종료입니다.

 

아래는 주석을 추가한 메서드 본문입니다.

private IEnumerator ShowRoutine(GameObject root, Image img, CanvasGroup canvasGroup, Sprite sprite, bool isMine)
{
    // 필요한 객체가 없으면 이터레이터 종료! null 방어 코드
    if (root == null || img == null || canvasGroup == null)
        yield break;


    // 첫 yield를 만날 때까지 아래 코드는 같은 프레임에서 연속 실행
    Transform target = root.transform;
    img.sprite = sprite; // NOTE DAV: 스프라이트는 EmotePopupUI에서 참조
    target.localScale = startScale;
    canvasGroup.alpha = Const.IDENTITY;
    root.SetActive(true);

    // Open, 중요: yield로 실행을 넘겨도 elapsed와 실행 위치는 유지
    float elapsed = Const.ZEROF;

    while (elapsed < popDuration)
    {
        // 시간 배율의 영향을 받지 않는 프레임 간 시간을 누적
        elapsed += Time.unscaledDeltaTime;
        float t = (popDuration > Const.ZEROF) ? Mathf.Clamp01(elapsed / popDuration) : Const.IDENTITY;

        if (t < Const.HALF)
        {
            // 첫번째: 시작 크기에서 최대 크기까지 확대한다.
            float firstT = t / Const.HALF;
            target.localScale = Vector3.Lerp(startScale, overshootScale, firstT);
        }
        else
        {
            // 두번째: 최대 크기에서 기본 크기로 돌아온다.
            float secondT = (t - Const.HALF) / Const.HALF;
            target.localScale = Vector3.Lerp(overshootScale, Vector3.one, secondT);
        }

        // Unity에 제어를 돌려주고 다음 프레임에 이 지점 다음부터 이어간다.
        // 반복문 끝이므로 재개 후 while 조건을 다시 검사한다.
        yield return null;
    }

    target.localScale = Vector3.one;

    // 대기 객체를 Unity 에 전달
    // Unity는 시간 배율과 무관하게 지정된 시간이 지난 뒤 실행을 재개
    yield return new WaitForSecondsRealtime(displayDuration);

    // Close: 대기 이후 이 지점부터 이어서 실행
    elapsed = Const.ZEROF;

    while (elapsed < closeDuration)
    {
        elapsed += Time.unscaledDeltaTime;
        float t = closeDuration > Const.ZEROF ? Mathf.Clamp01(elapsed / closeDuration) : Const.IDENTITY;

        canvasGroup.alpha = Mathf.Lerp(Const.IDENTITY, Const.ZEROF, t);
        target.localScale = Vector3.Lerp(Vector3.one, closeScale, t);

        // 닫기 애니메이션도 한 프레임씩 나누어 진행
        yield return null;
    }

    // 정상적으로 끝까지 실행되면 숨긴 뒤 다음 표시를 위한 상태 복구
    root.SetActive(false);
    target.localScale = Vector3.one;
    canvasGroup.alpha = Const.IDENTITY;

    // 보관 중인 코루틴 참조를 비우기 + 이터레이터는 메서드 끝에서 종료
    if (isMine)
        _myCoroutine = null;
    else
        _otherCoroutine = null;
}

 


 

앙 기모링 입니다.

Σ(っ °Д °;)っ