Search code examples
c#unity-game-enginetimedelay

What are ways to control the time delay in Unity?


I've looked around and apparently I've got the choice between these libraries/ solutions:

One:

public void Awake() {
    Invoke("InvokeAndPrint", 2);
}

void InvokeAndPrint() {
    print("InvokeAndPrint 2");
}

Two:

void Start() {
    StartCoroutine(WaitAndPrint(2.0F));
}

IEnumerator WaitAndPrint(float waitTime) {
    yield return new WaitForSeconds(waitTime);
    print("WaitAndPrint " + Time.time);
}

I want to know if there is any other better way?


Solution


  • This is a summary of my comments above


    The only other method I can think of is old-school take note of the time at start; then in your Update() method check for elapsed time. You essentially do everything yourself. Though way more verbose than the above examples, it's type-safe and does not require any extra threads or thread job objects.

    Simplest

    First we need some fields defined:

    private DateTime _start;
    private bool _done;
    

    In your start take note of the time:

    void Start()
    {
        _start = DateTime.Now;
    }
    

    ...then in your update check to see how much time has elapsed. If its greater than your timeout of say 2 seconds, fire off what-ever you wish to do - in this case print():

    void Update()
    {
        if (! _done && (DateTime.Now - _start).TotalSeconds >= 2)
        {
            print("hello world");
            _done = true;
        }
    }
    

    That's it.

    Re-usable Code

    You'll probably find that there are many places where there is a need for this so wouldn't it be groovy if there was a way to cut down on repeated code. Perhaps a class to wrap it up in?

    class DelayedJob
    {
        private readonly TimeSpan _delay;
        private readonly Action _action;
        private readonly DateTime _start;
    
        public DelayedJob(TimeSpan delay, Action action)
        {
            if (action == null)
            {
                throw new ArgumentNullException("action");
            }
            _delay = delay;
            _action = action;
            _start = DateTime.Now;
        }
    
        /// <summary>
        /// Updates this instance.
        /// </summary>
        /// <returns>true if there is more work to do, false otherwise</returns>
        public bool Update()
        {
            if (DateTime.Now - _start >= _delay)
            {
                _action();
                return false;
            }
    
            return true;
        }
    }
    

    Then you could do something like this:

    void Start()
    {
        _job = new DelayedJob(TimeSpan.FromSeconds(2), ()=> print("hello"));
    }
    

    ...after updating your Update() accordingly:

    void Update()
    {
        if (_job != null && !_job.Update())
        {
            _job = null;
        }
    }
    

    Multiple Jobs

    It's just a matter of placing them in a collection and processing it at runtime.

    private List<DelayedJob> _jobs; 
    
    void Start()
    {
        _jobs = new List<DelayedJob>
                {
                    new DelayedJob(TimeSpan.FromSeconds(2), () => print("star wars")),
                    new DelayedJob(TimeSpan.FromSeconds(3f), () => print("is coming!"))
                };
    }
    

    ...a few alterations to Update():

    void Update()
    {    
        bool again;
    
        do
        {
            again = false;
            // you probably want to optimise this so that we don't check the same items
            // at the start again after removing an item
            foreach (var delayedJob in _jobs)
            {
                if (!delayedJob.Update())
                {
                    _jobs.Remove(delayedJob);
                    again = true; // start over
                    break;
                }
            }
        }
        while (again);    
    }