Sponsored By

Lovers in a Dangerous Spacetime DevLog #11: Timing Is Everything

Implementing an easy to use coroutine-based timer in Unity.

Adam Winkels, Blogger

September 18, 2014

3 Min Read
Game Developer logo in a gray background | Game Developer

gif_ceraf

The Ceraf enemy uses timers to control its pre-shoot, shoot and post-shoot animations and actions.

 

One of the tasks we find ourselves doing quite frequently while working on Lovers in a Dangerous Spacetime is controlling the timing of things (loop an animation for x seconds, randomize AI behaviour every y seconds, etc.). There are many ways to accomplish these types of actions, for instance you could do something like this:

float timerLength = 1;
void Update()
{
  if(timerLength > 0)
  {
    timerLength -= Time.deltaTime;
    if(timerLength <= 0)
    {
      doSomeAction();
    }
  }
}

This works in the simplest cases, but it requires you to reuse the same code in any script that uses a timer and quickly becomes cumbersome if you require multiple timers in a script. You could use a coroutine with Unity's handy built-in wait function:

void Start()
{
  StartCoroutine(timerCoroutine());
}

IEnumerator timerCoroutine()
{
  yield return new WaitForSeconds(1);
  doSomeAction();
}

Coroutines are great for when you want a long sequence of actions or when you need to do something over a number of frames, but if you just want to wait for a certain amount time before performing an action, having to write a new method is tedious (plus, until recently they were very difficult to cancel). To get around these issues and help satisfy our laziness we created a class that to encapsulate the functionality of a countdown timer: CoroutineTimer.

Click to view Gist

As its name implies, CoroutineTimer utilizes Unity's coroutine library to provide a straightforward timer mechanism. In its simplest form, CoroutineTimer acts a straightforward timer:

CoroutineTimer timer = new CoroutineTimer(timerLength);
timer.Start(gameObject, doSomeAction);

After timerLength seconds, the doSomeAction method will be called. (Note that you must supply a GameObject to the timer's Start method as coroutines can only be run by MonoBehaviours and CoroutineTimer attaches a new MonoBehaviour to the supplied GameObject when it runs.) We've also included additional functionality to CoroutineTimer that comes in handy relatively frequently. For instance, say you wanted an enemy to shoot every 2 seconds, but to only start shooting initially after 4.5 seconds have passed. Also, you realize that it looks pretty mechanical if the enemy shoots *exactly* every 2 seconds, so you want to randomize the behaviour a bit so it actually only shoots every 1.8-2.2 seconds (i.e. a 10% randomization). Sure thing, no problem:

float length = 2f;
float randomizationFactor = 0.1f;
float startDelay = 4.5f;
bool repeat = true;
CoroutineTimer timer = new CoroutineTimer(length, randomizationFactor, startDelay, repeat);
timer.Start(gameObject, shoot);

CoroutineTimers can also be cancelled at any point (using the Stop() method) or reused once they are stopped finished. Additionally, it uses the [System.Serializable] attribute, so its properties can be serialized and exposed in the Unity Editor.

coroutine-timer-serialized

Limitations & peculiarities
Unfortunately there is no way to check how much time is left in a CoroutineTimer, you merely start it and it lets you know when it's done.

CoroutineTimer uses the string-based method of starting and stopping its coroutines. We're generally not big fans of this approach, but until Unity 4.5 it was the only way to easily cancel a running coroutine. Now that the StopCoroutine method can take an IEnumerator, we will likely update the class in the future to use that instead. This change would also negate the need to pass a GameObject to the timer (we are attaching a new MonoBehaviour for the timer only for safety since StopCoroutine(someString) stops all coroutine methods named someString on a given MonoBehaviour) and we could instead simply pass a MonoBehaviour.

Original post

// Adam Winkels (@winkels) is a co-founder of Asteroid Base.

Read more about:

Featured Blogs
Daily news, dev blogs, and stories from Game Developer straight to your inbox

You May Also Like