Git Product home page Git Product logo

unitytimer's Introduction

Unity Timer

Run actions after a delay in Unity3D.

This library has been battle-tested and hardened throughout numerous projects, including the award-winning Pitfall Planet.

Written by Alexander Biggs + Adam Robinson-Yu.

Installation

To get the latest release of UnityTimer, head over to the Releases page and download the Timer.unitypackage file from the latest release. Then if you have a Unity project open, you can open the .unitypackage file to install the scripts into your project.

Alternatively, if you like to live on the bleeding edge, add https://github.com/akbiggs/UnityTimer.git to your packages in Unity Package Manager. However, we do not guarantee this will give you a stable version.

Screenshot of selecting add git URL Screenshot of adding package to UPM

Basic Example

The Unity Timer package provides the following method for creating timers:

/// <summary>
/// Register a new timer that should fire an event after a certain amount of time
/// has elapsed.
/// </summary>
/// <param name="duration">The time to wait before the timer should fire, in seconds.</param>
/// <param name="onComplete">An action to fire when the timer completes.</param>
public static Timer Register(float duration, Action onComplete);

The method is called like this:

// Log "Hello World" after five seconds.

Timer.Register(5f, () => Debug.Log("Hello World"));

Motivation

Out of the box, without this library, there are two main ways of handling timers in Unity:

  1. Use a coroutine with the WaitForSeconds method.
  2. Store the time that your timer started in a private variable (e.g. startTime = Time.time), then check in an Update call if Time.time - startTime >= timerDuration.

The first method is verbose, forcing you to refactor your code to use IEnumerator functions. Furthermore, it necessitates having access to a MonoBehaviour instance to start the coroutine, meaning that solution will not work in non-MonoBehaviour classes. Finally, there is no way to prevent WaitForSeconds from being affected by changes to the time scale.

The second method is error-prone, and hides away the actual game logic that you are trying to express.

This library alleviates both of these concerns, making it easy to add an easy-to-read, expressive timer to any class in your Unity project.

Features

Make a timer repeat by setting isLooped to true.

// Call the player's jump method every two seconds.

Timer.Register(2f, player.Jump, isLooped: true);

Cancel a timer after calling it.

Timer timer;

void Start() {
   timer = Timer.Register(2f, () => Debug.Log("You won't see this text if you press X."));
}

void Update() {
   if (Input.GetKeyDown(KeyCode.X)) {
      Timer.Cancel(timer);
   }
}

Measure time by realtimeSinceStartup instead of scaled game time by setting useRealTime to true.

// Let's say you pause your game by setting the timescale to 0.
Time.timeScale = 0f;

// ...Then set useRealTime so this timer will still fire even though the game time isn't progressing.
Timer.Register(1f, this.HandlePausedGameState, useRealTime: true);

Attach the timer to a MonoBehaviour so that the timer is destroyed when the MonoBehaviour is.

Very often, a timer called from a MonoBehaviour will manipulate that behaviour's state. Thus, it is common practice to cancel the timer in the OnDestroy method of the MonoBehaviour. We've added a convenient extension method that attaches a Timer to a MonoBehaviour such that it will automatically cancel the timer when the MonoBehaviour is detected as null.

public class CoolMonoBehaviour : MonoBehaviour {

   void Start() {
      // Use the AttachTimer extension method to create a timer that is destroyed when this
      // object is destroyed.
      this.AttachTimer(5f, () => {
      
         // If this code runs after the object is destroyed, a null reference will be thrown,
         // which could corrupt game state.
         this.gameObject.transform.position = Vector3.zero;
      });
   }
   
   void Update() {
      // This code could destroy the object at any time!
      if (Input.GetKeyDown(KeyCode.X)) {
         GameObject.Destroy(this.gameObject);
      }
   }
}

Update a value gradually over time using the onUpdate callback.

// Change a color from white to red over the course of five seconds.
Color color = Color.white;
float transitionDuration = 5f;

Timer.Register(transitionDuration,
   onUpdate: secondsElapsed => color.r = 255 * (secondsElapsed / transitionDuration),
   onComplete: () => Debug.Log("Color is now red"));

A number of other useful features are included!

  • timer.Pause()
  • timer.Resume()
  • timer.GetTimeRemaining()
  • timer.GetRatioComplete()
  • timer.isDone

A test scene + script demoing all the features is included with the package in the Timer/Example folder.

Usage Notes / Caveats

  1. All timers are destroyed when changing scenes. This behaviour is typically desired, and it happens because timers are updated by a TimerController that is also destroyed when the scene changes. Note that as a result of this, creating a Timer when the scene is being closed, e.g. in an object's OnDestroy method, will result in a Unity error when the TimerController is spawned.

unitytimer's People

Contributors

adamgryu avatar akbiggs avatar djavadihamid avatar fcnaud avatar petethegoat avatar robinnorth avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

unitytimer's Issues

Timer does not exist in the current context

I tried installing from UPM, adding the remote .git as a package, and downloading the package and each time I was unable to run any demos. I looked in the source and can't figure out why since its a static class with an extension?

Unity 2021.2.7f1

image

image

image

Feature request: Restart

It would be useful in some special cases to be able to restart a running timer from the beginning. Maybe this functionality already exists? Here is my attempt, but maybe I missed something:

/// <summary>
/// Restarts a running or paused timer.
/// </summary>
public void Restart() 
{
    if (this.isDone) 
    {
        return;
    }

    this._timeElapsedBeforePause = null;
    this._startTime = this.GetWorldTime();
    this.isCompleted = false;
}

Persist Timer manager between scenes

In my current project I use timers to drive some nice presentation lerps and stuff while async loading the next scene.

This totally broke immediately because timers are cancelled on scene change, so I marked the initial manager as DontDestroyOnLoad and it works fine.
I think this is pretty useful functionality, so how would you feel about a pull req that implements this properly, with the manager manually cancelling all timers on scene changes, unless they're specifically marked to persist?

Manually initialize TimerManager

Some objects were not cleaned up when closing the scene. (Did you spawn new GameObjects from OnDestroy?)
The following scene GameObjects were found:
TimerManager

If the first time a timer is registered is in the OnDisable or OnDestroy function, then it may create the TimerManager as a scene is closed (for example, when exiting playmode)

I simply added a method to initialize it manually. This could also use RuntimeInitializeOnLoadMethod and occur with no user action required.

    /// <summary>
    /// Initializes the TimerManager, useful if your first Timer usage
    /// may otherwise be in OnDisable or OnDestroy, which may be called when exiting playmode.
    /// </summary>
    public static void InitializeManager()
	{
        if(Timer._manager == null)
        {
            TimerManager managerInScene = Object.FindObjectOfType<TimerManager>();
            if(managerInScene != null)
            {
                Timer._manager = managerInScene;
            }
            else
            {
                GameObject managerObject = new GameObject { name = "TimerManager" };
                Timer._manager = managerObject.AddComponent<TimerManager>();
                GameObject.DontDestroyOnLoad(managerObject);
            }
        }
    }

Pool Timers ?

Thanks for the incredible library.
I wanted to double check if timers are pooled & if there are any plans to pool them ?

Open UPM No valid Git tag

There is no valid Git tag detected in Open UPM. Therefore I can not install the package from Open UPM.

Is this compatible with editor scripts?

Hey there,
I have been trying to find something to give me some easy numeric incrementation in editor scripts and it seemed like this might just do the trick, but I am having a bit of an issue. My designated callback doesn't seem to actually get called when the timer is supposedly completed.

I was trying to test it out by having the following:

        private Timer displayTimer;
        private void OnInspectorUpdate()
        {
            if (displayTimer == null || displayTimer.isCompleted) return;
            Debug.Log(string.Format("Percentage completed: {0:F4}%", displayTimer.GetRatioComplete() * 100));
            Debug.Log(string.Format("Percentage remaining: {0:F4}%", displayTimer.GetRatioRemaining() * 100));
        }

        void BeginDisplayMsg()
        {
            displayTimer = Timer.Register(2, BeginFadeOut,
                secondsElapsed => { Debug.Log($"Timer ran update callback: {secondsElapsed:F2} seconds"); },
                false, true);
        }

It seems to run through the timer as it should based on these percentage complete messages and hits 100% but then sits there continually saying its at 100% instead of setting .isComplete or calling my callback.

Percentage completed: 91.2354%
Percentage remaining: 8.7402%
Percentage completed: 98.9746%
Percentage remaining: 1.0010%
(29) Percentage completed: 100.0000%
(29) Percentage remaining: 0.0000%

Is there something I am missing in order to use this in an editor window, or is there any sort of known compatibility issue that I missed? I tried to see if there was any mention of usage in the editor, but I didn't see any.

Thanks,
-MH

Optimize GC in Unity5.4.4

I found 40B GC in UpdateAllTimers() function of TimerManager class that was caused by foreach and my unity version is 5.4.4. However, all the lower versions have this issue as to my knowlege.
Before optimize:

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

After optimize

for (int i = 0; i < this._timers.Count; i++)
{
     this._timers[i].Update();
}

Might need PauseUpdate()

When the timer is paused, I find that it is hard to resume the timer by itself.

For example, when the game is paused, all timers could detect this in their Action<float> OnUpdate and they are paused automatically. However, when the game is resumed, it is hard to let the timer know this by itself so that it could resume by itself.

Additive Scenes

Hi!
I am using UnityTimer in the context of additive scenes. I have a main scene as a kind of manager scene that is never unloaded, but it cannot be the active scene most of the time. It needs to hold the TimerManager because the other scenes are all volatile. My workaround for now was to spawn a dummy timer at startup in the main scene and setting it to loop so it will always be there.

Would it be OK to make the TimerManager class public instead and add it manually from a different script in the main scene?

void Start()
{
    GameObject managerObject = new GameObject { name = "TimerManager" };
    managerObject.AddComponent<Timer.TimerManager>();
}

I made a little test and it seems to work, but I am not sure if there are going to be problems if TimerManager is public.

Thanks!
Jan

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.