Chrono and coolDown

Every time you need a timer, you should use a custom timer like this one. The Invoke methodes & coroutine are not the best way to go if you just need a simple timer:
  • First, it uses memory process for nothing.
  • Second, it adds confusion in the code.
  • Third, you can't have the ability to Pause, Loop...

The solution is a simple Chrono script which can be started, paused, looped. I have created 2 class for 2 slightly different needs:

  • FrequencyChrono
    It starts the time at 0 and goes forward (or backward, if your timeScale is negative !)
  • FrequencyCoolDown
    it starts at X seconds, and goes toward 0


I use in both script the class
TimeEditor
to have the timers working in both Editor and Play mode. More info here. Furthermore, the use of the TimeEditor allows you to have a negative timeScale. In Short: go back in time !
If you don't want to use it, simply replace
TimeEditor
by
Time
.








What it does:

FrequencyChrono

Animated Chrono

A Chrono which start the time at 0 and go forward

  • It handles the
    timeScale
    , in editor & in play mode
  • It can be paused, and resumed
  • It can loop from a given
    MaxTime
    , or stop if needed
  • It can be manually moved forward or backward if needed
  • It can go backward if the timeScale is negative

FrequencyCoolDown

Animated CoolDown

A CoolDown which starts at X seconds, and goes toward 0

  • It handles the
    timeScale
    , in editor & in play mode
  • It can be paused, and resumed


How to use:

Here there is the exemple of both class. Use the one the more convenient for you. First you need to instantiate the timer:

private FrequencyCoolDown _coolDown = new FrequencyCoolDown();
private FrequencyChrono _chrono = new FrequencyChrono();

At any time in a script editor or in a normal script, you can start the coolDown:

_coolDown.StartCoolDown(5f);    //it will go from 5 to 0

_chrono.StartChono();        //it will go from 0 to infinite
_chrono.StartChrono(5f);      //it will go from 0 to 5, and stay at 5
_chrono.StartChrono(5f, true); //it will loop from 0 to 5

At any time, you can get the current time:

_coolDown.GetTimer();
_chrono.GetTimer();

if you want to test if the timers are currently running:

if (_coolDown.IsRunning()) {}
if (_chrono.IsRunning()) {}

if you want to test if the timers started and finished:

if (_coolDown.IsFinished()) {}
if (_chrono.IsFinished()) {}



Scripts:

FrequencyChrono.cs
Download
Copy
/// <summary>
/// MIT License - Copyright(c) 2019 Ugo Belfiore
/// </summary>

using UnityEngine;

/// <summary>
/// Create a Chrono, who start at 0, and increment over time.
/// 
/// Use: create a new FrequencyChrono:
/// private FrequencyChrono _chrono = new FrequencyChrono();
/// 
/// Start it when you want:
/// _coolDown.StartChono();
/// 
/// You can give it a max time (it will go from 0 to 5, and stay at 5)
/// _coolDown.StartChrono(5f);
/// 
/// You can even ask it to loop:
/// _coolDown.StartChrono(5f, true);
/// 
/// at any time you want, get the current value of the Chrono:
/// _coolDown.GetTimer();
/// 
/// if you want to test if the Chrono is currently running
/// if (_coolDown.IsRunning())
/// 
/// if you have set a maxTime, and you want to test if the _coolDown is Started, and Finished:
/// if (_coolDown.IsFinished())
/// 
/// Sometime it nice to know if the timer is not initialize, or finished, to start it again, you can do it with this:
/// if (_coolDown.IsNotStartedOrFinished())
/// </summary>
[System.Serializable]
public class FrequencyChrono
{
    private float _timeStart = 0;
    private bool _loop = false;
    private float _maxTime = -1;
    public float MaxTime { get { return (_maxTime); } set { _maxTime = value; } }
    private float _saveTime = 0;
    private bool _isInPause = false;
    public bool IsInPause {  get {  return (_isInPause); } }
    private bool _isFinishedOrNeverStarted = true;
    private bool _isStarted = false;
    public bool IsStarted { get { return (_isStarted); } }
    private bool _useUnscaleTime = false;

    private float _currentTime;

    #region public functions

    /// <summary>
    /// Start the Chrono
    /// </summary>
    /// <param name="useUnscaleTime">define if we want to use a timeScale-independent timer or not</param>
    public void StartChrono(bool useUnscaleTime = false)
    {
        _useUnscaleTime = useUnscaleTime;
        _timeStart = GetTimeScaledOrNot();
        _loop = false;
        _isInPause = false;
        _maxTime = -1;
        _isFinishedOrNeverStarted = true;
        _isStarted = true;
    }

    /// <summary>
    /// Start the Chrono, with a maxTime. The chrono will stop at the max time,
    /// or loop  if needed.
    /// </summary>
    /// <param name="maxTime">max time in seconds</param>
    /// <param name="loop">do the chrono loop modulo maxTime ?</param>
    /// <param name="useUnscaleTime">define if we want to use a timeScale-independent timer or not</param>
    public void StartChrono(float maxTime, bool loop, bool useUnscaleTime = false)
    {
        //if there is a negative maxTime, don't use it, and simply advance forward the timer without stopping
        if (maxTime < 0)
        {
            StartChrono(useUnscaleTime);
            return;
        }

        _useUnscaleTime = useUnscaleTime;
        _timeStart = GetTimeScaledOrNot();
        _loop = loop;
        _maxTime = maxTime;
        _isInPause = false;
        _isFinishedOrNeverStarted = true;
        _isStarted = true;
    }

    /// <summary>
    /// return the current timer, in seconds
    /// eg: return 125.95 if the timer is at 2 minutes, 5 seconds and 95 miliseconds
    /// </summary>
    /// <param name="managePause">Should always be true, return the time when we were in pause</param>
    /// <returns>return the time in seconds eg: return 125.95 if the timer is at 2 minutes, 5 seconds and 95 miliseconds</returns>
    public float GetTimer(bool managePause = true)
    {
        if (!_isStarted)
        {
            return (0);
        }

        if (managePause && _isInPause)
        {
            return (_saveTime);
        }

        _currentTime = GetTimeScaledOrNot() - _timeStart;

        if (_loop)
        {
            if (_currentTime > _maxTime)
            {
                _currentTime -= _maxTime;
            }
            _currentTime %= _maxTime;
        }
        else if (_maxTime != -1 && Mathf.Abs(_currentTime) > _maxTime)
        {
            _currentTime = _maxTime * Mathf.Sign(_currentTime);
        }

        return (_currentTime);
    }

    /// <summary>
    /// Is the Chrono started, but not finished yet ?
    /// if the Start options loop is set to false, and if the chrono is started,
    ///     it will return true as long as you don't reset it.
    /// </summary>
    public bool IsRunning()
    {
        if (IsStarted && _isInPause)
        {
            return (true);
        }

        if (IsStarted && (!IsTimeExceedMaxTime() || _loop))
        {
            return (true);
        }
        return (false);
    }

    /// <summary>
    /// Is the Chrono not started, or started and over ?
    /// </summary>
    /// <returns></returns>
    public bool IsNotRunning()
    {
        return (!IsRunning());
    }

    /// <summary>
    /// is the timer not started, or started and over, and not in pause
    /// </summary>
    /// <returns>return true if the chrono is not started, or started and over</returns>
    public bool IsNotStartedOrFinished()
    {
        if (!IsStarted)
        {
            return (true);
        }
        if (_isInPause)
        {
            return (false);
        }

        //if _maxTime is at 1, that mean we do'nt have any stop time, we will continue forever.
        if (_maxTime == -1)
        {
            return (false);
        }

        _currentTime = GetTimeScaledOrNot() - _timeStart;
        return (Mathf.Abs(_currentTime) >= _maxTime);
    }

    /// <summary>
    /// return true if the timer is finished (and reset the timer)
    /// 
    /// if the parametter resetIfFinished si set to true, the chrono will
    /// reset if the coolDown is finished. That's mean if we test IsFinished() again, it will return false
    /// 
    /// if the timer never started, return false;
    /// if the timer is in looping mode, return false;
    /// </summary>
    /// <param name="resetIfFinished">if true, the coolDown is reset. That's mean if we test IsFinished() again, it will return false</param>
    /// <returns></returns>
    public bool IsFinished(bool resetIfFinished = true)
    {
        if (_loop)
        {
            return (false);
        }

        if (!IsStarted)
        {
            return (false);
        }

        if (_maxTime == -1)
        {
            return (false);
        }

        if (IsTimeExceedMaxTime())
        {
            if (resetIfFinished)
            {
                Reset();
            }
            return (true);
        }
        //Debug.Log("ic not finished...");
        return (false);
    }

    /// <summary>
    /// move in time forward manually by a certain amount
    /// </summary>
    /// <param name="jumpInTime">jump time in second</param>
    public void ManualForward(float jumpInTime = 0.016f)
    {
        _timeStart -= jumpInTime;
        _saveTime += jumpInTime;
    }

    /// <summary>
    /// move in time backward
    /// </summary>
    /// <param name="jumpInTime">jump time in second</param>
    public void ManualBackward(float jumpInTime = 0.016f)
    {
        _timeStart += jumpInTime;
        _saveTime -= jumpInTime;
    }

    /// <summary>
    /// set the Chrono to pause
    /// </summary>
    public void Pause()
    {
        if (_isInPause)
        {
            return;
        }

        _saveTime = GetTimer(false);
        _isInPause = true;
    }

    /// <summary>
    /// Resume the timer, assuming it was in pause previously.
    /// </summary>
    public void Resume()
    {
        if (!IsInPause)
        {
            return;
        }

        _isInPause = false;
        _timeStart = GetTimeScaledOrNot() - _saveTime;
    }

    /// <summary>
    /// reset the chrono to 0 & all the options
    /// </summary>
    public void Reset()
    {
        _timeStart = GetTimeScaledOrNot();
        _loop = false;
        _isInPause = false;
        _isFinishedOrNeverStarted = true;
        _isStarted = false;
    }

    #endregion

    #region private functions

    /// <summary>
    /// get the current time, scaled or unscaled depending on the Start options
    /// Note if you don't have TimeEditor, you can use the normla Time of Unity
    /// </summary>
    /// <returns>return the current timer of unity</returns>
    private float GetTimeScaledOrNot()
    {
        return ((_useUnscaleTime) ? TimeEditor.unscaledTime : TimeEditor.time);
    }

    private bool IsTimeExceedMaxTime()
    {
        if (_maxTime < 0)
        {
            return (false);
        }
        return (Mathf.Abs(GetTimeScaledOrNot()) >= _timeStart + _maxTime);
    }

    #endregion

    
}
FrequencyCoolDown.cs
Download
Copy
/// <summary>
/// MIT License - Copyright(c) 2019 Ugo Belfiore
/// </summary>

using UnityEngine;

/// <summary>
/// Create a cooldown, who go from [5] seconds to 0.
/// 
/// Use: create a new FrequancyCoolDown:
/// private FrequencyCoolDown _coolDown = new FrequencyCoolDown();
/// 
/// Start it when you want, with a given time in second:
/// _coolDown.StartCoolDown(5f);
/// 
/// at any time you want, get the current value of the coolDown:
/// _coolDown.GetTimer();
/// 
/// if you want to test if the _coolDown is currently running, and not finished:
/// if (_coolDown.IsRunning())
/// 
/// if you want to test if the _coolDown is Started, and Finished:
/// if (_coolDown.IsFinished())
/// 
/// Sometime it nice to know if the timer is not initialize, or finished, to start it again, you can do it with this:
/// if (_coolDown.IsNotStartedOrFinished())
/// </summary>
[System.Serializable]
public class FrequencyCoolDown
{
    private float _departureTime = 0; //save the time departure of the coolDown
    private float DepartureTime { get { return (_departureTime); } }
    private bool _isStarted = false;
    public bool IsStarted { get { return (_isStarted); } }
    private bool _useUnscaleTime = false;
    private float _currentTime;
    private bool _isInPause = false;
    public bool IsInPause() { return (_isInPause); }
    private float _savePause;

    #region public functions
    /// <summary>
    /// Start the CoolDown with a given time. It will go from time to 0
    /// </summary>
    public void StartCoolDown(float time, bool useUnscaleTime = false)
    {
        _departureTime = time;
        _departureTime = (_departureTime < 0) ? 0 : _departureTime;

        _useUnscaleTime = useUnscaleTime;
        _currentTime = GetTimeScaledOrNot() + _departureTime * Mathf.Sign(TimeEditor.timeScale);

        _isStarted = true;
        _isInPause = false;
    }

    /// <summary>
    /// return the current timer, in seconds
    /// eg: return 125.95 if the timer is at 2 minutes, 5 seconds and 95 miliseconds
    /// </summary>
    /// <returns>return the time in seconds eg: return 125.95 if the timer is at 2 minutes, 5 seconds and 95 miliseconds</returns>
    public float GetTimer()
    {
        if (!IsRunning())
            return (0);
        if (IsInPause())
        {
            return (_savePause);
        }
        return (_currentTime - GetTimeScaledOrNot());
    }

    /// <summary>
    /// Is the CoolDown started, but not finished yet ?
    /// </summary>
    public bool IsRunning()
    {
        if (IsStarted && !IsReady(false))
        {
            return (true);
        }
        return (false);
    }

    /// <summary>
    /// Is the Chrono not started, or started and over ?
    /// </summary>
    /// <returns></returns>
    public bool IsNotRunning()
    {
        return (!IsRunning());
    }

    /// <summary>
    /// return true if the timer is finished
    /// if the parametter resetIfFinished si set to true, the coolDown will
    /// reset if the coolDown is finished. That's mean if we test IsFinished() again, it will return false
    /// 
    /// if the timer never started, return false;
    /// </summary>
    /// <param name="resetIfFinished">if true, the coolDown is reset. That's mean if we test IsFinished() again, it will return false</param>
    /// <returns>true if the coolDown is finished</returns>
    public bool IsFinished(bool resetIfFinished = true)
    {
        if (IsStarted && IsReady(resetIfFinished))
        {
            return (true);
        }
        return (false);
    }

    /// <summary>
    /// set the Chrono to pause
    /// </summary>
    public void Pause()
    {
        if (_isInPause || !_isStarted)
        {
            return;
        }
        _savePause = GetTimer();
        _isInPause = true;
    }

    /// <summary>
    /// Resume the timer, assuming it was in pause previously.
    /// </summary>
    public void Resume()
    {
        if (!_isInPause || !_isStarted)
        {
            return;
        }
        _currentTime = GetTimeScaledOrNot() + _savePause;
        _isInPause = false;
    }

    /// <summary>
    /// reset the coolDown & all the options
    /// </summary>
    public void Reset()
    {
        _isStarted = false;    //le cooldown est fini, on reset
        _currentTime = GetTimeScaledOrNot();
        _isInPause = false;
    }

    #endregion

    #region private function
    private float GetTimeScaledOrNot()
    {
        return ((_useUnscaleTime) ? TimeEditor.unscaledTime : TimeEditor.time);
    }

    private bool IsReady(bool canDoReset)
    {
        if (!IsStarted)   //cooldown never started, do'nt do anything
            return (false);

        if (_isInPause)
        {
            return (false);
        }


        if (GetTimeScaledOrNot() >= _currentTime && Mathf.Sign(TimeEditor.timeScale) > 0) //le coolDown a commencé, et est terminé !
        {
            if (canDoReset)
                Reset();
            return (true);
        }
        if (_currentTime - GetTimeScaledOrNot() > 0 && Mathf.Sign(TimeEditor.timeScale) < 0) //le coolDown a commencé, et est terminé !
        {
            if (canDoReset)
                Reset();
            return (true);
        }
        return (false); //cooldown a commencé, et est en cours
    }

    #endregion



}



In Bonus, the functions to display the timer nicely in a format
minutes
'
seconds
''
milliseconds

Download the

ExtDateTime.cs
file bellow, then use the function like in this exemple:

float timeInSeconds = Chrono.GetTimer();    //return eg: 84,91

//convert 84,91 into an structure, once filled,
//it contain 1 minutes, 24 seconds, and 91 milliseconds
ExtDateTime.TimeSec timeSec = ExtDateTime.GetTimeSec(timeInSeconds);

//convert the structure into a text 01 ' 24 '' 91. It also manage negative number.
string text = ExtDateTime.GetTimeInStringFromTimeSec(timeSec);

ExtDateTime.cs
Download
Copy
/// <summary>
/// MIT License - Copyright(c) 2019 Ugo Belfiore
/// </summary>

using UnityEngine;

/// <summary>
/// Extensions for DateTime
/// </summary>
public static class ExtDateTime
{
    public struct TimeSec
    {
        public int Minutes;
        public int Seconds;
        public float Miliseconds;
        public bool IsNegative;

        public TimeSec(int minutes, int seconds, float miliseconds, bool isNegative)
        {
            Minutes = minutes;
            Seconds = seconds;
            Miliseconds = miliseconds;
            IsNegative = isNegative;
        }
    }

    /// <summary>
    /// from a TimeSec, transform it to a string
    /// </summary>
    /// <param name="timeSec"></param>
    /// <returns></returns>
    public static string GetTimeInStringFromTimeSec(TimeSec timeSec)
    {
        string timeInText = "";

        if (timeSec.IsNegative)
        {
            timeInText += '-';
        }

        if (timeSec.Seconds == -1)
        {
            return (timeInText);
        }

        if (timeSec.Minutes < 10)
        {
            timeInText += "0";
        }
        timeInText += timeSec.Minutes;

        timeInText += " ' ";

        if (timeSec.Seconds < 10)
        {
            timeInText += "0";
        }
        timeInText += timeSec.Seconds;

        timeInText += " '' ";

        timeSec.Miliseconds = (int)timeSec.Miliseconds;
        if (timeSec.Miliseconds < 10)
        {
            timeInText += "0";
        }

        if (timeSec.Miliseconds >= 100)
        {
            timeSec.Miliseconds = 99;
        }
        
        timeInText += timeSec.Miliseconds;
        return (timeInText);
    }

    /// <summary>
    /// Get a TimeSec from a float time in seconds
    /// </summary>
    /// <param name="time">time in seconds</param>
    /// <returns></returns>
    public static TimeSec GetTimeSec(float time)
    {
        float absTime = Mathf.Abs(time);

        int minutes = GetMinutes(absTime);
        int second = GetSeconds(absTime);
        float miliseconds = GetMiliseconds(absTime);
        return (new TimeSec(minutes, second, miliseconds, Mathf.Sign(time) == -1));
    }

    /// <summary>
    /// from a given time, Get the minutes
    /// </summary>
    public static int GetMinutes(float time)
    {
        int minutes = (int)(time / 60);
        return (minutes);
    }
    public static int GetSeconds(float time)
    {
        int seconds = (int)(time % 60);
        return (seconds);
    }
    public static float GetMiliseconds(float time)
    {
        int minutes = GetMinutes(time);
        int seconds = (int)(time % 60);
        return ((time - seconds - (minutes * 60)) * 100f);
    }
}









See also:

Time, TimeScale and DeltaTime in Editor

I have created a TimeEditor Class which reflects the behaviour of the Time class in editor mode. Useful for tools who need timer & slow motion


EditorOnly Components and Actions when building

This article show how to remove components you don't want in build while keeping them in gameObjects, and how to execute actions when you are building your application