diff --git a/Runtime/Utility/CountdownTimer.cs b/Runtime/Utility/CountdownTimer.cs
index 59b668c1..8fbc2ed2 100644
--- a/Runtime/Utility/CountdownTimer.cs
+++ b/Runtime/Utility/CountdownTimer.cs
@@ -1,6 +1,7 @@
namespace Zinnia.Utility
{
using System;
+ using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using Zinnia.Extension;
@@ -10,6 +11,33 @@
///
public class CountdownTimer : MonoBehaviour
{
+ ///
+ /// The source of Time to use.
+ ///
+ public enum TimeSourceType
+ {
+ ///
+ /// Taken from .
+ ///
+ ScaledTime,
+ ///
+ /// Taken from .
+ ///
+ UnscaledTime,
+ ///
+ /// Taken from .
+ ///
+ FixedScaledTime,
+ ///
+ /// Taken from .
+ ///
+ FixedUnscaledTime,
+ ///
+ /// Taken from .
+ ///
+ RealTime
+ }
+
///
/// Defines the event with the specified .
///
@@ -56,6 +84,25 @@ public bool BeginOnEnable
beginOnEnable = value;
}
}
+
+ [Tooltip("The source for the time to be used in the countdown.")]
+ [SerializeField]
+ private TimeSourceType timeSource;
+ ///
+ /// The source for the time to be used in the countdown.
+ ///
+ public TimeSourceType TimeSource
+ {
+ get
+ {
+ return timeSource;
+ }
+ set
+ {
+ timeSourceChanged = !timeSource.Equals(value);
+ timeSource = value;
+ }
+ }
#endregion
#region Timer Events
@@ -116,7 +163,7 @@ public float ElapsedTime
{
if (IsRunning && !IsPaused)
{
- currentTime = Time.time;
+ currentTime = actualTime;
}
return currentTime - beginTime;
}
@@ -131,18 +178,18 @@ public float RemainingTime
{
if (IsRunning && !IsPaused)
{
- currentTime = Time.time;
+ currentTime = actualTime;
}
return StartTime + (beginTime - currentTime);
}
}
///
- /// when is called.
+ /// when is called.
///
protected float beginTime;
///
- /// of the current frame.
+ /// of the current frame.
///
protected float currentTime;
@@ -151,6 +198,55 @@ public float RemainingTime
///
protected float remainingAtPauseTime;
+ ///
+ /// Whether the has changed.
+ ///
+ protected bool timeSourceChanged = true;
+
+ ///
+ /// The stored function for retrieving the time.
+ ///
+ protected Func selectedTimeFunction = () => Time.time;
+
+ ///
+ /// The actual time value based on the selected .
+ ///
+ protected float actualTime
+ {
+ get
+ {
+ if (timeSourceChanged)
+ {
+ switch(TimeSource)
+ {
+ case TimeSourceType.ScaledTime:
+ selectedTimeFunction = () => Time.time;
+ break;
+ case TimeSourceType.UnscaledTime:
+ selectedTimeFunction = () => Time.unscaledTime;
+ break;
+ case TimeSourceType.FixedScaledTime:
+ selectedTimeFunction = () => Time.fixedTime;
+ break;
+ case TimeSourceType.FixedUnscaledTime:
+ selectedTimeFunction = () => Time.fixedUnscaledTime;
+ break;
+ case TimeSourceType.RealTime:
+ selectedTimeFunction = () => Time.realtimeSinceStartup;
+ break;
+ }
+ timeSourceChanged = false;
+ }
+
+ return selectedTimeFunction();
+ }
+ }
+
+ ///
+ /// A container to hold the timer coroutine.
+ ///
+ protected Coroutine timerRoutine;
+
///
/// Starts the timer counting down.
///
@@ -171,10 +267,11 @@ public virtual void Begin()
///
public virtual void Cancel()
{
- CancelInvoke(nameof(Complete));
+ CancelRoutine();
+ //CancelInvoke(nameof(Complete));
if (IsRunning)
{
- currentTime = Time.time;
+ currentTime = actualTime;
Cancelled?.Invoke();
IsRunning = false;
IsPaused = false;
@@ -194,7 +291,7 @@ public virtual void Pause()
remainingAtPauseTime = RemainingTime;
IsPaused = true;
- CancelInvoke(nameof(Complete));
+ CancelRoutine();
Paused?.Invoke();
}
@@ -280,7 +377,36 @@ protected virtual void OnDisable()
protected virtual void StartTimer(float invokeTime)
{
SetInternalStates();
- Invoke(nameof(Complete), invokeTime);
+ CancelRoutine();
+ timerRoutine = StartCoroutine(StartRoutine(invokeTime));
+ }
+
+ ///
+ /// Starts the timer routine.
+ ///
+ /// The time to wait until completion.
+ /// The enumerator for the coroutine.
+ protected virtual IEnumerator StartRoutine(float invokeTime)
+ {
+ float targetTime = actualTime + invokeTime;
+ while (actualTime < targetTime)
+ {
+ yield return null;
+ }
+ Complete();
+ }
+
+ ///
+ /// Cancels the timer routine.
+ ///
+ protected virtual void CancelRoutine()
+ {
+ if (timerRoutine != null)
+ {
+ StopCoroutine(timerRoutine);
+ }
+
+ timerRoutine = null;
}
///
@@ -294,12 +420,12 @@ protected virtual void Complete()
}
///
- /// Stores current for calculations.
+ /// Stores current for calculations.
///
protected virtual void SetInternalStates()
{
- beginTime = Time.time;
- currentTime = Time.time;
+ beginTime = actualTime;
+ currentTime = actualTime;
}
///
diff --git a/Tests/Editor/Utility/CountdownTimerTest.cs b/Tests/Editor/Utility/CountdownTimerTest.cs
index 1c343c83..e990e0cb 100644
--- a/Tests/Editor/Utility/CountdownTimerTest.cs
+++ b/Tests/Editor/Utility/CountdownTimerTest.cs
@@ -17,6 +17,7 @@ public class CountdownTimerTest
[SetUp]
public void SetUp()
{
+ Time.timeScale = 1f;
containingObject = new GameObject("CountdownTimerTest");
containingObject.SetActive(false);
subject = containingObject.AddComponent();
@@ -29,7 +30,7 @@ public void TearDown()
}
[UnityTest]
- public IEnumerator TimerComplete()
+ public IEnumerator ScaledTimerComplete()
{
containingObject.SetActive(true);
UnityEventListenerMock timerStartedMock = new UnityEventListenerMock();
@@ -44,6 +45,8 @@ public IEnumerator TimerComplete()
subject.StillRunning.AddListener(timerStillRunningMock.Listen);
subject.NotRunning.AddListener(timerNotRunningMock.Listen);
+ subject.TimeSource = CountdownTimer.TimeSourceType.ScaledTime;
+
subject.StartTime = 0.1f;
Assert.IsFalse(timerStartedMock.Received);
@@ -63,7 +66,163 @@ public IEnumerator TimerComplete()
Assert.IsTrue(timerStillRunningMock.Received);
Assert.IsFalse(timerNotRunningMock.Received);
- yield return new WaitForSeconds(0.1f);
+ yield return new WaitForSecondsRealtime(0.1f);
+
+ Assert.IsFalse(timerCancelledMock.Received);
+ Assert.IsTrue(timerCompleteMock.Received);
+
+ timerStillRunningMock.Reset();
+ timerNotRunningMock.Reset();
+
+ subject.EmitStatus();
+
+ Assert.IsFalse(timerStillRunningMock.Received);
+ Assert.IsTrue(timerNotRunningMock.Received);
+ }
+
+ [UnityTest]
+ public IEnumerator ScaledTimerCompleteReducedTimeScale()
+ {
+ Time.timeScale = 0.5f;
+
+ containingObject.SetActive(true);
+ UnityEventListenerMock timerStartedMock = new UnityEventListenerMock();
+ UnityEventListenerMock timerCancelledMock = new UnityEventListenerMock();
+ UnityEventListenerMock timerCompleteMock = new UnityEventListenerMock();
+ UnityEventListenerMock timerStillRunningMock = new UnityEventListenerMock();
+ UnityEventListenerMock timerNotRunningMock = new UnityEventListenerMock();
+
+ subject.Started.AddListener(timerStartedMock.Listen);
+ subject.Cancelled.AddListener(timerCancelledMock.Listen);
+ subject.Completed.AddListener(timerCompleteMock.Listen);
+ subject.StillRunning.AddListener(timerStillRunningMock.Listen);
+ subject.NotRunning.AddListener(timerNotRunningMock.Listen);
+
+ subject.TimeSource = CountdownTimer.TimeSourceType.ScaledTime;
+
+ subject.StartTime = 0.1f;
+
+ Assert.IsFalse(timerStartedMock.Received);
+ Assert.IsFalse(timerCancelledMock.Received);
+ Assert.IsFalse(timerCompleteMock.Received);
+ Assert.IsFalse(timerStillRunningMock.Received);
+ Assert.IsFalse(timerNotRunningMock.Received);
+
+ subject.Begin();
+
+ Assert.IsTrue(timerStartedMock.Received);
+ Assert.IsFalse(timerCancelledMock.Received);
+ Assert.IsFalse(timerCompleteMock.Received);
+
+ subject.EmitStatus();
+
+ Assert.IsTrue(timerStillRunningMock.Received);
+ Assert.IsFalse(timerNotRunningMock.Received);
+
+ yield return new WaitForSecondsRealtime(0.1f);
+
+ Assert.IsFalse(timerCancelledMock.Received);
+ Assert.IsFalse(timerCompleteMock.Received);
+
+ timerStillRunningMock.Reset();
+ timerNotRunningMock.Reset();
+
+ subject.EmitStatus();
+
+ Assert.IsTrue(timerStillRunningMock.Received);
+ Assert.IsFalse(timerNotRunningMock.Received);
+ }
+
+ [UnityTest]
+ public IEnumerator UnscaledTimerComplete()
+ {
+ subject.TimeSource = CountdownTimer.TimeSourceType.UnscaledTime;
+
+ containingObject.SetActive(true);
+ UnityEventListenerMock timerStartedMock = new UnityEventListenerMock();
+ UnityEventListenerMock timerCancelledMock = new UnityEventListenerMock();
+ UnityEventListenerMock timerCompleteMock = new UnityEventListenerMock();
+ UnityEventListenerMock timerStillRunningMock = new UnityEventListenerMock();
+ UnityEventListenerMock timerNotRunningMock = new UnityEventListenerMock();
+
+ subject.Started.AddListener(timerStartedMock.Listen);
+ subject.Cancelled.AddListener(timerCancelledMock.Listen);
+ subject.Completed.AddListener(timerCompleteMock.Listen);
+ subject.StillRunning.AddListener(timerStillRunningMock.Listen);
+ subject.NotRunning.AddListener(timerNotRunningMock.Listen);
+
+ subject.StartTime = 0.1f;
+
+ Assert.IsFalse(timerStartedMock.Received);
+ Assert.IsFalse(timerCancelledMock.Received);
+ Assert.IsFalse(timerCompleteMock.Received);
+ Assert.IsFalse(timerStillRunningMock.Received);
+ Assert.IsFalse(timerNotRunningMock.Received);
+
+ subject.Begin();
+
+ Assert.IsTrue(timerStartedMock.Received);
+ Assert.IsFalse(timerCancelledMock.Received);
+ Assert.IsFalse(timerCompleteMock.Received);
+
+ subject.EmitStatus();
+
+ Assert.IsTrue(timerStillRunningMock.Received);
+ Assert.IsFalse(timerNotRunningMock.Received);
+
+ yield return new WaitForSecondsRealtime(0.1f);
+
+ Assert.IsFalse(timerCancelledMock.Received);
+ Assert.IsTrue(timerCompleteMock.Received);
+
+ timerStillRunningMock.Reset();
+ timerNotRunningMock.Reset();
+
+ subject.EmitStatus();
+
+ Assert.IsFalse(timerStillRunningMock.Received);
+ Assert.IsTrue(timerNotRunningMock.Received);
+ }
+
+ [UnityTest]
+ public IEnumerator UnscaledTimerCompleteReduceTimeScale()
+ {
+ Time.timeScale = 0.5f;
+ subject.TimeSource = CountdownTimer.TimeSourceType.UnscaledTime;
+
+ containingObject.SetActive(true);
+ UnityEventListenerMock timerStartedMock = new UnityEventListenerMock();
+ UnityEventListenerMock timerCancelledMock = new UnityEventListenerMock();
+ UnityEventListenerMock timerCompleteMock = new UnityEventListenerMock();
+ UnityEventListenerMock timerStillRunningMock = new UnityEventListenerMock();
+ UnityEventListenerMock timerNotRunningMock = new UnityEventListenerMock();
+
+ subject.Started.AddListener(timerStartedMock.Listen);
+ subject.Cancelled.AddListener(timerCancelledMock.Listen);
+ subject.Completed.AddListener(timerCompleteMock.Listen);
+ subject.StillRunning.AddListener(timerStillRunningMock.Listen);
+ subject.NotRunning.AddListener(timerNotRunningMock.Listen);
+
+ subject.StartTime = 0.1f;
+
+ Assert.IsFalse(timerStartedMock.Received);
+ Assert.IsFalse(timerCancelledMock.Received);
+ Assert.IsFalse(timerCompleteMock.Received);
+ Assert.IsFalse(timerStillRunningMock.Received);
+ Assert.IsFalse(timerNotRunningMock.Received);
+
+ subject.Begin();
+
+ Assert.IsTrue(timerStartedMock.Received);
+ Assert.IsFalse(timerCancelledMock.Received);
+ Assert.IsFalse(timerCompleteMock.Received);
+
+ subject.EmitStatus();
+
+ Assert.IsTrue(timerStillRunningMock.Received);
+ Assert.IsFalse(timerNotRunningMock.Received);
+
+ yield return new WaitForSecondsRealtime(0.1f);
Assert.IsFalse(timerCancelledMock.Received);
Assert.IsTrue(timerCompleteMock.Received);