A Generic Duration Timer Class

In game development, there are a lot of instances where you would like to do the following:

  • Wait for X seconds
  • Fly for X seconds
  • Fire for X seconds
  • Move forward for X seconds

The common element in these actions is the concept of a time duration. The most common solution that I see in tutorial pages looks like the following:

public class SomeComponent : MonoBehaviour {
    [SerializeField]
    private float durationTime; // the time to wait or go through

    private float polledTime;

    void Awake() {
        this.polledTime = 0;
    }

    void Update() {
        this.polledTime += Time.deltaTime;

        if(this.polledTime >= this.durationTime) {
            // time's up
            // maybe reset or do something since duration is up
            return;
        }

        float ratio = this.polledTime / this.durationTime;
        // do something with ratio like interpolation (lerp)
    }
}

This is good enough, but we can do something better. Imagine if you are maintaining more than one duration time. You’d probably do something like this:

public class SomeComponent : MonoBehaviour {
    [SerializeField]
    private float waitDurationTime;

    private float waitPolledTime;

    [SerializeField]
    private float fireDurationTime;

    private float firePolledTime;

    void Update() {
        UpdateWait();
        UpdateFire();
    }
    
    private void UpdateWait() {
    	this.waitPolledTime += Time.deltaTime;

        if(this.waitPolledTime >= this.waitDurationTime) {
            // time's up
            // maybe reset or do something since duration is up
            return;
        }

        float ratio = this.waitPolledTime / this.waitDurationTime;
        // do something with ratio
    }
    
    private void UpdateFire() {
    	this.firePolledTime += Time.deltaTime;

        if(this.firePolledTime >= this.fireDurationTime) {
            // time's up
            // maybe reset or do something since duration is up
            return;
        }

        float ratio = this.firePolledTime / this.fireDurationTime;
        // do something with ratio
    }
}

We can see here that the floating point arithmetic is repeated for each timed duration domain. A simple solution to manage this is to refactor and create a reusable DurationTimer class.

using UnityEngine;

/**
 * Generic class for implementing timers (specified in seconds)
 */
public class DurationTimer {
    private float polledTime;
    private float durationTime;

    /**
     * Constructor with a specified duration time
     */
    public DurationTimer(float durationTime) {
        Reset(durationTime);
    }

    /**
     * Updates the timer
     */
    public void Update() {
        this.polledTime += Time.deltaTime;
    }

    /**
     * Resets the timer
     */
    public void Reset() {
        this.polledTime = 0;
    }

    /**
     * Resets the timer and assigns a new duration time
     */
    public void Reset(float durationTime) {
        Reset();
        this.durationTime = durationTime;
    }

    /**
     * Returns whether or not the timed duration has elapsed
     */
    public bool HasElapsed() {
        return Comparison.TolerantGreaterThanOrEquals(this.polledTime, this.durationTime);
    }

    /**
     * Returns the ratio of polled time to duration time. Returned value is 0 to 1 only
     */
    public float GetRatio() {
        if(Comparison.TolerantLesserThanOrEquals(this.durationTime, 0)) {
            // bad duration time value
            // if countdownTime is zero, ratio will be infinity (divide by zero)
            // we just return 1.0 here for safety
            return 1.0f;
        }

        float ratio = this.polledTime / this.durationTime;
        return Mathf.Clamp(ratio, 0, 1);
    }

    /**
     * Returns the polled time since it started
     */
    public float GetPolledTime() {
        return this.polledTime;
    }

    /**
     * Forces the timer to end
     */
    public void EndTimer() {
        this.polledTime = this.durationTime;
    }

    /**
     * Returns the durationTime
     */
    public float GetDurationTime() {
        return this.durationTime;
    }

}

What we did here is we simply contained the duration timer variables and routines in a separate class. Take note of the usage of Comparison functions in HasElapsed() and GetRatio(). Read here for the reason why.

Let’s look at the basic usage using of this class:

public class SomeComponent : MonoBehaviour {
    [SerializeField]
    private float duration; // the time to wait or go through

    private DurationTimer timer;

    void Awake() {
        this.timer = new DurationTimer(this.duration);
    }

    void Update() {
        this.timer.Update();

        if(this.timer.HasElapsed()) {
            // time's up
            // maybe reset or do something since duration is up
            return;
        }

        float ratio = this.timer.GetRatio();
        // do something with ratio like interpolation (lerp)
    }
}

By using this class, we reduce the clutter of floating point arithmetic (increment, check, compute ratio). We use the functions of the class instead, making it more readable. See how it looks like when it’s used for more than one timed duration:

public class SomeComponent : MonoBehaviour {
    [SerializeField]
    private float waitDuration; // the time to wait or go through

    private DurationTimer waitTimer;
    
    [SerializeField]
    private float fireDuration;
    
    private DurationTimer fireTimer;

    void Awake() {
        this.waitTimer = new DurationTimer(this.waitDuration);
        this.fireTimer = new DurationTimer(this.fireDuration);
    }

    void Update() {
        UpdateWait();
        UpdateFire();
    }
    
    private void UpdateWait() {
    	this.waitTimer.Update();

        if(this.waitTimer.HasElapsed()) {
            // time's up
            // maybe reset or do something since duration is up
            return;
        }

        float ratio = this.waitTimer.GetRatio();
        // do something with ratio like interpolation (lerp)
    }
    
    private void UpdateFire() {
    	this.fireTimer.Update();

        if(this.fireTimer.HasElapsed()) {
            // time's up
            // maybe reset or do something since duration is up
            return;
        }

        float ratio = this.fireTimer.GetRatio();
        // do something with ratio like interpolation (lerp)
    }
}

We can also reuse this class if we’re making other frameworks that uses time.

public class WaitAction : FsmAction {
    private DurationTimer timer; // pretty cool huh?
	
    ...
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s