diff --git a/src/DataTypes/Functions/NullFunc.ts b/src/DataTypes/Functions/NullFunc.ts new file mode 100644 index 0000000..bb13b22 --- /dev/null +++ b/src/DataTypes/Functions/NullFunc.ts @@ -0,0 +1,6 @@ +/** + * A placeholder function for No Operation. Does nothing + */ +const NullFunc = () => {}; + +export default NullFunc; \ No newline at end of file diff --git a/src/Loop/FixedUpdateGameLoop.ts b/src/Loop/FixedUpdateGameLoop.ts new file mode 100644 index 0000000..69c265b --- /dev/null +++ b/src/Loop/FixedUpdateGameLoop.ts @@ -0,0 +1,218 @@ +import GameLoop from "./GameLoop"; +import Debug from "../Debug/Debug"; +import Stats from "../Debug/Stats"; + +/** + * A game loop with a fixed update time and a variable render time. + * Every frame, the game updates until all time since the last frame has been processed. + * If too much time has passed, such as if the last update was too slow, + * or if the browser was put into the background, the loop will panic and discard time. + * A render happens at the end of every frame. This happens as fast as possible unless specified. + * A loop of this type allows for deterministic behavior - No matter what the frame rate is, the update should behave the same, + * as it is occuring in a fixed interval. + */ +export default class FixedUpdateGameLoop extends GameLoop { + + /** The max allowed update fps.*/ + private maxUpdateFPS: number; + + /** The timestep for each update. This is the deltaT passed to update calls. */ + private updateTimestep: number; + + /** The amount of time we are yet to simulate. */ + private frameDelta: number; + + /** 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; + + /** The actual fps of the game. */ + private fps: number; + + /** The time between fps measurement updates. */ + private fpsUpdateInterval: 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 number of update steps this iteration of the game loop. */ + private numUpdateSteps: number; + + constructor() { + super(); + this.maxUpdateFPS = 60; + this.updateTimestep = Math.floor(1000/this.maxUpdateFPS); + this.frameDelta = 0; + this.lastFrameTime = 0; + this.minFrameDelay = 0; + this.frame = 0; + this.fps = this.maxUpdateFPS; // Initialize the fps to the max allowed fps + this.fpsUpdateInterval = 1000; + this.lastFpsUpdate = 0; + this.framesSinceLastFpsUpdate = 0; + this.started = false; + this.running = false; + this.numUpdateSteps = 0; + } + + getFPS(): number { + return 0; + } + + /** + * Updates the frame count and sum of time for the framerate of the game + * @param timestep The current time in ms + */ + protected updateFPS(timestamp: number): void { + this.fps = 0.9 * this.framesSinceLastFpsUpdate * 1000 / (timestamp - this.lastFpsUpdate) +(1 - 0.9) * this.fps; + this.lastFpsUpdate = timestamp; + this.framesSinceLastFpsUpdate = 0; + + Debug.log("fps", "FPS: " + this.fps.toFixed(1)); + Stats.updateFPS(this.fps); + } + + /** + * Changes the maximum allowed physics framerate of the game + * @param initMax The max framerate + */ + setMaxUpdateFPS(initMax: number): void { + this.maxUpdateFPS = initMax; + this.updateTimestep = Math.floor(1000/this.maxUpdateFPS); + } + + /** + * Sets the maximum rendering framerate + * @param maxFPS The max framerate + */ + setMaxFPS(maxFPS: number): void { + this.minFrameDelay = 1000/maxFPS; + } + + /** + * This function is called when the game loop panics, i.e. it tries to process too much time in an entire frame. + * This will reset the amount of time back to zero. + * @returns The amount of time we are discarding from processing. + */ + resetFrameDelta() : number { + let oldFrameDelta = this.frameDelta; + this.frameDelta = 0; + return oldFrameDelta; + } + + /** + * Starts up the game loop and calls the first requestAnimationFrame + */ + start(): void { + if(!this.started){ + this.started = true; + + window.requestAnimationFrame((timestamp) => this.doFirstFrame(timestamp)); + } + } + + /** + * The first game frame - initializes the first frame time and begins the render + * @param timestamp The current time in ms + */ + protected doFirstFrame(timestamp: number): void { + this.running = true; + + this._doRender(); + + this.lastFrameTime = timestamp; + this.lastFpsUpdate = timestamp; + this.framesSinceLastFpsUpdate = 0; + + window.requestAnimationFrame((t) => this.doFrame(t)); + } + + /** + * Handles any processing that needs to be done at the start of the frame + * @param timestamp The time of the frame in ms + */ + protected startFrame(timestamp: number): void { + // Update the amount of time we need our update to process + this.frameDelta += timestamp - this.lastFrameTime; + + // Set the new time of the last frame + this.lastFrameTime = timestamp; + + // Update the estimate of the framerate + if(timestamp > this.lastFpsUpdate + this.fpsUpdateInterval){ + this.updateFPS(timestamp); + } + + // Increment the number of frames + this.frame++; + this.framesSinceLastFpsUpdate++; + } + + /** + * The main loop of the game. Updates until the current time is reached. Renders once + * @param timestamp The current time in ms + */ + protected doFrame = (timestamp: number): void => { + // Request animation frame to prepare for another update or render + window.requestAnimationFrame((t) => this.doFrame(t)); + + // If we are trying to render too soon, do nothing. + if(timestamp < this.lastFrameTime + this.minFrameDelay){ + return + } + + // A frame is actually happening + this.startFrame(timestamp); + + // Update while there is still time to make up. If we do too many update steps, panic and exit the loop. + this.numUpdateSteps = 0; + let panic = false; + + while(this.frameDelta >= this.updateTimestep){ + // Do an update + this._doUpdate(this.updateTimestep/1000); + + // Remove the update step time from the time we have to process + this.frameDelta -= this.updateTimestep; + + // Increment steps and check if we've done too many + this.numUpdateSteps++; + if(this.numUpdateSteps > 100){ + panic = true; + break; + } + } + + // Updates are done, render + this._doRender(); + + // Wrap up the frame + this.finishFrame(panic); + } + + /** + * Wraps up the frame and handles the panic state if there is one + * @param panic Whether or not the loop panicked + */ + protected finishFrame(panic: boolean): void { + if(panic) { + var discardedTime = Math.round(this.resetFrameDelta()); + console.warn('Main loop panicked, probably because the browser tab was put in the background. Discarding ' + discardedTime + 'ms'); + } + } + +} \ No newline at end of file diff --git a/src/Loop/Game.ts b/src/Loop/Game.ts new file mode 100644 index 0000000..5e873f6 --- /dev/null +++ b/src/Loop/Game.ts @@ -0,0 +1,169 @@ +import EventQueue from "../Events/EventQueue"; +import InputReceiver from "../Input/InputReceiver"; +import InputHandler from "../Input/InputHandler"; +import Recorder from "../Playback/Recorder"; +import Debug from "../Debug/Debug"; +import ResourceManager from "../ResourceManager/ResourceManager"; +import Viewport from "../SceneGraph/Viewport"; +import SceneManager from "../Scene/SceneManager"; +import AudioManager from "../Sound/AudioManager"; +import Stats from "../Debug/Stats"; +import RenderingManager from "../Rendering/RenderingManager"; +import CanvasRenderer from "../Rendering/CanvasRenderer"; +import Color from "../Utils/Color"; +import GameOptions from "./GameOptions"; +import GameLoop from "./GameLoop"; +import FixedUpdateGameLoop from "./FixedUpdateGameLoop"; + +/** + * The main loop of the game engine. + * Handles the update order, and initializes all subsystems. + * The Game manages the update cycle, and requests animation frames to render to the browser. + */ +export default class Game { + gameOptions: GameOptions; + + // The game loop + private loop: GameLoop; + + // Game canvas and its width and height + readonly GAME_CANVAS: HTMLCanvasElement; + readonly DEBUG_CANVAS: HTMLCanvasElement; + readonly WIDTH: number; + readonly HEIGHT: number; + private viewport: Viewport; + private ctx: CanvasRenderingContext2D; + private clearColor: Color; + + // All of the necessary subsystems that need to run here + private eventQueue: EventQueue; + private inputHandler: InputHandler; + private inputReceiver: InputReceiver; + private recorder: Recorder; + private resourceManager: ResourceManager; + private sceneManager: SceneManager; + private audioManager: AudioManager; + private renderingManager: RenderingManager; + + /** + * Creates a new Game + * @param options The options for Game initialization + */ + constructor(options?: Record){ + // Typecast the config object to a GameConfig object + this.gameOptions = GameOptions.parse(options); + + // Create an instance of a game loop + this.loop = new FixedUpdateGameLoop(); + + // Get the game canvas and give it a background color + this.GAME_CANVAS = document.getElementById("game-canvas"); + this.DEBUG_CANVAS = document.getElementById("debug-canvas"); + + // Give the canvas a size and get the rendering context + this.WIDTH = this.gameOptions.viewportSize.x; + this.HEIGHT = this.gameOptions.viewportSize.y; + + // For now, just hard code a canvas renderer. We can do this with options later + this.renderingManager = new CanvasRenderer(); + this.initializeGameWindow(); + this.ctx = this.renderingManager.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT); + this.clearColor = new Color(this.gameOptions.clearColor.r, this.gameOptions.clearColor.g, this.gameOptions.clearColor.b); + + // Initialize debugging and stats + Debug.initializeDebugCanvas(this.DEBUG_CANVAS, this.WIDTH, this.HEIGHT); + Stats.initStats(); + + // Size the viewport to the game canvas + this.viewport = new Viewport(); + this.viewport.setCanvasSize(this.WIDTH, this.HEIGHT); + this.viewport.setSize(this.WIDTH, this.HEIGHT); + + // Initialize all necessary game subsystems + 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.resourceManager = ResourceManager.getInstance(); + this.sceneManager = new SceneManager(this.viewport, this.renderingManager); + this.audioManager = AudioManager.getInstance(); + + + } + + /** + * Set up the game window that holds the canvases + */ + private initializeGameWindow(): void { + const gameWindow = document.getElementById("game-window"); + + // Set the height of the game window + gameWindow.style.width = this.WIDTH + "px"; + gameWindow.style.height = this.HEIGHT + "px"; + } + + /** + * Retreives the SceneManager from the Game + * @returns The SceneManager + */ + getSceneManager(): SceneManager { + return this.sceneManager; + } + + /** + * Starts the game + */ + start(): void { + // Set the update function of the loop + this.loop.doUpdate = (deltaT: number) => this.update(deltaT); + + // Set the render function of the loop + this.loop.doRender = () => this.render(); + + // Start the loop + this.loop.start(); + } + + /** + * Updates all necessary subsystems of the game. Defers scene updates to the sceneManager + * @param deltaT The time sine the last update + */ + update(deltaT: number): void { + // Handle all events that happened since the start of the last loop + this.eventQueue.update(deltaT); + + // Update the input data structures so game objects can see the input + this.inputReceiver.update(deltaT); + + // Update the recording of the game + this.recorder.update(deltaT); + + // Update all scenes + this.sceneManager.update(deltaT); + + // Update all sounds + this.audioManager.update(deltaT); + + // Load or unload any resources if needed + this.resourceManager.update(deltaT); + } + + /** + * Clears the canvas and defers scene rendering to the sceneManager. Renders the debug canvas + */ + render(): void { + // Clear the canvases + this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT); + Debug.clearCanvas(); + + // Game Canvas + this.ctx.fillStyle = this.clearColor.toString(); + this.ctx.fillRect(0, 0, this.WIDTH, this.HEIGHT); + this.sceneManager.render(); + + // Debug render + Debug.render(); + Stats.render(); + } +} \ No newline at end of file diff --git a/src/Loop/GameLoop.ts b/src/Loop/GameLoop.ts index 9501bf4..a7f37b4 100644 --- a/src/Loop/GameLoop.ts +++ b/src/Loop/GameLoop.ts @@ -1,327 +1,59 @@ -import EventQueue from "../Events/EventQueue"; -import InputReceiver from "../Input/InputReceiver"; -import InputHandler from "../Input/InputHandler"; -import Recorder from "../Playback/Recorder"; -import Debug from "../Debug/Debug"; -import ResourceManager from "../ResourceManager/ResourceManager"; -import Viewport from "../SceneGraph/Viewport"; -import SceneManager from "../Scene/SceneManager"; -import AudioManager from "../Sound/AudioManager"; -import Stats from "../Debug/Stats"; -import RenderingManager from "../Rendering/RenderingManager"; -import CanvasRenderer from "../Rendering/CanvasRenderer"; -import Color from "../Utils/Color"; -import GameOptions from "./GameOptions"; +import NullFunc from "../DataTypes/Functions/NullFunc"; /** - * The main loop of the game engine. - * Handles the update order, and initializes all subsystems. - * The GameLoop manages the update cycle, and requests animation frames to render to the browser. + * The main game loop of the game. Keeps track of fps and handles scheduling of updates and rendering. + * This class is left abstract, so that a subclass can handle exactly how the loop is scheduled. + * For an example of different types of game loop scheduling, check out @link(Game Programming Patterns)(https://gameprogrammingpatterns.com/game-loop.html) */ -export default class GameLoop { - gameOptions: GameOptions; +export default abstract class GameLoop { - /** The max allowed update fps.*/ - private maxUpdateFPS: number; - - /** The timestep for each update. This is the deltaT passed to update calls. */ - private simulationTimestep: number; + /** The function to call when an update occurs */ + protected _doUpdate: Function = NullFunc; - /** The amount of time we are yet to simulate. */ - private frameDelta: number; + set doUpdate(update: Function){ + this._doUpdate = update; + } - /** 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 function to call when a render occurs */ + protected _doRender: Function = NullFunc; - /** The current frame of the game. */ - private frame: number; - /** The actual fps of the game. */ - private fps: number; - - /** The time between fps measurement updates. */ - private fpsUpdateInterval: number; + set doRender(render: Function){ + this._doRender = render; + } + + /** + * Retrieves the current FPS of the game + */ + abstract getFPS(): 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; - - /** The number of update steps this iteration of the game loop. */ - private numUpdateSteps: number; - - // Game canvas and its width and height - readonly GAME_CANVAS: HTMLCanvasElement; - readonly DEBUG_CANVAS: HTMLCanvasElement; - readonly WIDTH: number; - readonly HEIGHT: number; - private viewport: Viewport; - private ctx: CanvasRenderingContext2D; - private clearColor: Color; - - // All of the necessary subsystems that need to run here - private eventQueue: EventQueue; - private inputHandler: InputHandler; - private inputReceiver: InputReceiver; - private recorder: Recorder; - private resourceManager: ResourceManager; - private sceneManager: SceneManager; - private audioManager: AudioManager; - private renderingManager: RenderingManager; - - /** - * Creates a new GameLoop - * @param options The options for GameLoop initialization + /** + * Starts up the game loop */ - constructor(options?: Record){ - // Typecast the config object to a GameConfig object - this.gameOptions = GameOptions.parse(options); + abstract start(): void; - this.maxUpdateFPS = 60; - this.simulationTimestep = Math.floor(1000/this.maxUpdateFPS); - this.frameDelta = 0; - this.lastFrameTime = 0; - this.minFrameDelay = 0; - this.frame = 0; - this.fps = this.maxUpdateFPS; // Initialize the fps to the max allowed fps - this.fpsUpdateInterval = 1000; - this.lastFpsUpdate = 0; - this.framesSinceLastFpsUpdate = 0; - this.started = false; - this.running = false; - this.panic = false; - this.numUpdateSteps = 0; + /** + * Runs the first frame of the game. No update occurs here, only a render. + * This is needed to initialize delta time values + * @param timestamp The timestamp of the frame. This is received from the browser + */ + protected abstract doFirstFrame(timestamp: number): void; - // Set the max fps to 60fps - // this.setMaxFPS(60); + /** + * Run before any updates or the render of a frame. + * @param timestamp The timestamp of the frame. This is received from the browser + */ + protected abstract startFrame(timestamp: number): void; - // Get the game canvas and give it a background color - this.GAME_CANVAS = document.getElementById("game-canvas"); - this.DEBUG_CANVAS = document.getElementById("debug-canvas"); - - // Give the canvas a size and get the rendering context - this.WIDTH = this.gameOptions.viewportSize.x; - this.HEIGHT = this.gameOptions.viewportSize.y; + /** + * The core of the frame, where any necessary updates occur, and where a render happens + * @param timestamp The timestamp of the frame. This is received from the browser + */ + protected abstract doFrame(timestamp: number): void; - // For now, just hard code a canvas renderer. We can do this with options later - this.renderingManager = new CanvasRenderer(); - this.initializeGameWindow(); - this.ctx = this.renderingManager.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT); - this.clearColor = new Color(this.gameOptions.clearColor.r, this.gameOptions.clearColor.g, this.gameOptions.clearColor.b); - - // Initialize debug canvas - - Debug.initializeDebugCanvas(this.DEBUG_CANVAS, this.WIDTH, this.HEIGHT); - - // Size the viewport to the game canvas - this.viewport = new Viewport(); - this.viewport.setCanvasSize(this.WIDTH, this.HEIGHT); - this.viewport.setSize(this.WIDTH, this.HEIGHT); - - // Initialize all necessary game subsystems - 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.resourceManager = ResourceManager.getInstance(); - this.sceneManager = new SceneManager(this.viewport, this, this.renderingManager); - this.audioManager = AudioManager.getInstance(); - - Stats.initStats(); - } - - /** - * Set up the game window that holds the canvases - */ - private initializeGameWindow(): void { - const gameWindow = document.getElementById("game-window"); - - // Set the height of the game window - gameWindow.style.width = this.WIDTH + "px"; - gameWindow.style.height = this.HEIGHT + "px"; - } - - /** - * Changes the maximum allowed physics framerate of the game - * @param initMax The max framerate - */ - setMaxUpdateFPS(initMax: number): void { - this.maxUpdateFPS = initMax; - this.simulationTimestep = Math.floor(1000/this.maxUpdateFPS); - } - - /** - * Sets the maximum rendering framerate - * @param maxFPS The max framerate - */ - setMaxFPS(maxFPS: number): void { - this.minFrameDelay = 1000/maxFPS; - } - - /** - * Retreives the SceneManager from the GameLoop - * @returns The SceneManager - */ - getSceneManager(): SceneManager { - return this.sceneManager; - } - - /** - * Updates the frame count and sum of time for the framerate of the game - * @param timestep The current time in ms - */ - private updateFPS(timestamp: number): void { - this.fps = 0.9 * this.framesSinceLastFpsUpdate * 1000 / (timestamp - this.lastFpsUpdate) +(1 - 0.9) * this.fps; - this.lastFpsUpdate = timestamp; - this.framesSinceLastFpsUpdate = 0; - - Debug.log("fps", "FPS: " + this.fps.toFixed(1)); - Stats.updateFPS(this.fps); - } - - /** - * Starts up the game loop and calls the first requestAnimationFrame - */ - start(): void { - if(!this.started){ - this.started = true; - - window.requestAnimationFrame(this.startFrame); - } - } - - /** - * The first game frame - initializes the first frame time and begins the render - * @param timestamp The current time in ms - */ - startFrame(timestamp: number): void { - this.running = true; - - this.render(); - - this.lastFrameTime = timestamp; - this.lastFpsUpdate = timestamp; - this.framesSinceLastFpsUpdate = 0; - - window.requestAnimationFrame(this.doFrame); - } - - /** - * The main loop of the game. Updates and renders every frame - * @param timestamp - */ - 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.minFrameDelay){ - 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 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) - this.numUpdateSteps = 0; - while(this.frameDelta >= this.simulationTimestep){ - this.update(this.simulationTimestep/1000); - this.frameDelta -= this.simulationTimestep; - - this.numUpdateSteps++; - if(this.numUpdateSteps > 100){ - this.panic = true; - break; - } - } - - // Updates are done, draw - this.render(); - - // End the frame - this.end(); - - this.panic = false; - } - - /** - * Ends the game loop - */ - end(){ - if(this.panic) { - var discardedTime = Math.round(this.resetFrameDelta()); - console.warn('Main loop panicked, probably because the browser tab was put in the background. Discarding ' + discardedTime + 'ms'); - } - } - - resetFrameDelta() : number { - var oldFrameDelta = this.frameDelta; - this.frameDelta = 0; - return oldFrameDelta; - } - - /** - * Updates all necessary subsystems of the game. Defers scene updates to the sceneManager - * @param deltaT The time sine the last update - */ - update(deltaT: number): void { - // Handle all events that happened since the start of the last loop - this.eventQueue.update(deltaT); - - // Update the input data structures so game objects can see the input - this.inputReceiver.update(deltaT); - - // Update the recording of the game - this.recorder.update(deltaT); - - // Update all scenes - this.sceneManager.update(deltaT); - - // Update all sounds - this.audioManager.update(deltaT); - - // Load or unload any resources if needed - this.resourceManager.update(deltaT); - } - - /** - * Clears the canvas and defers scene rendering to the sceneManager. Renders the debug canvas - */ - render(): void { - // Clear the canvases - this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT); - Debug.clearCanvas(); - - // Game Canvas - this.ctx.fillStyle = this.clearColor.toString(); - this.ctx.fillRect(0, 0, this.WIDTH, this.HEIGHT); - this.sceneManager.render(); - - // Debug render - Debug.render(); - Stats.render(); - } + /** + * Wraps up the frame + * @param panic Whether or not the update cycle panicked. This happens when too many updates try to happen in a single frame + */ + protected abstract finishFrame(panic: boolean): void; } \ No newline at end of file diff --git a/src/ResourceManager/ResourceManager.ts b/src/ResourceManager/ResourceManager.ts index 6c5ca26..3d00d47 100644 --- a/src/ResourceManager/ResourceManager.ts +++ b/src/ResourceManager/ResourceManager.ts @@ -35,7 +35,7 @@ export default class ResourceManager { private loadonly_imagesToLoad: number; /** The queue of images we must load */ private loadonly_imageLoadingQueue: Queue; - /** A map of the images that are currently loaded and (presumably) being used by the scene */ + /** A map of the images that are currently loaded and being used by the scene. The reference to these images only exist here for easy cleanup. */ private images: Map; /** Number to keep track of how many tilemaps need to be loaded */ diff --git a/src/Scene/Scene.ts b/src/Scene/Scene.ts index dae194d..9669840 100644 --- a/src/Scene/Scene.ts +++ b/src/Scene/Scene.ts @@ -8,7 +8,7 @@ import SceneGraphArray from "../SceneGraph/SceneGraphArray"; import FactoryManager from "./Factories/FactoryManager"; import Tilemap from "../Nodes/Tilemap"; import ResourceManager from "../ResourceManager/ResourceManager"; -import GameLoop from "../Loop/GameLoop"; +import Game from "../Loop/Game"; import SceneManager from "./SceneManager"; import Receiver from "../Events/Receiver"; import Emitter from "../Events/Emitter"; @@ -40,9 +40,6 @@ export default class Scene implements Updateable { /** A flag that represents whether this scene is running or not. */ protected running: boolean; - /** The overall game loop. */ - protected game: GameLoop; - /** The manager of this scene. */ protected sceneManager: SceneManager; @@ -93,17 +90,16 @@ export default class Scene implements Updateable { * @param viewport The viewport of the game * @param sceneManager The SceneManager that owns this Scene * @param renderingManager The RenderingManager that will handle this Scene's rendering - * @param game The instance of the GameLoop + * @param game The instance of the Game * @param options The options for Scene initialization */ - constructor(viewport: Viewport, sceneManager: SceneManager, renderingManager: RenderingManager, game: GameLoop, options: Record){ + constructor(viewport: Viewport, sceneManager: SceneManager, renderingManager: RenderingManager, options: Record){ this.sceneOptions = SceneOptions.parse(options); this.worldSize = new Vec2(500, 500); this.viewport = viewport; this.viewport.setBounds(0, 0, 2560, 1280); this.running = false; - this.game = game; this.sceneManager = sceneManager; this.receiver = new Receiver(); this.emitter = new Emitter(); diff --git a/src/Scene/SceneManager.ts b/src/Scene/SceneManager.ts index 4267669..e8bccc7 100644 --- a/src/Scene/SceneManager.ts +++ b/src/Scene/SceneManager.ts @@ -1,38 +1,38 @@ import Scene from "./Scene"; import ResourceManager from "../ResourceManager/ResourceManager"; import Viewport from "../SceneGraph/Viewport"; -import GameLoop from "../Loop/GameLoop"; +import Game from "../Loop/Game"; import RenderingManager from "../Rendering/RenderingManager"; /** - * The SceneManager of the game engine. There is only one of theses. * The SceneManager acts as an interface to create Scenes, and handles the lifecycle methods of Scenes. - * The Scene manager keeps track of systems that are constant across scene, such as the @reference[ResourceManager] + * It gives Scenes access to information they need from the @reference[Game] class while keeping a layer of separation. */ export default class SceneManager { /** The current Scene of the game */ protected currentScene: Scene; + /** The Viewport of the game */ protected viewport: Viewport; + /** A reference to the ResourceManager */ protected resourceManager: ResourceManager; - /** The GameLoop this SceneManager belongs to */ - protected game: GameLoop; + /** A counter to keep track of game ids */ protected idCounter: number; + /** The RenderingManager of the game */ protected renderingManager: RenderingManager; /** * Creates a new SceneManager * @param viewport The Viewport of the game - * @param game The GameLoop instance + * @param game The Game instance * @param renderingManager The RenderingManager of the game */ - constructor(viewport: Viewport, game: GameLoop, renderingManager: RenderingManager){ + constructor(viewport: Viewport, renderingManager: RenderingManager){ this.resourceManager = ResourceManager.getInstance(); this.viewport = viewport; - this.game = game; this.renderingManager = renderingManager; this.idCounter = 0; } @@ -43,7 +43,7 @@ export default class SceneManager { * @param constr The constructor of the scene to add */ public addScene(constr: new (...args: any) => T, options: Record): void { - let scene = new constr(this.viewport, this, this.renderingManager, this.game, options); + let scene = new constr(this.viewport, this, this.renderingManager, options); this.currentScene = scene; // Enqueue all scene asset loads diff --git a/src/main.ts b/src/main.ts index d28a736..d8eecb2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import GameLoop from "./Loop/GameLoop"; +import Game from "./Loop/Game"; import {} from "./index"; import MainMenu from "./_DemoClasses/Mario/MainMenu"; import Level1 from "./_DemoClasses/Mario/Level1"; @@ -11,7 +11,7 @@ function main(){ clearColor: {r: 34, g: 32, b: 52} } - let game = new GameLoop(options); + let game = new Game(options); game.start(); let sm = game.getSceneManager(); @@ -54,6 +54,6 @@ CanvasRenderingContext2D.prototype.strokeRoundedRect = function(x, y, w, h, r){ CanvasRenderingContext2D.prototype.fillRoundedRect = function(x, y, w, h, r){ this.roundedRect(x, y, w, h, r); this.fill(); -} +} main(); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 4f2bfab..10a4787 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,7 @@ "src/Input/InputHandler.ts", "src/Input/InputReceiver.ts", - "src/Loop/GameLoop.ts", + "src/Loop/Game.ts", "src/Nodes/Tilemaps/OrthogonalTilemap.ts", "src/Nodes/UIElements/Button.ts",