fixed fps tracking in game loop

This commit is contained in:
Joe Weaver 2020-10-21 06:38:03 -04:00
parent d9a87b2727
commit 15dd74f45e
5 changed files with 97 additions and 56 deletions

View File

@ -32,7 +32,7 @@ export default class BoidDemo extends Scene {
this.viewport.enableZoom(); this.viewport.enableZoom();
// Create a bunch of boids // Create a bunch of boids
for(let i = 0; i < 100; i++){ for(let i = 0; i < 200; i++){
let boid = this.add.graphic(Boid, layer, new Vec2(this.worldSize.x*Math.random(), this.worldSize.y*Math.random())); let boid = this.add.graphic(Boid, layer, new Vec2(this.worldSize.x*Math.random(), this.worldSize.y*Math.random()));
boid.fb = new FlockBehavior(this, boid, this.boids, 75, 50); boid.fb = new FlockBehavior(this, boid, this.boids, 75, 50);
boid.setSize(5, 5); boid.setSize(5, 5);

View File

@ -45,12 +45,25 @@ export default class QuadTree<T extends Region & Unique> implements Collection {
this.capacity = capacity ? capacity : 10; this.capacity = capacity ? capacity : 10;
// If we're at the bottom of the tree, don't set a max size // If we're at the bottom of the tree, don't set a max size
if(this.maxDepth === 0){ if(this.maxDepth === 1){
this.capacity = Infinity; this.capacity = Infinity;
} }
this.divided = false; this.divided = false;
this.items = new Array(); this.items = new Array();
// Create all of the children for this quadtree if there are any
if(this.maxDepth > 1){
let x = this.boundary.x;
let y = this.boundary.y;
let hw = this.boundary.hw;
let hh = this.boundary.hh;
this.nw = new QuadTree(new Vec2(x-hw/2, y-hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1, this.capacity);
this.ne = new QuadTree(new Vec2(x+hw/2, y-hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1, this.capacity);
this.sw = new QuadTree(new Vec2(x-hw/2, y+hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1, this.capacity);
this.se = new QuadTree(new Vec2(x+hw/2, y+hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1, this.capacity);
}
} }
/** /**
@ -71,7 +84,6 @@ export default class QuadTree<T extends Region & Unique> implements Collection {
// We aren't divided, but are at capacity - divide // We aren't divided, but are at capacity - divide
this.subdivide(); this.subdivide();
this.deferInsert(item); this.deferInsert(item);
this.divided = true;
} }
} }
} }
@ -173,16 +185,7 @@ export default class QuadTree<T extends Region & Unique> implements Collection {
* Divides this quadtree up into 4 smaller ones - called through insert. * Divides this quadtree up into 4 smaller ones - called through insert.
*/ */
protected subdivide(): void { protected subdivide(): void {
let x = this.boundary.x; this.divided = true;
let y = this.boundary.y;
let hw = this.boundary.hw;
let hh = this.boundary.hh;
this.nw = new QuadTree(new Vec2(x-hw/2, y-hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1, this.capacity);
this.ne = new QuadTree(new Vec2(x+hw/2, y-hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1, this.capacity);
this.sw = new QuadTree(new Vec2(x-hw/2, y+hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1, this.capacity);
this.se = new QuadTree(new Vec2(x+hw/2, y+hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1, this.capacity);
this.distributeItems(); this.distributeItems();
} }
@ -239,11 +242,16 @@ export default class QuadTree<T extends Region & Unique> implements Collection {
} }
} }
/**
* Clear the items in this quadtree
*/
clear(): void { clear(): void {
delete this.nw; if(this.nw){
delete this.ne; this.nw.clear();
delete this.sw; this.ne.clear();
delete this.se; this.sw.clear();
this.se.clear();
}
for(let item in this.items){ for(let item in this.items){
delete this.items[item]; delete this.items[item];

View File

@ -9,26 +9,46 @@ import SceneManager from "../Scene/SceneManager";
import AudioManager from "../Sound/AudioManager"; import AudioManager from "../Sound/AudioManager";
export default class GameLoop { export default class GameLoop {
// The amount of time to spend on a physics step /** The max allowed update fps.*/
private maxFPS: number; private maxUpdateFPS: number;
/** The timestep for each update. This is the deltaT passed to update calls. */
private simulationTimestep: number; private simulationTimestep: number;
// The time when the last frame was drawn /** The amount of time we are yet to simulate. */
private lastFrameTime: number; private frameDelta: number;
// The current frame of the game /** The time when the last frame was drawn. */
private lastFrameTime: number;
/** The minimum time we want to wait between game frames. */
private minFrameDelay: number;
/** The current frame of the game. */
private frame: number; private frame: number;
// Keeping track of the fps /** The actual fps of the game. */
private runningFrameSum: number; private fps: number;
private numFramesInSum: number;
private maxFramesInSum: number;
private fps: number;
private started: boolean; /** The time between fps measurement updates. */
private running: boolean; private fpsUpdateInterval: number;
private frameDelta: number;
/** The time of the last fps update. */
private lastFpsUpdate: number;
/** The number of frames since the last fps update was done. */
private framesSinceLastFpsUpdate: number;
/** The status of whether or not the game loop has started. */
private started: boolean;
/** The status of whether or not the game loop is currently running. */
private running: boolean;
/** The panic state of the game. True if we have too many update frames in a single render. */
private panic: boolean; private panic: boolean;
/** The number of update steps this iteration of the game loop. */
private numUpdateSteps: number; private numUpdateSteps: number;
// Game canvas and its width and height // Game canvas and its width and height
@ -51,16 +71,23 @@ export default class GameLoop {
// Typecast the config object to a GameConfig object // Typecast the config object to a GameConfig object
let gameConfig = config ? <GameConfig>config : new GameConfig(); let gameConfig = config ? <GameConfig>config : new GameConfig();
this.maxFPS = 60; this.maxUpdateFPS = 60;
this.simulationTimestep = Math.floor(1000/this.maxFPS); this.simulationTimestep = Math.floor(1000/this.maxUpdateFPS);
this.frameDelta = 0;
this.lastFrameTime = 0;
this.minFrameDelay = 0;
this.frame = 0; this.frame = 0;
this.runningFrameSum = 0; this.fps = this.maxUpdateFPS; // Initialize the fps to the max allowed fps
this.numFramesInSum = 0; this.fpsUpdateInterval = 1000;
this.maxFramesInSum = 30; this.lastFpsUpdate = 0;
this.fps = this.maxFPS; this.framesSinceLastFpsUpdate = 0;
this.started = false; this.started = false;
this.running = false; this.running = false;
this.panic = false;
this.numUpdateSteps = 0;
// Set the max fps to 60fps
// this.setMaxFPS(60);
// Get the game canvas and give it a background color // Get the game canvas and give it a background color
this.GAME_CANVAS = <HTMLCanvasElement>document.getElementById("game-canvas"); this.GAME_CANVAS = <HTMLCanvasElement>document.getElementById("game-canvas");
@ -103,9 +130,13 @@ export default class GameLoop {
* Changes the maximum allowed physics framerate of the game * Changes the maximum allowed physics framerate of the game
* @param initMax * @param initMax
*/ */
setMaxFPS(initMax: number): void { setMaxUpdateFPS(initMax: number): void {
this.maxFPS = initMax; this.maxUpdateFPS = initMax;
this.simulationTimestep = Math.floor(1000/this.maxFPS); this.simulationTimestep = Math.floor(1000/this.maxUpdateFPS);
}
setMaxFPS(maxFPS: number): void {
this.minFrameDelay = 1000/maxFPS;
} }
getSceneManager(): SceneManager { getSceneManager(): SceneManager {
@ -116,15 +147,10 @@ export default class GameLoop {
* Updates the frame count and sum of time for the framerate of the game * Updates the frame count and sum of time for the framerate of the game
* @param timestep * @param timestep
*/ */
private updateFrameCount(timestep: number): void { private updateFPS(timestamp: number): void {
this.frame += 1; this.fps = 0.9 * this.framesSinceLastFpsUpdate * 1000 / (timestamp - this.lastFpsUpdate) +(1 - 0.9) * this.fps;
this.numFramesInSum += 1; this.lastFpsUpdate = timestamp;
this.runningFrameSum += timestep; this.framesSinceLastFpsUpdate = 0;
if(this.numFramesInSum >= this.maxFramesInSum){
this.fps = 1000 * this.numFramesInSum / this.runningFrameSum;
this.numFramesInSum = 0;
this.runningFrameSum = 0;
}
Debug.log("fps", "FPS: " + this.fps.toFixed(1)); Debug.log("fps", "FPS: " + this.fps.toFixed(1));
} }
@ -150,6 +176,8 @@ export default class GameLoop {
this.render(); this.render();
this.lastFrameTime = timestamp; this.lastFrameTime = timestamp;
this.lastFpsUpdate = timestamp;
this.framesSinceLastFpsUpdate = 0;
window.requestAnimationFrame(this.doFrame); window.requestAnimationFrame(this.doFrame);
} }
@ -163,14 +191,22 @@ export default class GameLoop {
window.requestAnimationFrame(this.doFrame); window.requestAnimationFrame(this.doFrame);
// If we are trying to update too soon, return and do nothing // If we are trying to update too soon, return and do nothing
if(timestamp < this.lastFrameTime + this.simulationTimestep){ if(timestamp < this.lastFrameTime + this.minFrameDelay){
return return
} }
// Currently, update and draw are synced - eventually it would probably be good to desync these // Currently, update and draw are synced - eventually it would probably be good to desync these
this.frameDelta = timestamp - this.lastFrameTime; this.frameDelta += timestamp - this.lastFrameTime;
this.lastFrameTime = timestamp; this.lastFrameTime = timestamp;
// Update the estimate of the framerate
if(timestamp > this.lastFpsUpdate + this.fpsUpdateInterval){
this.updateFPS(timestamp);
}
this.frame++;
this.framesSinceLastFpsUpdate++;
// Update while we can (This will present problems if we leave the window) // Update while we can (This will present problems if we leave the window)
this.numUpdateSteps = 0; this.numUpdateSteps = 0;
while(this.frameDelta >= this.simulationTimestep){ while(this.frameDelta >= this.simulationTimestep){
@ -181,9 +217,6 @@ export default class GameLoop {
if(this.numUpdateSteps > 100){ if(this.numUpdateSteps > 100){
this.panic = true; this.panic = true;
} }
// Update the frame of the game
this.updateFrameCount(this.simulationTimestep);
} }
// Updates are done, draw // Updates are done, draw

View File

@ -95,7 +95,7 @@ export default class MainScene extends Scene {
let i = 0; let i = 0;
let fps = [15, 30, 60]; let fps = [15, 30, 60];
cycleFramerateButton.onClick = () => { cycleFramerateButton.onClick = () => {
this.game.setMaxFPS(fps[i]); this.game.setMaxUpdateFPS(fps[i]);
i = (i + 1) % 3; i = (i + 1) % 3;
} }

View File

@ -72,7 +72,7 @@ export default class SecondScene extends Scene {
let i = 0; let i = 0;
let fps = [15, 30, 60]; let fps = [15, 30, 60];
cycleFramerateButton.onClick = () => { cycleFramerateButton.onClick = () => {
this.game.setMaxFPS(fps[i]); this.game.setMaxUpdateFPS(fps[i]);
i = (i + 1) % 3; i = (i + 1) % 3;
} }