ShatteredSword/src/Loop/GameLoop.ts
2020-08-24 18:54:52 -04:00

160 lines
5.0 KiB
TypeScript

import EventQueue from "../Events/EventQueue";
import InputReceiver from "../Input/InputReceiver";
import InputHandler from "../Input/InputHandler";
import Recorder from "../Playback/Recorder";
import GameState from "../GameState/GameState";
import Debug from "../Debug/Debug";
import ResourceManager from "../ResourceManager/ResourceManager";
import Viewport from "../SceneGraph/Viewport";
export default class GameLoop{
// The amount of time to spend on a physics step
private maxFPS: number;
private simulationTimestep: number;
// The time when the last frame was drawn
private lastFrameTime: number;
// The current frame of the game
private frame: number;
// Keeping track of the fps
private runningFrameSum: number;
private numFramesInSum: number;
private maxFramesInSum: number;
private fps: number;
private started: boolean;
private running: boolean;
private frameDelta: number;
readonly GAME_CANVAS: HTMLCanvasElement;
readonly WIDTH: number;
readonly HEIGHT: number;
private viewport: Viewport;
private ctx: CanvasRenderingContext2D;
private eventQueue: EventQueue;
private inputHandler: InputHandler;
private inputReceiver: InputReceiver;
private recorder: Recorder;
private gameState: GameState;
private resourceManager: ResourceManager;
constructor(){
this.maxFPS = 60;
this.simulationTimestep = Math.floor(1000/this.maxFPS);
this.frame = 0;
this.runningFrameSum = 0;
this.numFramesInSum = 0;
this.maxFramesInSum = 30;
this.fps = this.maxFPS;
this.started = false;
this.running = false;
this.GAME_CANVAS = document.getElementById("game-canvas") as HTMLCanvasElement;
this.GAME_CANVAS.style.setProperty("background-color", "whitesmoke");
this.WIDTH = 800;
this.HEIGHT = 500;
this.ctx = this.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT);
this.viewport = new Viewport();
this.viewport.setSize(this.WIDTH, this.HEIGHT);
this.eventQueue = EventQueue.getInstance();
this.inputHandler = new InputHandler(this.GAME_CANVAS);
this.inputReceiver = InputReceiver.getInstance();
this.inputReceiver.setViewport(this.viewport);
this.recorder = new Recorder();
this.gameState = new GameState(this.viewport);
this.resourceManager = ResourceManager.getInstance();
}
private initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D {
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = false;
return ctx;
}
setMaxFPS(initMax: number): void {
this.maxFPS = initMax;
this.simulationTimestep = Math.floor(1000/this.maxFPS);
}
getGameState(): GameState {
return this.gameState;
}
private updateFrameCount(timestep: number): void {
this.frame += 1;
this.numFramesInSum += 1;
this.runningFrameSum += timestep;
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));
}
start(): void {
if(!this.started){
this.started = true;
window.requestAnimationFrame(this.startFrame);
}
}
startFrame = (timestamp: number): void => {
this.running = true;
this.render();
this.lastFrameTime = timestamp;
window.requestAnimationFrame(this.doFrame);
}
doFrame = (timestamp: number): void => {
// Request animation frame to prepare for another update or render
window.requestAnimationFrame(this.doFrame);
// If we are trying to update too soon, return and do nothing
if(timestamp < this.lastFrameTime + this.simulationTimestep){
return
}
// Currently, update and draw are synced - eventually it would probably be good to desync these
this.frameDelta = timestamp - this.lastFrameTime;
this.lastFrameTime = timestamp;
// Update while we can (This will present problems if we leave the window)
let i = 0;
while(this.frameDelta >= this.simulationTimestep){
this.update(this.simulationTimestep/1000);
this.frameDelta -= this.simulationTimestep;
// Update the frame of the game
this.updateFrameCount(this.simulationTimestep);
}
// Updates are done, draw
this.render();
}
update(deltaT: number): void {
this.eventQueue.update(deltaT);
this.inputReceiver.update(deltaT);
this.recorder.update(deltaT);
this.gameState.update(deltaT);
}
render(): void {
this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT);
this.gameState.render(this.ctx);
Debug.render(this.ctx);
}
}