From 214eba6e71c7ea60289fba937c46dd4d3960e66f Mon Sep 17 00:00:00 2001 From: Joe Weaver Date: Sat, 5 Sep 2020 00:04:14 -0400 Subject: [PATCH] added resource loader --- src/DataTypes/Queue.ts | 13 +- src/DataTypes/Tilesets/TileLayer.ts | 5 + src/GameState/Factories/TilemapFactory.ts | 45 ----- src/GameState/GameState.ts | 45 ----- src/Loop/GameLoop.ts | 14 +- src/Nodes/CanvasNode.ts | 2 +- src/Nodes/GameNode.ts | 8 +- src/Nodes/Tilemap.ts | 35 +--- src/Nodes/Tilemaps/OrthogonalTilemap.ts | 68 ++++--- src/ResourceManager/ResourceManager.ts | 168 ++++++++++++++++-- .../Factories/CanvasNodeFactory.ts | 6 +- .../Factories/PhysicsNodeFactory.ts | 6 +- src/Scene/Factories/TilemapFactory.ts | 45 +++++ src/{GameState/Scene.ts => Scene/Layer.ts} | 12 +- src/Scene/Scene.ts | 41 +++++ src/Scene/SceneManager.ts | 39 ++++ src/SceneGraph/SceneGraph.ts | 6 +- src/SceneGraph/SceneGraphArray.ts | 4 +- src/main.ts | 124 ++++++------- tsconfig.json | 10 +- 20 files changed, 439 insertions(+), 257 deletions(-) create mode 100644 src/DataTypes/Tilesets/TileLayer.ts delete mode 100644 src/GameState/Factories/TilemapFactory.ts delete mode 100644 src/GameState/GameState.ts rename src/{GameState => Scene}/Factories/CanvasNodeFactory.ts (81%) rename src/{GameState => Scene}/Factories/PhysicsNodeFactory.ts (86%) create mode 100644 src/Scene/Factories/TilemapFactory.ts rename src/{GameState/Scene.ts => Scene/Layer.ts} (92%) create mode 100644 src/Scene/Scene.ts create mode 100644 src/Scene/SceneManager.ts diff --git a/src/DataTypes/Queue.ts b/src/DataTypes/Queue.ts index 16c2e7d..3539508 100644 --- a/src/DataTypes/Queue.ts +++ b/src/DataTypes/Queue.ts @@ -4,13 +4,15 @@ export default class Queue implements Collection{ private readonly MAX_ELEMENTS: number; private q: Array; private head: number; - private tail: number; + private tail: number; + private size: number; constructor(maxElements: number = 100){ this.MAX_ELEMENTS = maxElements; this.q = new Array(this.MAX_ELEMENTS); this.head = 0; this.tail = 0; + this.size = 0; } enqueue(item: T): void{ @@ -18,6 +20,7 @@ export default class Queue implements Collection{ throw "Queue full - cannot add element" } + this.size += 1; this.q[this.tail] = item; this.tail = (this.tail + 1) % this.MAX_ELEMENTS; } @@ -27,6 +30,8 @@ export default class Queue implements Collection{ throw "Queue empty - cannot remove element" } + + this.size -= 1; let item = this.q[this.head]; this.head = (this.head + 1) % this.MAX_ELEMENTS; @@ -47,7 +52,13 @@ export default class Queue implements Collection{ return this.head !== this.tail; } + getSize(): number { + return this.size; + } + + // TODO: This should actually delete the items in the queue instead of leaving them here clear(): void { + this.size = 0; this.head = this.tail; } diff --git a/src/DataTypes/Tilesets/TileLayer.ts b/src/DataTypes/Tilesets/TileLayer.ts new file mode 100644 index 0000000..77803ea --- /dev/null +++ b/src/DataTypes/Tilesets/TileLayer.ts @@ -0,0 +1,5 @@ +export default class TileLayer { + public data: Array; + public collidable: boolean; + public visible: boolean; +} \ No newline at end of file diff --git a/src/GameState/Factories/TilemapFactory.ts b/src/GameState/Factories/TilemapFactory.ts deleted file mode 100644 index 1ca0745..0000000 --- a/src/GameState/Factories/TilemapFactory.ts +++ /dev/null @@ -1,45 +0,0 @@ -import Scene from "../Scene"; -import Viewport from "../../SceneGraph/Viewport"; -import Tilemap from "../../Nodes/Tilemap" -import ResourceManager from "../../ResourceManager/ResourceManager"; -import { TiledTilemapData } from "../../DataTypes/Tilesets/TiledData"; -import StringUtils from "../../Utils/StringUtils"; -import StaticBody from "../../Physics/StaticBody"; -import Vec2 from "../../DataTypes/Vec2"; - -export default class TilemapFactory { - private scene: Scene; - // TODO: get the resource manager OUT of here, it does not belong - private resourceManager: ResourceManager; - - constructor(scene: Scene){ - this.scene = scene; - this.resourceManager = ResourceManager.getInstance(); - } - - add(constr: new (...a: any) => T, path: string, ...args: any): void { - this.resourceManager.loadTilemap(path, (tilemapData: TiledTilemapData) => { - // For each of the layers in the tilemap, create a tilemap - for(let layer of tilemapData.layers){ - let tilemap = new constr(tilemapData, layer); - tilemap.init(this.scene); - - // Add to scene - this.scene.addTilemap(tilemap); - - if(tilemap.isCollidable()){ - // Register in physics as a tilemap - this.scene.physics.addTilemap(tilemap); - } - - // Load images for the tilesets - tilemap.getTilesets().forEach(tileset => { - let imagePath = StringUtils.getPathFromFilePath(path) + tileset.getImageUrl(); - this.resourceManager.loadImage(imagePath, (path: string, image: HTMLImageElement) => { - tileset.setImage(image); - }) - }); - } - }); - } -} \ No newline at end of file diff --git a/src/GameState/GameState.ts b/src/GameState/GameState.ts deleted file mode 100644 index acf121b..0000000 --- a/src/GameState/GameState.ts +++ /dev/null @@ -1,45 +0,0 @@ -import Stack from "../DataTypes/Stack"; -import Scene from "./Scene"; -import Viewport from "../SceneGraph/Viewport"; -import Vec2 from "../DataTypes/Vec2"; - -export default class GameState{ - private sceneStack: Stack; - private worldSize: Vec2; - private viewport: Viewport; - - constructor(viewport: Viewport){ - this.sceneStack = new Stack(10); - this.worldSize = new Vec2(1600, 1000); - this.viewport = viewport; - this.viewport.setBounds(0, 0, 2560, 1280); - } - - createScene(): Scene{ - let scene = new Scene(this.viewport, this); - this.addScene(scene); - return scene; - } - - addScene(scene: Scene): void { - this.sceneStack.push(scene); - } - - removeScene(startNewTopScene: boolean = true): void { - this.sceneStack.pop(); - this.sceneStack.peek().setPaused(!startNewTopScene); - } - - changeScene(scene: Scene): void { - this.sceneStack.clear(); - this.sceneStack.push(scene); - } - - update(deltaT: number): void { - this.sceneStack.forEach((scene: Scene) => scene.update(deltaT)); - } - - render(ctx: CanvasRenderingContext2D): void { - this.sceneStack.forEach((scene: Scene) => scene.render(ctx)); - } -} \ No newline at end of file diff --git a/src/Loop/GameLoop.ts b/src/Loop/GameLoop.ts index 0baf25c..689ae9f 100644 --- a/src/Loop/GameLoop.ts +++ b/src/Loop/GameLoop.ts @@ -2,10 +2,10 @@ 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"; +import SceneManager from "../Scene/SceneManager"; export default class GameLoop{ // The amount of time to spend on a physics step @@ -37,8 +37,8 @@ export default class GameLoop{ private inputHandler: InputHandler; private inputReceiver: InputReceiver; private recorder: Recorder; - private gameState: GameState; private resourceManager: ResourceManager; + private sceneManager: SceneManager; constructor(){ this.maxFPS = 60; @@ -66,8 +66,8 @@ export default class GameLoop{ this.inputReceiver = InputReceiver.getInstance(); this.inputReceiver.setViewport(this.viewport); this.recorder = new Recorder(); - this.gameState = new GameState(this.viewport); this.resourceManager = ResourceManager.getInstance(); + this.sceneManager = new SceneManager(this.viewport); } private initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D { @@ -83,8 +83,8 @@ export default class GameLoop{ this.simulationTimestep = Math.floor(1000/this.maxFPS); } - getGameState(): GameState { - return this.gameState; + getSceneManager(): SceneManager { + return this.sceneManager; } private updateFrameCount(timestep: number): void { @@ -149,12 +149,12 @@ export default class GameLoop{ this.eventQueue.update(deltaT); this.inputReceiver.update(deltaT); this.recorder.update(deltaT); - this.gameState.update(deltaT); + this.sceneManager.update(deltaT); } render(): void { this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT); - this.gameState.render(this.ctx); + this.sceneManager.render(this.ctx); Debug.render(this.ctx); } } \ No newline at end of file diff --git a/src/Nodes/CanvasNode.ts b/src/Nodes/CanvasNode.ts index eff0be9..77bbd62 100644 --- a/src/Nodes/CanvasNode.ts +++ b/src/Nodes/CanvasNode.ts @@ -1,6 +1,6 @@ import GameNode from "./GameNode"; import Vec2 from "../DataTypes/Vec2"; -import Scene from "../GameState/Scene"; +import Layer from "../Scene/Layer"; export default abstract class CanvasNode extends GameNode{ protected size: Vec2; diff --git a/src/Nodes/GameNode.ts b/src/Nodes/GameNode.ts index 12a8f88..082fc45 100644 --- a/src/Nodes/GameNode.ts +++ b/src/Nodes/GameNode.ts @@ -4,14 +4,14 @@ import Vec2 from "../DataTypes/Vec2"; import Map from "../DataTypes/Map"; import Receiver from "../Events/Receiver"; import GameEvent from "../Events/GameEvent"; -import Scene from "../GameState/Scene"; +import Layer from "../Scene/Layer"; export default abstract class GameNode{ private eventQueue: EventQueue; protected input: InputReceiver; protected position: Vec2; private receiver: Receiver; - protected scene: Scene; + protected scene: Layer; constructor(){ this.eventQueue = EventQueue.getInstance(); @@ -19,11 +19,11 @@ export default abstract class GameNode{ this.position = new Vec2(0, 0); } - init(scene: Scene){ + init(scene: Layer){ this.scene = scene; } - getScene(): Scene { + getScene(): Layer { return this.scene; } diff --git a/src/Nodes/Tilemap.ts b/src/Nodes/Tilemap.ts index ac8d764..06d1d3e 100644 --- a/src/Nodes/Tilemap.ts +++ b/src/Nodes/Tilemap.ts @@ -2,38 +2,28 @@ import Vec2 from "../DataTypes/Vec2"; import GameNode from "./GameNode"; import Tileset from "../DataTypes/Tilesets/Tileset"; import { TiledTilemapData, TiledLayerData } from "../DataTypes/Tilesets/TiledData" +import TileLayer from "../DataTypes/Tilesets/TileLayer"; /** * Represents one layer of tiles */ export default abstract class Tilemap extends GameNode { - protected data: number[]; - protected collisionData: number[]; - protected tilesets: Tileset[]; + protected tilesets: Array; protected worldSize: Vec2; protected tileSize: Vec2; - protected visible: boolean; - protected collidable: boolean; protected scale: Vec2; + protected layers: Array; // TODO: Make this no longer be specific to Tiled - constructor(tilemapData: TiledTilemapData, layerData: TiledLayerData) { + constructor(tilemapData: TiledTilemapData) { super(); this.tilesets = new Array(); this.worldSize = new Vec2(0, 0); this.tileSize = new Vec2(0, 0); - this.parseTilemapData(tilemapData, layerData); + this.parseTilemapData(tilemapData); this.scale = new Vec2(4, 4); } - isCollidable(): boolean { - return this.collidable; - } - - isVisible(): boolean { - return this.visible; - } - getTilesets(): Tileset[] { return this.tilesets; } @@ -56,24 +46,11 @@ export default abstract class Tilemap extends GameNode { abstract getTileAt(worldCoords: Vec2): number; - isReady(): boolean { - if(this.tilesets.length !== 0){ - for(let tileset of this.tilesets){ - if(!tileset.isReady()){ - return false; - } - } - } - return true; - } - - abstract forEachTile(func: Function): void; - /** * Sets up the tileset using the data loaded from file */ // TODO: This shouldn't use tiled data specifically - it should be more general - protected abstract parseTilemapData(tilemapData: TiledTilemapData, layerData: TiledLayerData): void; + protected abstract parseTilemapData(tilemapData: TiledTilemapData): void; abstract render(ctx: CanvasRenderingContext2D, origin: Vec2, viewportSize: Vec2): void; } \ No newline at end of file diff --git a/src/Nodes/Tilemaps/OrthogonalTilemap.ts b/src/Nodes/Tilemaps/OrthogonalTilemap.ts index 712d005..0f131b5 100644 --- a/src/Nodes/Tilemaps/OrthogonalTilemap.ts +++ b/src/Nodes/Tilemaps/OrthogonalTilemap.ts @@ -2,27 +2,33 @@ import Tilemap from "../Tilemap"; import Vec2 from "../../DataTypes/Vec2"; import { TiledTilemapData, TiledLayerData } from "../../DataTypes/Tilesets/TiledData"; import Tileset from "../../DataTypes/Tilesets/Tileset"; +import TileLayer from "../../DataTypes/Tilesets/TileLayer"; export default class OrthogonalTilemap extends Tilemap { - protected parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void { + protected parseTilemapData(tilemapData: TiledTilemapData): void { this.worldSize.set(tilemapData.width, tilemapData.height); this.tileSize.set(tilemapData.tilewidth, tilemapData.tileheight); - this.data = layer.data; - this.collisionData = this.data.map(tile => tile !== 0 ? 1 : 0); - this.visible = layer.visible; - this.collidable = false; - if(layer.properties){ - for(let item of layer.properties){ - if(item.name === "Collidable"){ - this.collidable = item.value; + for(let layerData of tilemapData.layers){ + let layer = new TileLayer(); + layer.data = layer.data; + layer.visible = layer.visible; + layer.collidable = false; + if(layerData.properties){ + for(let item of layerData.properties){ + if(item.name === "Collidable"){ + layer.collidable = item.value; + } } } + this.layers.push(layer); } + tilemapData.tilesets.forEach(tilesetData => this.tilesets.push(new Tileset(tilesetData))); } + // TODO - Should this even work as it currently does? The layers make things more complicated getTileAt(worldCoords: Vec2): number { let localCoords = this.getColRowAt(worldCoords); if(localCoords.x < 0 || localCoords.x >= this.worldSize.x || localCoords.y < 0 || localCoords.y >= this.worldSize.y){ @@ -30,23 +36,39 @@ export default class OrthogonalTilemap extends Tilemap { return 0; } - return this.data[localCoords.y * this.worldSize.x + localCoords.x]; + // Return the top nonzero tile + let tile = 0; + for(let layer of this.layers){ + if(layer.data[localCoords.y * this.worldSize.x + localCoords.x] !== 0){ + tile = layer.data[localCoords.y * this.worldSize.x + localCoords.x]; + } + } + return tile; } isTileCollidable(indexOrCol: number, row?: number): boolean { + let index = 0; if(row){ if(indexOrCol < 0 || indexOrCol >= this.worldSize.x || row < 0 || row >= this.worldSize.y){ // There are no tiles in negative positions or out of bounds positions return false; } - return this.collisionData[row * this.worldSize.x + indexOrCol] === 1 && this.collidable; + index = row * this.worldSize.x + indexOrCol; } else { - if(indexOrCol < 0 || indexOrCol >= this.collisionData.length){ + if(indexOrCol < 0 || indexOrCol >= this.layers[0].data.length){ // Tiles that don't exist aren't collidable return false; } - return this.collisionData[indexOrCol] === 1 && this.collidable; + index = indexOrCol; } + + for(let layer of this.layers){ + if(layer.data[index] !== 0 && layer.collidable){ + return true; + } + } + + return false; } // TODO: Should this throw an error if someone tries to access an out of bounds value? @@ -56,22 +78,20 @@ export default class OrthogonalTilemap extends Tilemap { return new Vec2(col, row); } - forEachTile(func: Function): void { - for(let i = 0; i < this.data.length; i++){ - func(this.data[i], i); - } - } - update(deltaT: number): void {} // TODO: Don't render tiles that aren't on screen render(ctx: CanvasRenderingContext2D, origin: Vec2, viewportSize: Vec2) { - for(let i = 0; i < this.data.length; i++){ - let tileIndex = this.data[i]; + for(let layer of this.layers){ + if(layer.visible){ + for(let i = 0; i < layer.data.length; i++){ + let tileIndex = layer.data[i]; - for(let tileset of this.tilesets){ - if(tileset.hasTile(tileIndex)){ - tileset.renderTile(ctx, tileIndex, i, this.worldSize, origin, this.scale); + for(let tileset of this.tilesets){ + if(tileset.hasTile(tileIndex)){ + tileset.renderTile(ctx, tileIndex, i, this.worldSize, origin, this.scale); + } + } } } } diff --git a/src/ResourceManager/ResourceManager.ts b/src/ResourceManager/ResourceManager.ts index 9dd2e37..eda4cc9 100644 --- a/src/ResourceManager/ResourceManager.ts +++ b/src/ResourceManager/ResourceManager.ts @@ -1,7 +1,37 @@ +import Map from "../DataTypes/Map"; +import Tilemap from "../Nodes/Tilemap"; +import Queue from "../DataTypes/Queue"; +import { TiledTilemapData } from "../DataTypes/Tilesets/TiledData"; +import StringUtils from "../Utils/StringUtils"; + export default class ResourceManager { private static instance: ResourceManager; + + private loading: boolean; - private constructor(){}; + private imagesLoaded: number; + private imagesToLoad: number; + private imageLoadingQueue: Queue<{key: string, path: string}>; + private images: Map; + + private tilemapsLoaded: number; + private tilemapsToLoad: number; + private tilemapLoadingQueue: Queue<{key: string, path: string}>; + private tilemaps: Map; + + private constructor(){ + this.loading = false; + + this.imagesLoaded = 0; + this.imagesToLoad = 0; + this.imageLoadingQueue = new Queue(); + this.images = new Map(); + + this.tilemapsLoaded = 0; + this.tilemapsToLoad = 0; + this.tilemapLoadingQueue = new Queue(); + this.tilemaps = new Map(); + }; static getInstance(): ResourceManager { if(!this.instance){ @@ -11,11 +41,128 @@ export default class ResourceManager { return this.instance; } - public loadTilemap(pathToTilemapJSON: string, callback: Function): void { - this.loadTextFile(pathToTilemapJSON, (fileText: string) => { - let tilemapObject = JSON.parse(fileText); - callback(tilemapObject); + public image(key: string, path: string): void { + this.imageLoadingQueue.enqueue({key: key, path: path}); + } + + public spritesheet(key: string, path: string, frames: {hFrames: number, vFrames: number}): void { + + } + + public audio(key: string, path: string): void { + + } + + // This one is trickier than the others because we first have to load the json file, then we have to load the images + public tilemap(key: string, path: string): void { + // Add a function that loads the tilemap to the queue + this.tilemapLoadingQueue.enqueue({key: key, path: path}); + + // this.tilemapLoadingQueue.enqueue((callback: Function) => { + // this.loadTilemap(path, (tilemapData: TiledTilemapData) => { + // // When the tilemap file loads, first construct the tilemap + // // TODO: Ignore multiple layers for now, but this will have to be elegantly dealt with sometime in the future + + // // Count the total number of images that need to be loaded + + // let tilemap = new constr(tilemapData); + // // For each of the tilesets in the tilemap, load the image + // tilemap.getTilesets().forEach(tileset => { + // let imagePath = StringUtils.getPathFromFilePath(path) + tileset.getImageUrl(); + // this.loadImage(imagePath, (image: HTMLImageElement) => { + // tileset.setImage(image); + // }) + // }); + + // this.tilemaps.add(key, tilemap); + // }); + // }); + } + + loadResourcesFromQueue(callback: Function): void { + this.loading = true; + + // Load everything in the queues. Tilemaps have to come before images because they will add new images to the queue + this.loadTilemapsFromQueue(() => { + this.loadImagesFromQueue(() => { + // Done loading + this.loading = false; + callback(); + }); }); + + } + + private loadTilemapsFromQueue(onFinishLoading: Function){ + this.tilemapsToLoad = this.tilemapLoadingQueue.getSize(); + this.tilemapsLoaded = 0; + + while(this.tilemapLoadingQueue.hasItems()){ + let tilemap = this.tilemapLoadingQueue.dequeue(); + this.loadTilemap(tilemap.key, tilemap.path, onFinishLoading); + } + } + + private loadTilemap(key: string, pathToTilemapJSON: string, callbackIfLast: Function): void { + this.loadTextFile(pathToTilemapJSON, (fileText: string) => { + let tilemapObject = JSON.parse(fileText); + + // We can parse the object later - it's much faster than loading + this.tilemaps.add(key, tilemapObject); + + // Grab the tileset images we need to load and add them to the imageloading queue + for(let tileset of tilemapObject.tilesets){ + let key = tileset.image; + let path = StringUtils.getPathFromFilePath(pathToTilemapJSON) + key; + this.imageLoadingQueue.enqueue({key: key, path: path}); + } + + // Finish loading + this.finishLoadingTilemap(callbackIfLast); + }); + } + + private finishLoadingTilemap(callback: Function){ + this.tilemapsLoaded += 1; + + if(this.tilemapsLoaded === this.tilemapsToLoad){ + // We're done loading tilemaps + callback(); + } + } + + private loadImagesFromQueue(onFinishLoading: Function): void { + this.imagesToLoad = this.imageLoadingQueue.getSize(); + this.tilemapsLoaded = 0; + + while(this.imageLoadingQueue.hasItems()){ + let image = this.imageLoadingQueue.dequeue(); + this.loadImage(image.key, image.path, onFinishLoading); + } + } + + // TODO: When you switch to WebGL, make sure to make this private and make a "loadTexture" function + public loadImage(key: string, path: string, callbackIfLast: Function): void { + var image = new Image(); + + image.onload = () => { + // Add to loaded images + this.images.add(key, image); + + // Finish image load + this.finishLoadingImage(callbackIfLast); + } + + image.src = path; + } + + private finishLoadingImage(callback: Function): void { + this.imagesLoaded += 1; + + if(this.imagesLoaded === this.imagesToLoad ){ + // We're done loading tilemaps + callback(); + } } private loadTextFile(textFilePath: string, callback: Function): void { @@ -29,15 +176,4 @@ export default class ResourceManager { }; xobj.send(null); } - - // TODO: When you switch to WebGL, make sure to make this private and make a "loadTexture" function - public loadImage(path: string, callback: Function): void { - var image = new Image(); - - image.onload = function () { - callback(path, image); - } - - image.src = path; - } } \ No newline at end of file diff --git a/src/GameState/Factories/CanvasNodeFactory.ts b/src/Scene/Factories/CanvasNodeFactory.ts similarity index 81% rename from src/GameState/Factories/CanvasNodeFactory.ts rename to src/Scene/Factories/CanvasNodeFactory.ts index eedd19b..cb1e998 100644 --- a/src/GameState/Factories/CanvasNodeFactory.ts +++ b/src/Scene/Factories/CanvasNodeFactory.ts @@ -1,11 +1,11 @@ -import Scene from "../Scene"; +import Layer from "../Layer"; import Viewport from "../../SceneGraph/Viewport"; import CanvasItem from "../../Nodes/CanvasNode" export default class CanvasNodeFactory { - private scene: Scene; + private scene: Layer; - constructor(scene: Scene){ + constructor(scene: Layer){ this.scene = scene; } diff --git a/src/GameState/Factories/PhysicsNodeFactory.ts b/src/Scene/Factories/PhysicsNodeFactory.ts similarity index 86% rename from src/GameState/Factories/PhysicsNodeFactory.ts rename to src/Scene/Factories/PhysicsNodeFactory.ts index 037f3c6..fc143cd 100644 --- a/src/GameState/Factories/PhysicsNodeFactory.ts +++ b/src/Scene/Factories/PhysicsNodeFactory.ts @@ -1,14 +1,14 @@ -import Scene from "../Scene"; +import Layer from "../Layer"; import Viewport from "../../SceneGraph/Viewport"; import PhysicsNode from "../../Physics/PhysicsNode"; import PhysicsManager from "../../Physics/PhysicsManager"; import Tilemap from "../../Nodes/Tilemap"; export default class PhysicsNodeFactory { - private scene: Scene; + private scene: Layer; private physicsManager: PhysicsManager; - constructor(scene: Scene, physicsManager: PhysicsManager){ + constructor(scene: Layer, physicsManager: PhysicsManager){ this.scene = scene; this.physicsManager = physicsManager; } diff --git a/src/Scene/Factories/TilemapFactory.ts b/src/Scene/Factories/TilemapFactory.ts new file mode 100644 index 0000000..e504350 --- /dev/null +++ b/src/Scene/Factories/TilemapFactory.ts @@ -0,0 +1,45 @@ +import Layer from "../Layer"; +import Viewport from "../../SceneGraph/Viewport"; +import Tilemap from "../../Nodes/Tilemap"; +import ResourceManager from "../../ResourceManager/ResourceManager"; +import { TiledTilemapData } from "../../DataTypes/Tilesets/TiledData"; +import StringUtils from "../../Utils/StringUtils"; +import StaticBody from "../../Physics/StaticBody"; +import Vec2 from "../../DataTypes/Vec2"; + +export default class TilemapFactory { + private scene: Layer; + // TODO: get the resource manager OUT of here, it does not belong + private resourceManager: ResourceManager; + + constructor(scene: Layer){ + this.scene = scene; + this.resourceManager = ResourceManager.getInstance(); + } + + add(constr: new (...a: any) => T, path: string, ...args: any): void { + // this.resourceManager.loadTilemap(path, (tilemapData: TiledTilemapData) => { + // // For each of the layers in the tilemap, create a tilemap + // for(let layer of tilemapData.layers){ + // let tilemap = new constr(tilemapData, layer); + // tilemap.init(this.scene); + + // // Add to scene + // this.scene.addTilemap(tilemap); + + // if(tilemap.isCollidable()){ + // // Register in physics as a tilemap + // this.scene.physics.addTilemap(tilemap); + // } + + // // Load images for the tilesets + // tilemap.getTilesets().forEach(tileset => { + // let imagePath = StringUtils.getPathFromFilePath(path) + tileset.getImageUrl(); + // this.resourceManager.loadImage(imagePath, (path: string, image: HTMLImageElement) => { + // tileset.setImage(image); + // }) + // }); + // } + // }); + } +} \ No newline at end of file diff --git a/src/GameState/Scene.ts b/src/Scene/Layer.ts similarity index 92% rename from src/GameState/Scene.ts rename to src/Scene/Layer.ts index 96ab6c4..176d441 100644 --- a/src/GameState/Scene.ts +++ b/src/Scene/Layer.ts @@ -4,15 +4,15 @@ import SceneGraph from "../SceneGraph/SceneGraph"; import SceneGraphArray from "../SceneGraph/SceneGraphArray"; import CanvasNode from "../Nodes/CanvasNode"; import CanvasNodeFactory from "./Factories/CanvasNodeFactory"; -import GameState from "./GameState"; +import Scene from "./Scene"; import Tilemap from "../Nodes/Tilemap"; import TilemapFactory from "./Factories/TilemapFactory"; import PhysicsManager from "../Physics/PhysicsManager"; import PhysicsNodeFactory from "./Factories/PhysicsNodeFactory"; import MathUtils from "../Utils/MathUtils"; -export default class Scene { - private gameState: GameState; +export default class Layer { + private gameState: Scene; private viewport: Viewport private parallax: Vec2; private sceneGraph: SceneGraph; @@ -27,7 +27,7 @@ export default class Scene { public tilemap: TilemapFactory; public physics: PhysicsNodeFactory; - constructor(viewport: Viewport, gameState: GameState){ + constructor(viewport: Viewport, gameState: Scene){ this.gameState = gameState; this.viewport = viewport; this.parallax = new Vec2(1, 1); @@ -114,9 +114,7 @@ export default class Scene { // Render tilemaps this.tilemaps.forEach(tilemap => { - if(tilemap.isReady() && tilemap.isVisible()){ - tilemap.render(ctx, origin, size); - } + tilemap.render(ctx, origin, size); }); // Render visible set diff --git a/src/Scene/Scene.ts b/src/Scene/Scene.ts new file mode 100644 index 0000000..2ff04d5 --- /dev/null +++ b/src/Scene/Scene.ts @@ -0,0 +1,41 @@ +import Stack from "../DataTypes/Stack"; +import Layer from "./Layer"; +import Viewport from "../SceneGraph/Viewport"; +import Vec2 from "../DataTypes/Vec2"; + +export default class Scene{ + private layers: Stack; + private worldSize: Vec2; + private viewport: Viewport; + private running: boolean; + + constructor(viewport: Viewport){ + this.layers = new Stack(10); + this.worldSize = new Vec2(1600, 1000); + this.viewport = viewport; + this.viewport.setBounds(0, 0, 2560, 1280); + this.running = false; + } + + loadScene(): void {} + + unloadScene(): void {} + + setRunning(running: boolean): void { + this.running = running; + } + + isRunning(): boolean { + return this.isRunning(); + } + + start(){} + + update(deltaT: number): void { + this.layers.forEach((scene: Layer) => scene.update(deltaT)); + } + + render(ctx: CanvasRenderingContext2D): void { + this.layers.forEach((scene: Layer) => scene.render(ctx)); + } +} \ No newline at end of file diff --git a/src/Scene/SceneManager.ts b/src/Scene/SceneManager.ts new file mode 100644 index 0000000..dd0b622 --- /dev/null +++ b/src/Scene/SceneManager.ts @@ -0,0 +1,39 @@ +import Scene from "./Scene"; +import ResourceManager from "../ResourceManager/ResourceManager"; +import Viewport from "../SceneGraph/Viewport"; + +export default class SceneManager{ + + private currentScene: Scene; + private viewport: Viewport; + private resourceManager: ResourceManager; + + constructor(viewport: Viewport){ + this.resourceManager = ResourceManager.getInstance(); + this.viewport = viewport; + } + + public addScene(constr: new (...args: any) => T){ + let scene = new constr(this.viewport); + this.currentScene = scene; + + // Enqueue all scene asset loads + scene.loadScene(); + + // Load all assets + this.resourceManager.loadResourcesFromQueue(() => { + scene.start(); + scene.setRunning(true); + }) + } + + public render(ctx: CanvasRenderingContext2D){ + this.currentScene.render(ctx); + } + + public update(deltaT: number){ + if(this.currentScene.isRunning()){ + this.currentScene.update(deltaT); + } + } +} \ No newline at end of file diff --git a/src/SceneGraph/SceneGraph.ts b/src/SceneGraph/SceneGraph.ts index 7c9ed1e..fd51261 100644 --- a/src/SceneGraph/SceneGraph.ts +++ b/src/SceneGraph/SceneGraph.ts @@ -2,15 +2,15 @@ import Viewport from "./Viewport"; import CanvasNode from "../Nodes/CanvasNode"; import Map from "../DataTypes/Map"; import Vec2 from "../DataTypes/Vec2"; -import Scene from "../GameState/Scene"; +import Layer from "../Scene/Layer"; export default abstract class SceneGraph{ protected viewport: Viewport; protected nodeMap: Map; protected idCounter: number; - protected scene: Scene; + protected scene: Layer; - constructor(viewport: Viewport, scene: Scene){ + constructor(viewport: Viewport, scene: Layer){ this.viewport = viewport; this.scene = scene; this.nodeMap = new Map(); diff --git a/src/SceneGraph/SceneGraphArray.ts b/src/SceneGraph/SceneGraphArray.ts index ae2f694..3467960 100644 --- a/src/SceneGraph/SceneGraphArray.ts +++ b/src/SceneGraph/SceneGraphArray.ts @@ -1,13 +1,13 @@ import SceneGraph from "./SceneGraph"; import CanvasNode from "../Nodes/CanvasNode"; import Viewport from "./Viewport"; -import Scene from "../GameState/Scene"; +import Layer from "../Scene/Layer"; export default class SceneGraphArray extends SceneGraph{ private nodeList: Array; private turnOffViewportCulling_demoTool: boolean; - constructor(viewport: Viewport, scene: Scene){ + constructor(viewport: Viewport, scene: Layer){ super(viewport, scene); this.nodeList = new Array(); diff --git a/src/main.ts b/src/main.ts index 8783036..6836d08 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,80 +9,80 @@ import OrthogonalTilemap from "./Nodes/Tilemaps/OrthogonalTilemap"; function main(){ // Create the game object let game = new GameLoop(); - let gameState = game.getGameState(); + let gameState = game.getSceneManager(); - let backgroundScene = gameState.createScene(); - backgroundScene.setParallax(0.5, 0.8); - backgroundScene.setAlpha(0.5); - let mainScene = gameState.createScene(); - let uiLayer = gameState.createScene(); - uiLayer.setParallax(0, 0); - let pauseMenu = gameState.createScene(); - pauseMenu.setParallax(0, 0); + // let backgroundScene = gameState.createScene(); + // backgroundScene.setParallax(0.5, 0.8); + // backgroundScene.setAlpha(0.5); + // let mainScene = gameState.createScene(); + // let uiLayer = gameState.createScene(); + // uiLayer.setParallax(0, 0); + // let pauseMenu = gameState.createScene(); + // pauseMenu.setParallax(0, 0); - // Initialize GameObjects - let recordButton = uiLayer.canvasNode.add(Button); - recordButton.setSize(100, 50); - recordButton.setText("Record"); - recordButton.setPosition(400, 30); - recordButton.onClickEventId = "record_button_press"; + // // Initialize GameObjects + // let recordButton = uiLayer.canvasNode.add(Button); + // recordButton.setSize(100, 50); + // recordButton.setText("Record"); + // recordButton.setPosition(400, 30); + // recordButton.onClickEventId = "record_button_press"; - let stopButton = uiLayer.canvasNode.add(Button); - stopButton.setSize(100, 50); - stopButton.setText("Stop"); - stopButton.setPosition(550, 30); - stopButton.onClickEventId = "stop_button_press"; + // let stopButton = uiLayer.canvasNode.add(Button); + // stopButton.setSize(100, 50); + // stopButton.setText("Stop"); + // stopButton.setPosition(550, 30); + // stopButton.onClickEventId = "stop_button_press"; - let playButton = uiLayer.canvasNode.add(Button); - playButton.setSize(100, 50); - playButton.setText("Play"); - playButton.setPosition(700, 30); - playButton.onClickEventId = "play_button_press"; + // let playButton = uiLayer.canvasNode.add(Button); + // playButton.setSize(100, 50); + // playButton.setText("Play"); + // playButton.setPosition(700, 30); + // playButton.onClickEventId = "play_button_press"; - let cycleFramerateButton = uiLayer.canvasNode.add(Button); - cycleFramerateButton.setSize(150, 50); - cycleFramerateButton.setText("Cycle FPS"); - cycleFramerateButton.setPosition(5, 400); - let i = 0; - let fps = [15, 30, 60]; - cycleFramerateButton.onClick = () => { - game.setMaxFPS(fps[i]); - i = (i + 1) % 3; - } + // let cycleFramerateButton = uiLayer.canvasNode.add(Button); + // cycleFramerateButton.setSize(150, 50); + // cycleFramerateButton.setText("Cycle FPS"); + // cycleFramerateButton.setPosition(5, 400); + // let i = 0; + // let fps = [15, 30, 60]; + // cycleFramerateButton.onClick = () => { + // game.setMaxFPS(fps[i]); + // i = (i + 1) % 3; + // } - let pauseButton = uiLayer.canvasNode.add(Button); - pauseButton.setSize(100, 50); - pauseButton.setText("Pause"); - pauseButton.setPosition(700, 400); - pauseButton.onClick = () => { - mainScene.setPaused(true); - pauseMenu.enable(); - } + // let pauseButton = uiLayer.canvasNode.add(Button); + // pauseButton.setSize(100, 50); + // pauseButton.setText("Pause"); + // pauseButton.setPosition(700, 400); + // pauseButton.onClick = () => { + // mainScene.setPaused(true); + // pauseMenu.enable(); + // } - let modalBackground = pauseMenu.canvasNode.add(UIElement); - modalBackground.setSize(400, 200); - modalBackground.setBackgroundColor(new Color(0, 0, 0, 0.4)); - modalBackground.setPosition(200, 100); + // let modalBackground = pauseMenu.canvasNode.add(UIElement); + // modalBackground.setSize(400, 200); + // modalBackground.setBackgroundColor(new Color(0, 0, 0, 0.4)); + // modalBackground.setPosition(200, 100); - let resumeButton = pauseMenu.canvasNode.add(Button); - resumeButton.setSize(100, 50); - resumeButton.setText("Resume"); - resumeButton.setPosition(400, 200); - resumeButton.onClick = () => { - mainScene.setPaused(false); - pauseMenu.disable(); - } + // let resumeButton = pauseMenu.canvasNode.add(Button); + // resumeButton.setSize(100, 50); + // resumeButton.setText("Resume"); + // resumeButton.setPosition(400, 200); + // resumeButton.onClick = () => { + // mainScene.setPaused(false); + // pauseMenu.disable(); + // } - backgroundScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/Background.json"); - mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/Platformer.json"); - let player = mainScene.physics.add(Player, "platformer"); + // backgroundScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/Background.json"); + // mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/Platformer.json"); + // let player = mainScene.physics.add(Player, "platformer"); - // mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/TopDown.json"); - // let player = mainScene.physics.add(Player, "topdown"); + // // mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/TopDown.json"); + // // let player = mainScene.physics.add(Player, "topdown"); - mainScene.getViewport().follow(player); + // mainScene.getViewport().follow(player); - pauseMenu.disable(); + // pauseMenu.disable(); game.start(); } diff --git a/tsconfig.json b/tsconfig.json index cbea0c8..745a1c8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,11 +18,11 @@ "src/Events/GameEvent.ts", "src/Events/Receiver.ts", - "src/GameState/Factories/CanvasNodeFactory.ts", - "src/GameState/Factories/PhysicsNodeFactory.ts", - "src/GameState/Factories/TilemapFactory.ts", - "src/GameState/GameState.ts", - "src/GameState/Scene.ts", + "src/Scene/Factories/CanvasNodeFactory.ts", + "src/Scene/Factories/PhysicsNodeFactory.ts", + "src/Scene/Factories/TilemapFactory.ts", + "src/Scene/Scene.ts", + "src/Scene/Layer.ts", "src/Input/InputHandler.ts", "src/Input/InputReceiver.ts",