In today’s blog post, I’m excited to introduce the CountdownTimer, a versatile utility class designed for free use in your Unity project. This class provides an elegant solution for managing countdowns and timing events in your games or applications.
It supports essential functionalities such as starting, restarting, and checking the timer’s status or the percentage of time remaining. Whether you need to enforce time limits, create countdown mechanics, or track elapsed time for any purpose, the CountdownTimer is crafted to be a robust and straightforward component you can easily integrate into your Unity projects.
Features
- ⏱️ Start/Stop/Pause: Full control over timer execution
- 🔄 Restart: Reset and restart with a single method call
- 📊 Progress Tracking: Get elapsed time, remaining time, and percentage complete
- 🔔 Callbacks: Execute actions when timer starts, pauses, or completes
- ⚡ Frame-rate Independent: Uses
Time.deltaTimefor consistent timing
The CountdownTimer Class
using System;
using UnityEngine;
[Serializable]
public class CountdownTimer : MonoBehaviour
{
[Header("Timer Settings")]
public float duration = 60f; // Total duration in seconds
[Header("Callbacks")]
public Action OnStart;
public Action OnPause;
public Action OnResume;
public Action OnComplete;
private float elapsedTime = 0f;
private bool isRunning = false;
private bool isPaused = false;
// Properties for external access
public float ElapsedTime => elapsedTime;
public float RemainingTime => Mathf.Max(0, duration - elapsedTime);
public float ProgressPercentage => (elapsedTime / duration) * 100f;
public bool IsRunning => isRunning && !isPaused;
public bool IsComplete => elapsedTime >= duration;
void Update()
{
if (!isRunning || isPaused) return;
elapsedTime += Time.deltaTime;
if (elapsedTime >= duration)
{
elapsedTime = duration;
Stop();
OnComplete?.Invoke();
}
}
public void StartTimer()
{
isRunning = true;
isPaused = false;
OnStart?.Invoke();
}
public void PauseTimer()
{
if (!isRunning || isPaused) return;
isPaused = true;
OnPause?.Invoke();
}
public void ResumeTimer()
{
if (!isRunning || !isPaused) return;
isPaused = false;
OnResume?.Invoke();
}
public void Stop()
{
isRunning = false;
isPaused = false;
}
public void Restart()
{
elapsedTime = 0f;
Stop();
StartTimer();
}
// Helper method for UI updates
public string GetFormattedTime()
{
int minutes = Mathf.FloorToInt(RemainingTime / 60);
int seconds = Mathf.FloorToInt(RemainingTime % 60);
return $"{minutes:D2}:{seconds:D2}";
}
}
Usage Examples
Basic Countdown Timer
using UnityEngine;
public class GameTimer : MonoBehaviour
{
public CountdownTimer timer;
void Start()
{
// Set up callbacks
timer.OnComplete += () =>
{
Debug.Log("Time's up! Game over!");
// Load next scene or show game over screen
};
timer.StartTimer();
}
void Update()
{
// Update UI with remaining time
GetComponent<Text>().text = timer.GetFormattedTime();
}
}
Timer with Pause/Resume (Menu System)
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelTimer : MonoBehaviour
{
public CountdownTimer levelTimer;
public GameObject pauseMenu;
void Start()
{
levelTimer.OnComplete += OnTimeUp;
levelTimer.StartTimer();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
TogglePause();
}
}
void TogglePause()
{
if (levelTimer.IsRunning)
{
levelTimer.PauseTimer();
pauseMenu.SetActive(true);
Time.timeScale = 0f; // Pause all time-based updates
}
else
{
levelTimer.ResumeTimer();
pauseMenu.SetActive(false);
Time.timeScale = 1f; // Resume game time
}
}
void OnTimeUp()
{
Debug.Log("Level failed – time ran out!");
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
Reusable Timer Pool for Multiple Timers
using System.Collections.Generic;
using UnityEngine;
public class TimerManager : MonoBehaviour
{
private List<CountdownTimer> activeTimers = new List<CountdownTimer>();
public CountdownTimer CreateTimer(float duration, Action onComplete)
{
GameObject timerObj = new GameObject("CountdownTimer");
CountdownTimer timer = timerObj.AddComponent<CountdownTimer>();
timer.duration = duration;
timer.OnComplete += onComplete;
activeTimers.Add(timer);
return timer;
}
void OnDestroy()
{
// Clean up all timers when manager is destroyed
foreach (var timer in activeTimers)
{
Destroy(timer.gameObject);
}
activeTimers.Clear();
}
}
// Usage:
void Start()
{
var manager = FindObjectOfType<TimerManager>();
// Create a 10-second timer
manager.CreateTimer(10f, () => Debug.Log("10 seconds elapsed!"));
// Create a 30-second timer
manager.CreateTimer(30f, () => Debug.Log("30 seconds elapsed!"));
}
Integration with Unity UI
Updating a Text Component
using UnityEngine;
using UnityEngine.UI;
public class TimerUI : MonoBehaviour
{
public CountdownTimer timer;
public Text timeText;
public Slider progressSlider;
void Start()
{
timer.StartTimer();
}
void Update()
{
// Update text display
timeText.text = timer.GetFormattedTime();
// Update progress bar (inverted – fills as time runs out)
if (progressSlider != null)
{
progressSlider.value = 1f - (timer.ProgressPercentage / 100f);
}
}
}
Best Practices
- Always check
Time.deltaTime: The timer uses it for frame-rate independence - Handle OnDestroy: Clean up timers when scenes change to prevent memory leaks
- Use callbacks instead of polling: Attach actions to
OnComplete,OnPause, etc. - Consider Time.timeScale: Remember that paused games also pause your timer
- Test on target devices: Verify timing accuracy on actual hardware, not just editor
Related Resources
- Understanding Unity Coroutines – Alternative timing approaches
- How to Utilize yield return in Unity – Coroutine-based timers
- Unity Build Optimization – Keep your project performant
The CountdownTimer class is free to use in any project, commercial or personal. Share it with fellow developers and let us know how you’re using it! ⏱️✨