Skip to content

Timer

The GameTimer provides pause-aware timing primitives for game logic. It offers one-shot delays, repeating intervals, and pollable cooldowns -- all operating in game-time milliseconds derived from the engine's delta time.

When the game is paused, all timers freeze automatically.

Accessing the Timer

typescript
const engine = await createGameEngine(canvas, entityClasses);
const timer = engine.timer;

The timer is engine-side, ticked once a rendered frame by a frame callback. It is not part of the entity system: a behavior does not reach it, and its callbacks run outside the fixed step.

API Reference

MethodSignatureReturnsDescription
delay(ms : number, callback : () => void)() => voidOne-shot timer; returns a cancel function
interval(ms : number, callback : () => void)() => voidRepeating timer; returns a cancel function
cooldown(ms : number)CooldownHandlePollable cooldown handle
cancelAll()voidCancel all active delays, intervals, and cooldowns

delay

Fires a callback once after ms milliseconds of game time. Returns a cancel function.

typescript
showHitIndicator();
const cancel = engine.timer.delay(200, () =>
{
    hideHitIndicator();
});

// Changed your mind? Cancel it.
cancel();

The ms parameter must be >= 0. Passing a negative value throws a RangeError.

interval

Fires a callback repeatedly every ms milliseconds of game time. Returns a cancel function.

typescript
// Regenerate 1 HP every 2 seconds
const cancel = engine.timer.interval(2000, () =>
{
    player.state.hp = Math.min(player.state.hp + 1, player.state.maxHp);
});

// Stop regeneration
cancel();

The ms parameter must be > 0 (strictly positive). Passing zero or a negative value throws a RangeError.

WARNING

An interval of 0 is not allowed because it would fire infinitely within a single frame. Use delay(0, ...) for a next-frame callback instead.

cooldown

Creates a pollable cooldown handle. Starts in the ready state. After calling reset(), the cooldown becomes not-ready until ms milliseconds of game time have elapsed.

typescript
const fireCooldown = engine.timer.cooldown(500);

function tryShoot() : void
{
    if(fireCooldown.ready)
    {
        shootProjectile();
        fireCooldown.reset(); // Starts the 500ms cooldown
    }
}

CooldownHandle

typescript
interface CooldownHandle
{
    readonly ready : boolean;
    reset() : void;
}
Property / MethodTypeDescription
readybooleantrue when the cooldown has elapsed (or has never been reset)
reset()() => voidStart (or restart) the cooldown timer

Cooldowns are passive -- they do not fire callbacks. Check ready whenever you need to gate an action. This makes them ideal for ability cooldowns, rate-limiting player actions, or any "can I do this yet?" check.

cancelAll

Clears all active delays, intervals, and cooldowns:

typescript
engine.timer.cancelAll();

Nothing calls this for you. A game that wants its timers cleared on a level transition calls it from the transition.

Any CooldownHandle references you are still holding will report ready: true after cancelAll() is called.

Game Time vs. Wall Time

All durations are measured in game-time milliseconds. The timer advances by the engine's delta time each frame via an internal tick(dtMs) call. Key consequences:

BehaviorDetail
Pausing freezes all timersWhen the game is paused, tick() is not called -- delays, intervals, and cooldowns all freeze
Frame rate affects granularityA 500ms delay fires on the first frame where accumulated time >= 500ms. At 60fps (~16.7ms/frame), that is within one frame of accuracy.
No built-in time scaleThere is no time multiplier. Slow-motion or fast-forward would need to be implemented at the engine loop level.

Usage Patterns

Timed Power-Up

typescript
function activateShield(player : PlayerEntity) : void
{
    player.emit('action:shield', { active: true }, player.id);

    engine.timer.delay(5000, () =>
    {
        player.emit('action:shield', { active: false }, player.id);
    });
}

The callback lands between fixed steps, so it emits an event for the entity's own behavior to apply at the next drain rather than writing the entity's state from outside.

Spawn Wave Timer

typescript
let wave = 0;

const cancelWaves = engine.timer.interval(10000, () =>
{
    wave++;
    spawnEnemyWave(wave);

    if(wave >= maxWaves)
    {
        cancelWaves();
    }
});

Rate-Limited Action

typescript
const dashCooldown = engine.timer.cooldown(3000);
let dashesHandled = 0;

engine.managers.gameManager.registerFrameCallback(() =>
{
    if(player.state.dashRequested > dashesHandled && dashCooldown.ready)
    {
        performDash();
        dashCooldown.reset();
    }

    dashesHandled = player.state.dashRequested;
});

Here dashRequested is a counter a behavior increments on each action:dash event, and the frame callback polls it after the fixed step has run.

Released under the MIT License.