본문 바로가기
개발/Unity

유니티 UI - 코루틴 메시지 큐로 순서대로 함수 실행하기 (Execute Coroutines Run in Order using Message

by 피로물든딸기 2022. 7. 28.
반응형

Unity 전체 링크

간단한 토스트 메시지 만들기 (Toast Message)를 TMPro를 이용하여 수정하면 아래와 같다.
그리고 큐브를 클릭할 경우 클릭 횟수를 보여주도록 메시지를 수정하였다.

여기서, 메시지가 많은 경우 코루틴이 순서대로 동작하도록 코드를 수정해보자.

ToastMsg.cs는 Ctrl + Shift + N으로 빈 게임오브젝트를 만든 후 추가하자.
그리고 Canvas에서 만든 Text (TMP)를 스크립트에 추가한다. (Canvas는 보기 편한대로 적절히 배치한다.)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class ToastMsg : MonoBehaviour
{
    public TextMeshProUGUI toast;
    float fadeInOutTime = 0.3f;
    static ToastMsg instance = null;

    public static ToastMsg Instance
    {
        get
        {
            if (null == instance) instance = FindObjectOfType<ToastMsg>();
            return instance;
        }
    }

    private void Awake()
    {
        if (null == instance) instance = this;
    }

    public void showMessage(string msg, float durationTime)
    {
        StartCoroutine(showMessageCoroutine(msg, durationTime));
    }

    private IEnumerator showMessageCoroutine(string msg, float durationTime)
    {
        toast.text = msg;
        toast.enabled = true;

        yield return fadeInOut(toast, fadeInOutTime, true);

        float elapsedTime = 0.0f;
        while (elapsedTime < durationTime)
        {
            elapsedTime += Time.deltaTime;
            yield return null;
        }

        yield return fadeInOut(toast, fadeInOutTime, false);

        toast.enabled = false;
    }

    private IEnumerator fadeInOut(TextMeshProUGUI target, float durationTime, bool inOut)
    {
        float start, end;
        if (inOut)
        {
            start = 0.0f;
            end = 1.0f;
        }
        else
        {
            start = 1.0f;
            end = 0f;
        }

        Color current = Color.clear; /* (0, 0, 0, 0) = 검은색 글자, 투명도 100% */
        float elapsedTime = 0.0f;

        while (elapsedTime < durationTime)
        {
            float alpha = Mathf.Lerp(start, end, elapsedTime / durationTime);

            target.color = new Color(current.r, current.g, current.b, alpha);

            elapsedTime += Time.deltaTime;

            yield return null;
        }
    }
}


ToastClick은 큐브에 추가한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ToastClick : MonoBehaviour
{
    int clickCount;
    public void OnMouseUp()
    {
        string message = "click " + ++clickCount;
        ToastMsg.Instance.showMessage(message, 1.0f);
    }
}


이제 큐브를 클릭하면 메시지가 1초 정도 보이다가 사라진다.



이제 큐브를 여러번 클릭해보자.
아래와 같이 정해진 시간대로 메시지가 사라지지 않는다.

게다가 fadeInOut이 끝나지 않았음에도 다시 코루틴을 실행하기 때문에 깜빡이는 현상도 발생하였다.

이 문제를 막기 위해 Queue를 이용하여 순차적으로 코루틴을 실행하자.
즉, Queue에서 하나씩 메시지를 꺼낸 후 코루틴을 실행하고,
코루틴이 종료되면 다음 Queue에서 메시지를 꺼내 코루틴을 실행한다.

코루틴의 작업을 저장할 TOAST 구조체를 선언하고 메시지와 durationTime을 정의하자.
그리고 Queue를 만든다.

    struct TOAST
    {
        public string msg;
        public float durationTime;
    }

    Queue<TOAST> toastQueue = new Queue<TOAST>();


showMessage 함수는 메시지를 Queue에 넣는다.
현재 Toast 메시지를 보여주는 코루틴이 실행 중이 아니라면 코루틴을 실행시킨다.
그렇지 않다면, Queue에 추가만 될테니 이미 실행 중인 코루틴이 알아서 해결하게 된다.

    bool isPopUp;

    public void showMessage(string msg, float durationTime)
    {
        TOAST t;
        
        t.msg = msg;
        t.durationTime = durationTime;

        toastQueue.Enqueue(t);
        if (isPopUp == false) StartCoroutine(showToastQueue());
    }


Queue.Count가 0이 아니라면 큐에 보여줄 메시지가 있다는 뜻이다.
while문을 이용해 기존에 있던 showMessageCoroutine를 StartCoroutine으로 실행한다.
yield return StartCoroutine 은 코루틴이 끝날 때까지 대기한다.

    IEnumerator showToastQueue()
    {
        isPopUp = true;

        while(toastQueue.Count != 0)
        {
            TOAST t = toastQueue.Dequeue();
            yield return StartCoroutine(showMessageCoroutine(t.msg, t.durationTime));
        }

        isPopUp = false;
    }


이제 정상적으로 코루틴이 종료되면 다음 코루틴이 실행된다.


전체 코드는 다음과 같다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class ToastMsg : MonoBehaviour
{
    public TextMeshProUGUI toast;
    float fadeInOutTime = 0.3f;
    static ToastMsg instance = null;

    public static ToastMsg Instance
    {
        get
        {
            if (null == instance) instance = FindObjectOfType<ToastMsg>();
            return instance;
        }
    }

    void Awake()
    {
        if (null == instance) instance = this;
    }

    struct TOAST
    {
        public string msg;
        public float durationTime;
    }

    Queue<TOAST> toastQueue = new Queue<TOAST>();
    bool isPopUp;

    public void showMessage(string msg, float durationTime)
    {
        TOAST t;
        
        t.msg = msg;
        t.durationTime = durationTime;

        toastQueue.Enqueue(t);
        if (isPopUp == false) StartCoroutine(showToastQueue());
    }

    IEnumerator showToastQueue()
    {
        isPopUp = true;

        while(toastQueue.Count != 0)
        {
            TOAST t = toastQueue.Dequeue();
            yield return StartCoroutine(showMessageCoroutine(t.msg, t.durationTime));
        }

        isPopUp = false;
    }

    IEnumerator showMessageCoroutine(string msg, float durationTime)
    {
        toast.text = msg;
        toast.enabled = true;

        yield return fadeInOut(toast, fadeInOutTime, true);

        float elapsedTime = 0.0f;
        while (elapsedTime < durationTime)
        {
            elapsedTime += Time.deltaTime;
            yield return null;
        }

        yield return fadeInOut(toast, fadeInOutTime, false);

        toast.enabled = false;
    }

    IEnumerator fadeInOut(TextMeshProUGUI target, float durationTime, bool inOut)
    {
        float start, end;
        if (inOut)
        {
            start = 0.0f;
            end = 1.0f;
        }
        else
        {
            start = 1.0f;
            end = 0f;
        }

        Color current = Color.clear; /* (0, 0, 0, 0) = 검은색 글자, 투명도 100% */
        float elapsedTime = 0.0f;

        while (elapsedTime < durationTime)
        {
            float alpha = Mathf.Lerp(start, end, elapsedTime / durationTime);

            target.color = new Color(current.r, current.g, current.b, alpha);

            elapsedTime += Time.deltaTime;

            yield return null;
        }
    }
}

추가로 버튼을 누르거나 이벤트를 발생시켜 코루틴을 종료시키고 싶다면
bool 변수를 추가하여 while문을 빠져나가도록 하면 된다.

    bool interrupt;
    public void interruptMessage() /* 필요한 곳에서 호출 */
    {
        interrupt = true;
    }

    IEnumerator showMessageCoroutine(string msg, float durationTime)
    {
        toast.text = msg;
        toast.enabled = true;

        yield return fadeInOut(toast, fadeInOutTime, true);

        float elapsedTime = 0.0f;
        while (elapsedTime < durationTime && interrupt == false) /* 삭제되지 않은 경우만 유지 */
        {
            elapsedTime += Time.deltaTime;
            yield return null;
        }

        /* 삭제 요청이 온 경우는 fadeInOutTime = 0 초 */
        yield return fadeInOut(toast, interrupt ? 0 : fadeInOutTime, false);

        /* 원상 복구 */
        interrupt = false;
        toast.enabled = false;
    }

 

Unity Plus:

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Unity Pro:

 

Unity Pro

The complete solutions for professionals to create and operate.

unity.com

 

Unity 프리미엄 학습:

 

Unity Learn

Advance your Unity skills with live sessions and over 750 hours of on-demand learning content designed for creators at every skill level.

unity.com

반응형

댓글