Skip to content
Dunking Dog Games Dunking Dog Games

A Useful Countdown Timer Class in C# for Unity

· C# , Game Architecture , Tutorials , Unity

CountdownTimer C# class for Unity games

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.deltaTime for 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

  1. Always check Time.deltaTime: The timer uses it for frame-rate independence
  2. Handle OnDestroy: Clean up timers when scenes change to prevent memory leaks
  3. Use callbacks instead of polling: Attach actions to OnComplete, OnPause, etc.
  4. Consider Time.timeScale: Remember that paused games also pause your timer
  5. Test on target devices: Verify timing accuracy on actual hardware, not just editor

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! ⏱️✨

Tags: c#indieunity