@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.
115 lines (99 loc) • 4.09 kB
text/typescript
import { Clock } from 'three'
import { type ITime } from './engine_types.js';
import { getParam } from './engine_utils.js';
const timescaleUrl = getParam("timescale");
let timeScale = 1;
if (typeof timescaleUrl === "number") timeScale = timescaleUrl;
/**
* Provides time-related information for frame-based game logic.
* Access via `this.context.time` from any component.
*
* @example Using deltaTime for frame-rate independent movement
* ```ts
* update() {
* // Move 1 unit per second regardless of frame rate
* this.gameObject.position.x += 1 * this.context.time.deltaTime;
* }
* ```
* @example Checking elapsed time
* ```ts
* start() {
* console.log(`Time since start: ${this.context.time.time}s`);
* console.log(`Current frame: ${this.context.time.frameCount}`);
* }
* ```
*/
export class Time implements ITime {
/** The time in seconds since the start of Needle Engine. */
get time() { return this._time; }
private set time(value: number) { this._time = value; }
private _time = 0;
/** The time in seconds it took to complete the last frame (Read Only). */
get deltaTime() { return this._deltaTime; }
private set deltaTime(value: number) { this._deltaTime = value; }
private _deltaTime = 0;
/** The time in seconds it took to complete the last frame (Read Only). Timescale is not applied. */
get deltaTimeUnscaled() { return this._deltaTimeUnscaled; }
private _deltaTimeUnscaled = 0;
/**
* The scale at which time passes. Default is 1.
* - Values < 1 create slow motion (e.g. 0.5 = half speed)
* - Values > 1 speed up time (e.g. 2 = double speed)
* - Value of 0 effectively pauses time-dependent logic
*/
timeScale = 1;
/** same as frameCount */
get frame() { return this._frame; }
private set frame(value: number) { this._frame = value; }
private _frame = 0;
/** The total number of frames that have passed (Read Only). Same as frame */
get frameCount() { return this.frame; }
/** The time in seconds it took to complete the last frame (Read Only). */
get realtimeSinceStartup(): number {
return this.clock.elapsedTime;
}
/**
* @returns {Number} FPS for this frame.
* Note that this returns the raw value (e.g. 59.88023952362959) and will fluctuate a lot between frames.
* If you want a more stable FPS, use `smoothedFps` instead.
*/
get fps() {
return 1 / this.deltaTime;
}
/**
* Approximated frames per second
* @returns the smoothed FPS value over the last 60 frames with decimals.
*/
get smoothedFps() { return this._smoothedFps; }
/** The smoothed time in seconds it took to complete the last frame (Read Only). */
get smoothedDeltaTime() { return 1 / this._smoothedFps; }
private clock = new Clock();
private _smoothedFps: number = 0;
private _smoothedDeltaTime: number = 0;
private readonly _fpsSamples: number[] = [];
private _fpsSampleIndex: number = 0;
constructor() {
if (typeof timeScale === "number")
this.timeScale = timeScale;
}
/** Step the time. This is called automatically by the Needle Engine Context.
* @internal
*/
update() {
this.deltaTime = this.clock.getDelta();
// clamp delta time because if tab is not active clock.getDelta can get pretty big
this.deltaTime = Math.min(.1, this.deltaTime);
this._deltaTimeUnscaled = this.deltaTime;
if (this.deltaTime <= 0) this.deltaTime = 0.000000000001;
this.deltaTime *= this.timeScale;
this.frame += 1;
this.time += this.deltaTime;
if (this._fpsSamples.length < 60) this._fpsSamples.push(this.deltaTime);
else this._fpsSamples[(this._fpsSampleIndex++) % 60] = this.deltaTime;
let sum = 0;
for (let i = 0; i < this._fpsSamples.length; i++)
sum += this._fpsSamples[i];
this._smoothedDeltaTime = sum / this._fpsSamples.length;
this._smoothedFps = 1 / this._smoothedDeltaTime;
}
}