[유니티 6] AwaitableTimer 공유

네,가능합니다 ㅣ 2025. 1. 26. 18:00

설명은 주석으로 대체

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

/// <summary>
/// Unity 6의 Awaitable을 활용하여 비동기 타이머 기능을 제공하는 클래스입니다.
/// </summary>
public class AwaitableTimer
{
    private static Dictionary<int, TimerInfo> activeTimers = new Dictionary<int, TimerInfo>();
    private static int timerId;
    private static bool isInitialized;
    public static event Action OnTimerStarted;

    private class TimerInfo
    {
        public float StartTime { get; set; }
        public float Duration { get; set; }
        public Action OnComplete { get; set; }
        public Action<float> OnUpdate { get; set; }
        public bool IsPaused { get; set; }
        public float ElapsedTime { get; set; }
    }

    /// <summary>
    /// 현재 실행 중인 타이머가 있는지 확인합니다.
    /// </summary>
    public static bool HasActiveTimers => activeTimers.Count > 0;

    static AwaitableTimer()
    {
        InitializeIfNeeded();
    }

    private static void InitializeIfNeeded()
    {
        if (!isInitialized)
        {
            SceneManager.sceneUnloaded += OnSceneUnloaded;
            Application.quitting += CleanupAllTimers;
            isInitialized = true;
        }
    }

    private static void OnSceneUnloaded(Scene scene)
    {
        CleanupAllTimers();
    }

    /// <summary>
    /// 모든 타이머를 정리하고 메모리를 해제합니다.
    /// </summary>
    public static void CleanupAllTimers()
    {
        activeTimers.Clear();
        timerId = 0;
    }

    /// <summary>
    /// 타이머를 시작하고 설정된 시간 후에 콜백을 호출합니다.
    /// </summary>
    /// <param name="time">타이머 지속 시간</param>
    /// <param name="onComplete">타이머 완료 시 호출할 콜백</param>
    /// <param name="onUpdate">타이머 업데이트 시 호출할 콜백</param>
    /// <returns>타이머 ID</returns>
    public static int StartTimer(float time, Action onComplete, Action<float> onUpdate = null)
    {
        if (time <= 0)
        {
            onComplete?.Invoke();
            return -1;
        }

        InitializeIfNeeded();
        OnTimerStarted?.Invoke();

        timerId++;
        int currentTimerId = timerId;

        var timerInfo = new TimerInfo
        {
            StartTime = Time.time,
            Duration = time,
            OnComplete = onComplete,
            OnUpdate = onUpdate,
            IsPaused = false,
            ElapsedTime = 0f
        };

        activeTimers[currentTimerId] = timerInfo;
        RunTimerAsync(currentTimerId);

        return currentTimerId;
    }

    private static async void RunTimerAsync(int id)
    {
        if (activeTimers.TryGetValue(id, out TimerInfo timerInfo))
        {
            while (timerInfo.ElapsedTime < timerInfo.Duration)
            {
                if (timerInfo.IsPaused)
                {
                    await Awaitable.NextFrameAsync();
                    continue;
                }

                timerInfo.ElapsedTime += Time.deltaTime;
                timerInfo.OnUpdate?.Invoke(timerInfo.ElapsedTime);
                await Awaitable.NextFrameAsync();
            }

            timerInfo.OnComplete?.Invoke();
            activeTimers.Remove(id);
        }
    }

    /// <summary>
    /// 특정 ID의 타이머를 중지합니다.
    /// </summary>
    public static void StopTimer(int id)
    {
        if (activeTimers.ContainsKey(id))
        {
            activeTimers.Remove(id);
        }
    }

    /// <summary>
    /// 특정 ID의 타이머를 일시정지합니다.
    /// </summary>
    public static void PauseTimer(int id)
    {
        if (activeTimers.TryGetValue(id, out TimerInfo timerInfo))
        {
            timerInfo.IsPaused = true;
        }
    }

    /// <summary>
    /// 특정 ID의 타이머를 재개합니다.
    /// </summary>
    public static void ResumeTimer(int id)
    {
        if (activeTimers.TryGetValue(id, out TimerInfo timerInfo))
        {
            timerInfo.IsPaused = false;
        }
    }

    /// <summary>
    /// 특정 ID의 타이머가 실행 중인지 확인합니다.
    /// </summary>
    public static bool IsTimerRunning(int id)
    {
        return activeTimers.ContainsKey(id) && !activeTimers[id].IsPaused;
    }

    /// <summary>
    /// 특정 ID의 타이머의 남은 시간을 반환합니다.
    /// </summary>
    public static float GetRemainingTime(int id)
    {
        if (activeTimers.TryGetValue(id, out TimerInfo timerInfo))
        {
            return timerInfo.Duration - timerInfo.ElapsedTime;
        }
        return 0f;
    }
}