OldBlueWater/BlueWater/Assets/Doozy/Runtime/UIManager/Content/UITimer.cs

282 lines
10 KiB
C#

// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved.
// This code can only be used under the standard Unity Asset Store End User License Agreement
// A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms
using System;
using System.Collections;
using Doozy.Runtime.Common;
using Doozy.Runtime.Common.Events;
using Doozy.Runtime.Common.Extensions;
using Doozy.Runtime.Common.Utils;
using Doozy.Runtime.Mody;
using Doozy.Runtime.Reactor;
using Doozy.Runtime.UIManager.Content.Internal;
using UnityEngine;
using UnityEngine.Events;
using static UnityEngine.Mathf;
namespace Doozy.Runtime.UIManager.Content
{
/// <summary>
/// The UITimer component can be used to trigger a callback after a certain amount of time has passed.
/// It works like a countdown timer, where the timer starts at the given duration and counts down to zero.
/// It's also possible to set the timer to loop, so that it will trigger the callback every time the timer reaches the target time.
/// It works with both real time (unscaled time) and game time (scaled time).
/// </summary>
[AddComponentMenu("Doozy/UI/DateTime/UI Timer")]
public class UITimer : DateTimeComponent
{
#if UNITY_EDITOR
[UnityEditor.MenuItem("GameObject/Doozy/UI/DateTime/UI Timer", false, 8)]
private static void CreateComponent(UnityEditor.MenuCommand menuCommand)
{
GameObjectUtils.AddToScene<UITimer>("UITimer", false, true);
}
#endif
/// <summary>
/// Callback triggered when the timer loops.
/// <para/> This means that the timer has reached the target time and it will start counting down again.
/// </summary>
public ModyEvent OnLoop = new ModyEvent();
/// <summary>
/// Callback triggered when the timer loops.
/// <para/> This means that the timer has reached the target time and it will start counting down again.
/// <para/> This is a quick access to the OnLoop ModyEvent.
/// </summary>
public UnityEvent onLoopEvent => OnLoop.Event;
/// <summary> Callback triggered when the timer is updated </summary>
public FloatEvent OnProgressChanged = new FloatEvent();
[SerializeField] private Progressor TargetProgressor;
/// <summary> Reference to a Progressor that will be updated when the timer is updated </summary>
public Progressor targetProgressor
{
get => TargetProgressor;
set => TargetProgressor = value;
}
/// <summary>
/// When true, the timer will update the target progressor value with SetProgressAt instead of PlayToProgress.
/// <para/> Basically, if true, the progressor will not animate to the new value when the timer is updated.
/// </summary>
public bool InstantProgressorUpdate = true;
[SerializeField] private int Loops;
/// <summary>
/// Number of times the timer restarts after it reaches the target time before it stops and completes.
/// <para/> -1 - infinite loops
/// <para/> 0 - no loops (plays once)
/// <para/> > 0 - replays (restarts) for the given number of loops
/// </summary>
public int loops
{
get => Loops;
set => Loops = Max(-1, value);
}
[SerializeField] private float LoopDelay;
/// <summary>
/// Delay between loops time interval
/// <para/> How long to wait before the timer starts counting down again after it reaches the target time
/// </summary>
public float loopDelay
{
get => LoopDelay;
set => LoopDelay = Max(0, value);
}
/// <summary>
/// Flag that indicates if the timer is currently waiting for the loop delay to end before it starts counting down again.
/// </summary>
public bool inLoopDelay { get; protected set; }
/// <summary>
/// Internal DateTime used to keep track of the time the timer needs to wait before it starts counting down again.
/// </summary>
public DateTime loopDelayEndTime { get; protected set; }
/// <summary> Timer progress value (0-1) </summary>
public float progress =>
isFinished
? 1f
: ((float)elapsedTime.TotalMilliseconds / (float)endTime.Subtract(startTime).TotalMilliseconds).Round(4);
#if UNITY_EDITOR
protected override void Reset()
{
base.Reset();
if (labels.Count == 0) labels.Add(new FormattedLabel(null, @"mm\:ss\.f"));
}
#endif // UNITY_EDITOR
protected override void Awake()
{
base.Awake();
inLoopDelay = false;
}
protected override void UpdateCurrentTime()
{
base.UpdateCurrentTime();
if (inLoopDelay) return;
UpdateProgress(progress);
UpdateLabels();
}
public override void UpdateLabels()
{
for (int i = 0; i < labels.Count; i++)
{
if (labels[i].Label == null) continue;
labels[i].SetText(remainingTime);
}
}
public override void ResetTimer()
{
base.ResetTimer();
inLoopDelay = false;
UpdateProgress(0f);
UpdateLabels();
}
public override void StartTimer()
{
inLoopDelay = false;
base.StartTimer();
UpdateProgress(0f);
UpdateLabels();
}
public override void StopTimer()
{
inLoopDelay = false;
base.StopTimer();
UpdateProgress(progress);
UpdateLabels();
}
public override void PauseTimer()
{
base.PauseTimer();
UpdateProgress(progress);
UpdateLabels();
}
public override void ResumeTimer()
{
base.ResumeTimer();
UpdateProgress(progress);
UpdateLabels();
}
public override void FinishTimer()
{
base.FinishTimer();
UpdateProgress(1f);
UpdateLabels();
}
public override void CancelTimer()
{
inLoopDelay = false;
base.CancelTimer();
UpdateLabels();
}
private void UpdateProgress(float newProgress)
{
OnProgressChanged?.Invoke(newProgress);
if (targetProgressor == null) return;
if (InstantProgressorUpdate)
targetProgressor.SetProgressAt(newProgress);
else
targetProgressor.PlayToProgress(newProgress);
}
protected override IEnumerator TimeUpdateCoroutine()
{
waitRealtime ??= new WaitForSecondsRealtime(UpdateInterval); //create a new wait object if it's null
wait ??= new WaitForSeconds(UpdateInterval); //create a new wait object if it's null
previousUpdateInterval = UpdateInterval; //set the previous update interval to the current update interval
int loopCount = loops;
while (isRunning)
{
if (isPaused) //wait for the timer to be resumed
{
yield return null; //wait for the next frame
lastTime = Time.timeAsDouble; //reset the last time
lastUnscaledTime = (float)Time.realtimeSinceStartupAsDouble; //reset the last unscaled time
continue; //skip the rest of the loop
}
//check if the update interval has changed
if (Math.Abs(previousUpdateInterval - UpdateInterval) > 0.001f)
{
waitRealtime = new WaitForSecondsRealtime(UpdateInterval); //create a new WaitForSecondsRealtime with the new update interval
wait = new WaitForSeconds(UpdateInterval); //create a new WaitForSeconds with the new update interval
previousUpdateInterval = UpdateInterval; //update the previous update interval
}
switch (TimescaleMode)
{
case Timescale.Independent:
yield return waitRealtime;
break;
case Timescale.Dependent:
yield return wait;
break;
default:
throw new ArgumentOutOfRangeException();
}
UpdateCurrentTime(); //update the current time
if (!inLoopDelay) //if the timer is not in loop delay
OnUpdate.Execute(); //trigger the OnUpdate event
if (currentTime < endTime)
continue; //if the current time is less than the end time, skip the rest of the loop
if (loops < 0 | (loops > 0 && loopCount > 0)) //if the timer is set to loop
{
if (loopDelay > 0)
{
if (!inLoopDelay)
{
loopDelayEndTime = endTime.AddSeconds(loopDelay);
}
if (currentTime < loopDelayEndTime)
{
inLoopDelay = true;
continue;
}
inLoopDelay = false;
}
loopCount--; //count the loop
OnLoop?.Execute(); //trigger the loop event
SetStartTime(); //reset the start time
SetEndTime(); //reset the end time
currentTime = startTime; //reset the current time
elapsedTime = TimeSpan.Zero; //reset the elapsed time
remainingTime = endTime - startTime; //reset the remaining time
UpdateCurrentTime(); //update the current time
continue; //continue the loop
}
isRunning = false; //stop the timer
OnFinish?.Execute(); //trigger the OnComplete event
}
}
}
}