걍 new가 쓰기 싫었다.
프레임워크에 타이머가 있길래 써봤는데 오브젝트를 생성하고 타이머가 끝나면 파괴하고 있었다.
너무 화가 났다 참을 수 없었다.
작성 중 우리 프로젝트에서는 ID 같은 건 ID로 하기로 했는데 Id라고 작성한 걸 발견했다.
(몰래 놔둘 생각)
이걸 사용한다면...
지연실행('Invoke', 'StartCoroutine') 대체가능!
업데이트(타이머 작동하는 동안 업데이트처리 가능)!!
쿨다운시스템(콜백 있음) 스킬, 아이템 등 쿨다운 활용 가능!!
UI업데이트!!! 스폰, 페이즈 전환 타이밍 제어 등 시간을 기반하는 건 거~~ 의 대부분 효율적으로 처리가 가능하다.
SimpleTimer 클래스 설명 및 사용방법
SimpleTimer 클래스는 DOTween 라이브러리를 활용하여 간단한 타이머 기능을 제공하는 유틸리티 클래스입니다.
이 클래스는 타이머를 시작, 중지, 일시정지, 재개할 수 있으며, 타이머 완료 시 콜백 함수를 호출할 수 있습니다.
또한 씬 전환 시 메모리 누수를 방지하기 위한 기능을 추가해 두었습니다.
주요 기능
- 타이머 시작: 지정된 시간 후에 특정 작업을 수행하도록 타이머를 설정합니다.
- 타이머 중지: 실행 중인 타이머를 중지하고 리소스를 해제합니다.
- 타이머 일시정지 및 재개: 타이머의 실행을 일시적으로 멈추거나 다시 시작할 수 있습니다.
- 타이머 상태 확인: 특정 타이머가 실행 중인지 여부를 확인할 수 있습니다.
- 모든 타이머 중지 및 정리: 활성화된 모든 타이머를 한 번에 중지하고 메모리를 정리합니다.
- 씬 전환 시 자동 정리: 씬이 언로드 될 때 모든 타이머를 자동으로 정리하여 메모리 누수를 방지합니다.
코드
(자세한 주석이 있으니 참고할 것)
using System;
using DG.Tweening;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using System.Linq;
/// <summary>
/// 간단한 타이머 기능을 제공하는 클래스입니다.
/// DOTween을 활용하여 효율적으로 구현되었습니다.
/// </summary>
public class SimpleTimer
{
private static Dictionary<int, Tween> activeTweens = new Dictionary<int, Tween>();
private static int timerId;
private static bool isInitialized;
static SimpleTimer()
{
InitializeIfNeeded();
}
private static void InitializeIfNeeded()
{
if (!isInitialized)
{
SceneManager.sceneUnloaded += OnSceneUnloaded;
isInitialized = true;
}
}
private static void OnSceneUnloaded(Scene scene)
{
CleanupAllTimers();
}
/// <summary>
/// 모든 타이머를 정리하고 메모리를 정리합니다.
/// 씬 전환 시 자동으로 호출되며, 필요한 경우 수동으로도 호출할 수 있습니다.
/// </summary>
public static void CleanupAllTimers()
{
var timerKeys = activeTweens.Keys.ToList();
foreach (var key in timerKeys)
{
if (activeTweens.ContainsKey(key))
{
StopTimer(key);
}
}
activeTweens.Clear();
timerId = 0;
}
/// <summary>
/// 타이머를 시작하고 설정된 시간 후에 콜백을 호출합니다.
/// </summary>
/// <param name="time">타이머가 작동할 시간(초)</param>
/// <param name="onComplete">타이머 완료 시 호출될 콜백</param>
/// <param name="onUpdate">타이머 진행 중 호출될 업데이트 콜백 (선택사항)</param>
/// <param name="autoStart">자동 시작 여부 fasle시 ResumeTimer 호출해서 시작</param>
/// <returns>생성된 타이머의 ID</returns>
public static int StartTimer(float time, Action onComplete, Action<float> onUpdate = null, bool autoStart = true)
{
InitializeIfNeeded();
timerId++;
int currentTimerId = timerId;
var tween = DOTween.To(() => time, x =>
{
if (onUpdate != null)
onUpdate(x);
}, 0f, time)
.SetAutoKill(false)
.SetRecyclable(true)
.OnComplete(() =>
{
if (onComplete != null)
onComplete();
if (activeTweens.ContainsKey(currentTimerId))
activeTweens.Remove(currentTimerId);
})
.OnKill(() =>
{
if (activeTweens.ContainsKey(currentTimerId))
activeTweens.Remove(currentTimerId);
});
if (!autoStart)
tween.Pause();
activeTweens[currentTimerId] = tween;
return currentTimerId;
}
/// <summary>
/// 특정 ID의 타이머를 중지합니다.
/// </summary>
/// <param name="id">중지할 타이머의 ID</param>
public static void StopTimer(int id)
{
if (activeTweens.TryGetValue(id, out Tween tween))
{
tween.Kill();
activeTweens.Remove(id);
}
}
/// <summary>
/// 특정 ID의 타이머를 일시정지합니다.
/// </summary>
/// <param name="id">일시정지할 타이머의 ID</param>
public static void PauseTimer(int id)
{
if (activeTweens.TryGetValue(id, out Tween tween))
{
tween.Pause();
}
}
/// <summary>
/// 특정 ID의 타이머를 재개합니다.
/// </summary>
/// <param name="id">재개할 타이머의 ID</param>
public static void ResumeTimer(int id)
{
if (activeTweens.TryGetValue(id, out Tween tween))
{
tween.Play();
}
}
/// <summary>
/// 모든 활성 타이머를 중지합니다.
/// </summary>
public static void StopAllTimers()
{
foreach (var tween in activeTweens.Values)
{
tween.Kill();
}
activeTweens.Clear();
}
/// <summary>
/// 특정 ID의 타이머가 실행 중인지 확인합니다.
/// </summary>
/// <param name="id">확인할 타이머의 ID</param>
/// <returns>타이머 실행 여부</returns>
public static bool IsTimerRunning(int id)
{
return activeTweens.ContainsKey(id) && activeTweens[id].IsPlaying();
}
}
사용예제
SimpleTimer를 사용한 지연실행 (Invoke 대체)
void Start()
{
SimpleTimer.StartTimer(5f, () =>
{
Debug.Log("5초 후 실행되었습니다.");
});
}
쿨다운 시스템 구현 (콜백 활용)
public class AbilityController : MonoBehaviour
{
private bool isCooldown = false;
private int cooldownTimerId;
public void UseAbility()
{
if (!isCooldown)
{
// 능력 발동 로직
Debug.Log("능력 사용!");
// 쿨다운 시작
isCooldown = true;
cooldownTimerId = SimpleTimer.StartTimer(10f, () =>
{
isCooldown = false;
Debug.Log("쿨다운 종료! 능력을 다시 사용할 수 있습니다.");
});
}
else
{
Debug.Log("능력이 쿨다운 중입니다.");
}
}
}
UI업데이트
(5초 동안 진행되는 타이머를 시작하고, onUpdate 콜백을 통해 남은 시간을 기반으로 UI 진행바를 업데이트)
using UnityEngine.UI;
public class TimerUI : MonoBehaviour
{
public Image progressBar;
void Start()
{
SimpleTimer.StartTimer(5f, () =>
{
Debug.Log("타이머 완료!");
progressBar.fillAmount = 0f;
},
(remainingTime) =>
{
progressBar.fillAmount = remainingTime / 5f;
});
}
}
페이즈 전환 타이밍 조절
(각 페이즈가 일정 시간 동안 진행되고, 타이머 완료 시 다음 페이즈로 전환됩니다.)
public class PhaseManager : MonoBehaviour
{
private int phase = 1;
void Start()
{
StartPhase(phase);
}
void StartPhase(int phaseNumber)
{
Debug.Log($"페이즈 {phaseNumber} 시작");
// 페이즈별 지속 시간 설정 (예: 각 페이즈는 10초)
float phaseDuration = 10f;
SimpleTimer.StartTimer(phaseDuration, () =>
{
phase++;
if (phase <= 3)
{
StartPhase(phase);
}
else
{
Debug.Log("모든 페이즈 완료");
}
});
}
}
현재 씬 전환 시 자동으로 정리가 되도록 해두었지만
만약 특별한 상황이 있다면 직접 정리하는 함수를 호출해주어야합니다.(우린아님)