Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/excaliburjs/Excalibur. 🎮 Your friendly TypeScript 2D game engine for the web 🗡️ excaliburjs.com
excalibur excaliburjs game-development game-engine game-framework gamedev games html5-canvas typescript
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304import type { Scene } from './scene';import { Logger } from './util/log';import { Random } from './math/random';import { EventEmitter } from './event-emitter';
/** * Built in events supported by all entities */export interface TimerEvents { start: void; stop: void; pause: void; resume: void; cancel: void;
action: void; complete: void;}
export const TimerEvents = { Start: 'start', Stop: 'stop', Pause: 'pause', Resume: 'resume', Cancel: 'cancel',
Action: 'action', Complete: 'complete'} as const;
export interface TimerOptions { /** * If true the timer repeats every interval infinitely */ repeats?: boolean; /** * If a number is specified then it will only repeat a number of times */ numberOfRepeats?: number; /** * Action to perform every time the timer fires */ action?: () => void; /** * Interval in milliseconds for the timer to fire */ interval: number; /** * Optionally specify a random range of milliseconds for the timer to fire */ randomRange?: [number, number]; /** * Optionally provide a random instance to use for random behavior, otherwise a new random will be created seeded from the current time. */ random?: Random; /** * Optionally provide a callback to fire once when the timer completes its last action callback. */ onComplete?: () => void;}
/** * The Excalibur timer hooks into the internal timer and fires callbacks, * after a certain interval, optionally repeating. */export class Timer { private _logger = Logger.getInstance(); private static _MAX_ID: number = 0; public id: number = 0; public events = new EventEmitter<TimerEvents>();
private _elapsedTime: number = 0; private _totalTimeAlive: number = 0;
private _running = false;
private _numberOfTicks: number = 0; private _callbacks: Array<() => void>;
public interval: number = 10; public repeats: boolean = false; public maxNumberOfRepeats: number = -1; public randomRange: [number, number] = [0, 0]; public random!: Random; private _baseInterval = 10; private _generateRandomInterval = () => { return this._baseInterval + this.random.integer(this.randomRange[0], this.randomRange[1]); };
// eslint-disable-next-line @typescript-eslint/no-empty-function private _onComplete: () => void = () => {}; private _complete = false; public get complete() { return this._complete; }
public scene!: Scene;
constructor(options: TimerOptions) { const fcn = options.action; const interval = options.interval; const repeats = options.repeats; const numberOfRepeats = options.numberOfRepeats; const randomRange = options.randomRange; const random = options.random; this._onComplete = options.onComplete ?? this._onComplete;
if (!!numberOfRepeats && numberOfRepeats >= 0) { this.maxNumberOfRepeats = numberOfRepeats; if (!repeats) { throw new Error('repeats must be set to true if numberOfRepeats is set'); } }
this.id = Timer._MAX_ID++; this._callbacks = []; this._baseInterval = this.interval = interval; if (!!randomRange) { if (randomRange[0] > randomRange[1]) { throw new Error('min value must be lower than max value for range'); } //We use the instance of ex.Random to generate the range this.random = random ?? new Random(); this.randomRange = randomRange;
this.interval = this._generateRandomInterval(); this.on(() => { this.interval = this._generateRandomInterval(); }); } this.repeats = repeats || this.repeats; if (fcn) { this.on(fcn); } }
/** * Adds a new callback to be fired after the interval is complete * @param action The callback to be added to the callback list, to be fired after the interval is complete. */ public on(action: () => void) { this._callbacks.push(action); }
/** * Removes a callback from the callback list to be fired after the interval is complete. * @param action The callback to be removed from the callback list, to be fired after the interval is complete. */ public off(action: () => void) { const index = this._callbacks.indexOf(action); if (index > -1) { this._callbacks.splice(index, 1); } } /** * Updates the timer after a certain number of milliseconds have elapsed. This is used internally by the engine. * @param elapsed Number of elapsed milliseconds since the last update. */ public update(elapsed: number) { if (this._running) { this._totalTimeAlive += elapsed; this._elapsedTime += elapsed;
if (this.maxNumberOfRepeats > -1 && this._numberOfTicks >= this.maxNumberOfRepeats) { this._complete = true; this._running = false; this._elapsedTime = 0; this._onComplete(); this.events.emit('complete'); }
if (!this.complete && this._elapsedTime >= this.interval) { this._callbacks.forEach((c) => { c.call(this); }); this._numberOfTicks++; this.events.emit('action'); if (this.repeats) { this._elapsedTime = 0; } else { this._complete = true; this._running = false; this._elapsedTime = 0; this._onComplete(); this.events.emit('complete'); } } } }
/** * Resets the timer so that it can be reused, and optionally reconfigure the timers interval. * * Warning** you may need to call `timer.start()` again if the timer had completed * @param newInterval If specified, sets a new non-negative interval in milliseconds to refire the callback * @param newNumberOfRepeats If specified, sets a new non-negative upper limit to the number of time this timer executes */ public reset(newInterval?: number, newNumberOfRepeats?: number) { if (!!newInterval && newInterval >= 0) { this._baseInterval = this.interval = newInterval; }
if (newNumberOfRepeats !== undefined && newNumberOfRepeats >= 0) { this.maxNumberOfRepeats = newNumberOfRepeats; if (!this.repeats) { throw new Error('repeats must be set to true if numberOfRepeats is set'); } }
this._complete = false; this._elapsedTime = 0; this._numberOfTicks = 0; }
public get timesRepeated(): number { return this._numberOfTicks; }
public getTimeRunning(): number { return this._totalTimeAlive; }
/** * @returns milliseconds until the next action callback, if complete will return 0 */ public get timeToNextAction() { if (this.complete) { return 0; } return this.interval - this._elapsedTime; }
/** * @returns milliseconds elapsed toward the next action */ public get timeElapsedTowardNextAction() { return this._elapsedTime; }
public get isRunning() { return this._running; }
/** * Pauses the timer, time will no longer increment towards the next call */ public pause(): Timer { this._running = false; this.events.emit('pause'); return this; }
/** * Resumes the timer, time will now increment towards the next call. */ public resume(): Timer { this._running = true; this.events.emit('resume'); return this; }
/** * Starts the timer, if the timer was complete it will restart the timer and reset the elapsed time counter */ public start(): Timer { if (!this.scene) { this._logger.warn('Cannot start a timer not part of a scene, timer wont start until added'); }
this._running = true; if (this.complete) { this._complete = false; this._elapsedTime = 0; this._numberOfTicks = 0; } else { this.events.emit('start'); }
return this; }
/** * Stops the timer and resets the elapsed time counter towards the next action invocation */ public stop(): Timer { this._running = false; this._elapsedTime = 0; this._numberOfTicks = 0; this.events.emit('stop'); return this; }
/** * Cancels the timer, preventing any further executions. */ public cancel() { this.pause(); if (this.scene) { this.scene.cancelTimer(this); this.events.emit('cancel'); } }}