using UnityEngine; using UnityEngine.Events; using TMPro; /// /// Countdown: the remaining time, its rules, its display and its events. /// The CES calls StartTimer(), StopTimer() or AddTime(float) with an Invoke Events action. /// Time freezes on its own when the pause menu sets Time.timeScale to 0. /// public class CountdownTimer : MonoBehaviour { [Header("Value")] [Tooltip("Duration in seconds")] public float duration = 60f; [Tooltip("Checked: starts when the scene loads. Unchecked: waits for a call to StartTimer().")] public bool startOnLoad = true; [Tooltip("Below this number of seconds, On Warning fires once (e.g. red text, faster music). 0 = no warning.")] public float warningThreshold = 10f; [Header("Display")] [Tooltip("Canvas text")] public TMP_Text text; [Tooltip("{0} = minutes, {1} = seconds, {2} = hundredths (optional). E.g. {0:00}:{1:00}.{2:00}")] public string format = "{0:00}:{1:00}"; [Header("Events")] public UnityEvent onStarted; public UnityEvent onWarning; public UnityEvent onFinished; float remaining; bool running; bool warningGiven; public float Remaining => remaining; public bool IsRunning => running; void Start() { remaining = duration; Display(); if (startOnLoad) StartTimer(); } void Update() { if (!running) return; remaining -= Time.deltaTime; if (!warningGiven && warningThreshold > 0 && remaining <= warningThreshold) { warningGiven = true; onWarning.Invoke(); } if (remaining <= 0) { remaining = 0; running = false; Display(); onFinished.Invoke(); return; } Display(); } // --- Public methods: visible in Invoke Events and On Click --- /// Starts over from the full duration. public void StartTimer() { remaining = duration; warningGiven = false; running = true; Display(); onStarted.Invoke(); } public void StopTimer() => running = false; /// Resumes where it stopped. public void ResumeTimer() { if (remaining > 0) running = true; } /// Time bonus (positive) or penalty (negative). public void AddTime(float seconds) { remaining = Mathf.Max(0, remaining + seconds); if (remaining > warningThreshold) warningGiven = false; Display(); } // --- Display --- void Display() { if (text == null) return; if (format.Contains("{2")) { // With hundredths: round down, so the display reaches 00:00.00 exactly at the end int hundredths = Mathf.FloorToInt(remaining * 100f); int totalSeconds = hundredths / 100; text.text = string.Format(format, totalSeconds / 60, totalSeconds % 60, hundredths % 100); } else { // Without hundredths: round up, so 00:00 only shows when time is up int totalSeconds = Mathf.CeilToInt(remaining); text.text = string.Format(format, totalSeconds / 60, totalSeconds % 60); } } }