Unity 一个好用的延时脚本Timer,比协程好用多了~

/*
 * Unity Timer
 *
 * Version: 1.0
 * By: Alexander Biggs + Adam Robinson-Yu
 */

using UnityEngine;
using System;
using System.Linq;
using System.Collections.Generic;
using JetBrains.Annotations;
using Object = UnityEngine.Object;

/// 
/// Allows you to run events on a delay without the use of s
/// or s.
///
/// To create and start a Timer, use the  method.
/// 
public class Timer
{
    #region Public Properties/Fields

    /// 
    /// How long the timer takes to complete from start to finish.
    /// 
    public float duration { get; private set; }

    /// 
    /// Whether the timer will run again after completion.
    /// 
    public bool isLooped { get; set; }

    /// 
    /// Whether or not the timer completed running. This is false if the timer was cancelled.
    /// 
    public bool isCompleted { get; private set; }

    /// 
    /// Whether the timer uses real-time or game-time. Real time is unaffected by changes to the timescale
    /// of the game(e.g. pausing, slow-mo), while game time is affected.
    /// 
    public bool usesRealTime { get; private set; }

    /// 
    /// Whether the timer is currently paused.
    /// 
    public bool isPaused
    {
        get { return this._timeElapsedBeforePause.HasValue; }
    }

    /// 
    /// Whether or not the timer was cancelled.
    /// 
    public bool isCancelled
    {
        get { return this._timeElapsedBeforeCancel.HasValue; }
    }

    /// 
    /// Get whether or not the timer has finished running for any reason.
    /// 
    public bool isDone
    {
        get { return this.isCompleted || this.isCancelled || this.isOwnerDestroyed; }
    }

    #endregion

    #region Public Static Methods

    /// 
    /// Register a new timer that should fire an event after a certain amount of time
    /// has elapsed.
    ///
    /// Registered timers are destroyed when the scene changes.
    /// 
    /// The time to wait before the timer should fire, in seconds.
    /// An action to fire when the timer completes.
    /// An action that should fire each time the timer is updated. Takes the amount
    /// of time passed in seconds since the start of the timer's current loop.
    /// Whether the timer should repeat after executing.
    /// Whether the timer uses real-time(i.e. not affected by pauses,
    /// slow/fast motion) or game-time(will be affected by pauses and slow/fast-motion).
    /// An object to attach this timer to. After the object is destroyed,
    /// the timer will expire and not execute. This allows you to avoid annoying s
    /// by preventing the timer from running and accessessing its parents' components
    /// after the parent has been destroyed.
    /// A timer object that allows you to examine stats and stop/resume progress.
    public static Timer Register(float duration, Action onComplete, Action onUpdate = null,
        bool isLooped = false, bool useRealTime = false, MonoBehaviour autoDestroyOwner = null)
    {
        // create a manager object to update all the timers if one does not already exist.
        if (Timer._manager == null)
        {
            TimerManager managerInScene = Object.FindObjectOfType();
            if (managerInScene != null)
            {
                Timer._manager = managerInScene;
            }
            else
            {
                GameObject managerObject = new GameObject { name = "TimerManager" };
                Timer._manager = managerObject.AddComponent();
            }
        }

        Timer timer = new Timer(duration, onComplete, onUpdate, isLooped, useRealTime, autoDestroyOwner);
        Timer._manager.RegisterTimer(timer);
        return timer;
    }

    /// 
    /// Cancels a timer. The main benefit of this over the method on the instance is that you will not get
    /// a  if the timer is null.
    /// 
    /// The timer to cancel.
    public static void Cancel(Timer timer)
    {
        if (timer != null)
        {
            timer.Cancel();
        }
    }

    /// 
    /// Pause a timer. The main benefit of this over the method on the instance is that you will not get
    /// a  if the timer is null.
    /// 
    /// The timer to pause.
    public static void Pause(Timer timer)
    {
        if (timer != null)
        {
            timer.Pause();
        }
    }

    /// 
    /// Resume a timer. The main benefit of this over the method on the instance is that you will not get
    /// a  if the timer is null.
    /// 
    /// The timer to resume.
    public static void Resume(Timer timer)
    {
        if (timer != null)
        {
            timer.Resume();
        }
    }

    public static void CancelAllRegisteredTimers()
    {
        if (Timer._manager != null)
        {
            Timer._manager.CancelAllTimers();
        }

        // if the manager doesn't exist, we don't have any registered timers yet, so don't
        // need to do anything in this case
    }

    public static void PauseAllRegisteredTimers()
    {
        if (Timer._manager != null)
        {
            Timer._manager.PauseAllTimers();
        }

        // if the manager doesn't exist, we don't have any registered timers yet, so don't
        // need to do anything in this case
    }

    public static void ResumeAllRegisteredTimers()
    {
        if (Timer._manager != null)
        {
            Timer._manager.ResumeAllTimers();
        }

        // if the manager doesn't exist, we don't have any registered timers yet, so don't
        // need to do anything in this case
    }

    #endregion

    #region Public Methods

    /// 
    /// Stop a timer that is in-progress or paused. The timer's on completion callback will not be called.
    /// 
    public void Cancel()
    {
        if (this.isDone)
        {
            return;
        }

        this._timeElapsedBeforeCancel = this.GetTimeElapsed();
        this._timeElapsedBeforePause = null;
    }

    /// 
    /// Pause a running timer. A paused timer can be resumed from the same point it was paused.
    /// 
    public void Pause()
    {
        if (this.isPaused || this.isDone)
        {
            return;
        }

        this._timeElapsedBeforePause = this.GetTimeElapsed();
    }

    /// 
    /// Continue a paused timer. Does nothing if the timer has not been paused.
    /// 
    public void Resume()
    {
        if (!this.isPaused || this.isDone)
        {
            return;
        }

        this._timeElapsedBeforePause = null;
    }

    /// 
    /// Get how many seconds have elapsed since the start of this timer's current cycle.
    /// 
    /// The number of seconds that have elapsed since the start of this timer's current cycle, i.e.
    /// the current loop if the timer is looped, or the start if it isn't.
    ///
    /// If the timer has finished running, this is equal to the duration.
    ///
    /// If the timer was cancelled/paused, this is equal to the number of seconds that passed between the timer
    /// starting and when it was cancelled/paused.
    public float GetTimeElapsed()
    {
        if (this.isCompleted || this.GetWorldTime() >= this.GetFireTime())
        {
            return this.duration;
        }

        return this._timeElapsedBeforeCancel ??
               this._timeElapsedBeforePause ??
               this.GetWorldTime() - this._startTime;
    }

    /// 
    /// Get how many seconds remain before the timer completes.
    /// 
    /// The number of seconds that remain to be elapsed until the timer is completed. A timer
    /// is only elapsing time if it is not paused, cancelled, or completed. This will be equal to zero
    /// if the timer completed.
    public float GetTimeRemaining()
    {
        return this.duration - this.GetTimeElapsed();
    }

    /// 
    /// Get how much progress the timer has made from start to finish as a ratio.
    /// 
    /// A value from 0 to 1 indicating how much of the timer's duration has been elapsed.
    public float GetRatioComplete()
    {
        return this.GetTimeElapsed() / this.duration;
    }

    /// 
    /// Get how much progress the timer has left to make as a ratio.
    /// 
    /// A value from 0 to 1 indicating how much of the timer's duration remains to be elapsed.
    public float GetRatioRemaining()
    {
        return this.GetTimeRemaining() / this.duration;
    }

    #endregion

    #region Private Static Properties/Fields

    // responsible for updating all registered timers
    private static TimerManager _manager;

    #endregion

    #region Private Properties/Fields

    private bool isOwnerDestroyed
    {
        get { return this._hasAutoDestroyOwner && this._autoDestroyOwner == null; }
    }

    private readonly Action _onComplete;
    private readonly Action _onUpdate;
    private float _startTime;
    private float _lastUpdateTime;

    // for pausing, we push the start time forward by the amount of time that has passed.
    // this will mess with the amount of time that elapsed when we're cancelled or paused if we just
    // check the start time versus the current world time, so we need to cache the time that was elapsed
    // before we paused/cancelled
    private float? _timeElapsedBeforeCancel;
    private float? _timeElapsedBeforePause;

    // after the auto destroy owner is destroyed, the timer will expire
    // this way you don't run into any annoying bugs with timers running and accessing objects
    // after they have been destroyed
    private readonly MonoBehaviour _autoDestroyOwner;
    private readonly bool _hasAutoDestroyOwner;

    #endregion

    #region Private Constructor (use static Register method to create new timer)

    private Timer(float duration, Action onComplete, Action onUpdate,
        bool isLooped, bool usesRealTime, MonoBehaviour autoDestroyOwner)
    {
        this.duration = duration;
        this._onComplete = onComplete;
        this._onUpdate = onUpdate;

        this.isLooped = isLooped;
        this.usesRealTime = usesRealTime;

        this._autoDestroyOwner = autoDestroyOwner;
        this._hasAutoDestroyOwner = autoDestroyOwner != null;

        this._startTime = this.GetWorldTime();
        this._lastUpdateTime = this._startTime;
    }

    #endregion

    #region Private Methods

    private float GetWorldTime()
    {
        return this.usesRealTime ? Time.realtimeSinceStartup : Time.time;
    }

    private float GetFireTime()
    {
        return this._startTime + this.duration;
    }

    private float GetTimeDelta()
    {
        return this.GetWorldTime() - this._lastUpdateTime;
    }

    private void Update()
    {
        if (this.isDone)
        {
            return;
        }

        if (this.isPaused)
        {
            this._startTime += this.GetTimeDelta();
            this._lastUpdateTime = this.GetWorldTime();
            return;
        }

        this._lastUpdateTime = this.GetWorldTime();

        if (this._onUpdate != null)
        {
            this._onUpdate(this.GetTimeElapsed());
        }

        if (this.GetWorldTime() >= this.GetFireTime())
        {

            if (this._onComplete != null)
            {
                this._onComplete();
            }

            if (this.isLooped)
            {
                this._startTime = this.GetWorldTime();
            }
            else
            {
                this.isCompleted = true;
            }
        }
    }

    #endregion

    #region Manager Class (implementation detail, spawned automatically and updates all registered timers)

    /// 
    /// Manages updating all the s that are running in the application.
    /// This will be instantiated the first time you create a timer -- you do not need to add it into the
    /// scene manually.
    /// 
    private class TimerManager : MonoBehaviour
    {
        private List _timers = new List();

        // buffer adding timers so we don't edit a collection during iteration
        private List _timersToAdd = new List();

        public void RegisterTimer(Timer timer)
        {
            this._timersToAdd.Add(timer);
        }

        public void CancelAllTimers()
        {
            foreach (Timer timer in this._timers)
            {
                timer.Cancel();
            }

            this._timers = new List();
            this._timersToAdd = new List();
        }

        public void PauseAllTimers()
        {
            foreach (Timer timer in this._timers)
            {
                timer.Pause();
            }
        }

        public void ResumeAllTimers()
        {
            foreach (Timer timer in this._timers)
            {
                timer.Resume();
            }
        }

        // update all the registered timers on every frame
        [UsedImplicitly]
        private void Update()
        {
            this.UpdateAllTimers();
        }

        private void UpdateAllTimers()
        {
            if (this._timersToAdd.Count > 0)
            {
                this._timers.AddRange(this._timersToAdd);
                this._timersToAdd.Clear();
            }

            foreach (Timer timer in this._timers)
            {
                timer.Update();
            }

            this._timers.RemoveAll(t => t.isDone);
        }
    }

    #endregion

}


你可能感兴趣的:(Unity 一个好用的延时脚本Timer,比协程好用多了~)