added resource loader

This commit is contained in:
Joe Weaver 2020-09-05 00:04:14 -04:00
parent bd49258d30
commit 214eba6e71
20 changed files with 439 additions and 257 deletions

View File

@ -5,12 +5,14 @@ export default class Queue<T> implements Collection{
private q: Array<T>; private q: Array<T>;
private head: number; private head: number;
private tail: number; private tail: number;
private size: number;
constructor(maxElements: number = 100){ constructor(maxElements: number = 100){
this.MAX_ELEMENTS = maxElements; this.MAX_ELEMENTS = maxElements;
this.q = new Array(this.MAX_ELEMENTS); this.q = new Array(this.MAX_ELEMENTS);
this.head = 0; this.head = 0;
this.tail = 0; this.tail = 0;
this.size = 0;
} }
enqueue(item: T): void{ enqueue(item: T): void{
@ -18,6 +20,7 @@ export default class Queue<T> implements Collection{
throw "Queue full - cannot add element" throw "Queue full - cannot add element"
} }
this.size += 1;
this.q[this.tail] = item; this.q[this.tail] = item;
this.tail = (this.tail + 1) % this.MAX_ELEMENTS; this.tail = (this.tail + 1) % this.MAX_ELEMENTS;
} }
@ -27,6 +30,8 @@ export default class Queue<T> implements Collection{
throw "Queue empty - cannot remove element" throw "Queue empty - cannot remove element"
} }
this.size -= 1;
let item = this.q[this.head]; let item = this.q[this.head];
this.head = (this.head + 1) % this.MAX_ELEMENTS; this.head = (this.head + 1) % this.MAX_ELEMENTS;
@ -47,7 +52,13 @@ export default class Queue<T> implements Collection{
return this.head !== this.tail; 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 { clear(): void {
this.size = 0;
this.head = this.tail; this.head = this.tail;
} }

View File

@ -0,0 +1,5 @@
export default class TileLayer {
public data: Array<number>;
public collidable: boolean;
public visible: boolean;
}

View File

@ -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<T extends Tilemap>(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);
})
});
}
});
}
}

View File

@ -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<Scene>;
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));
}
}

View File

@ -2,10 +2,10 @@ import EventQueue from "../Events/EventQueue";
import InputReceiver from "../Input/InputReceiver"; import InputReceiver from "../Input/InputReceiver";
import InputHandler from "../Input/InputHandler"; import InputHandler from "../Input/InputHandler";
import Recorder from "../Playback/Recorder"; import Recorder from "../Playback/Recorder";
import GameState from "../GameState/GameState";
import Debug from "../Debug/Debug"; import Debug from "../Debug/Debug";
import ResourceManager from "../ResourceManager/ResourceManager"; import ResourceManager from "../ResourceManager/ResourceManager";
import Viewport from "../SceneGraph/Viewport"; import Viewport from "../SceneGraph/Viewport";
import SceneManager from "../Scene/SceneManager";
export default class GameLoop{ export default class GameLoop{
// The amount of time to spend on a physics step // The amount of time to spend on a physics step
@ -37,8 +37,8 @@ export default class GameLoop{
private inputHandler: InputHandler; private inputHandler: InputHandler;
private inputReceiver: InputReceiver; private inputReceiver: InputReceiver;
private recorder: Recorder; private recorder: Recorder;
private gameState: GameState;
private resourceManager: ResourceManager; private resourceManager: ResourceManager;
private sceneManager: SceneManager;
constructor(){ constructor(){
this.maxFPS = 60; this.maxFPS = 60;
@ -66,8 +66,8 @@ export default class GameLoop{
this.inputReceiver = InputReceiver.getInstance(); this.inputReceiver = InputReceiver.getInstance();
this.inputReceiver.setViewport(this.viewport); this.inputReceiver.setViewport(this.viewport);
this.recorder = new Recorder(); this.recorder = new Recorder();
this.gameState = new GameState(this.viewport);
this.resourceManager = ResourceManager.getInstance(); this.resourceManager = ResourceManager.getInstance();
this.sceneManager = new SceneManager(this.viewport);
} }
private initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D { private initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D {
@ -83,8 +83,8 @@ export default class GameLoop{
this.simulationTimestep = Math.floor(1000/this.maxFPS); this.simulationTimestep = Math.floor(1000/this.maxFPS);
} }
getGameState(): GameState { getSceneManager(): SceneManager {
return this.gameState; return this.sceneManager;
} }
private updateFrameCount(timestep: number): void { private updateFrameCount(timestep: number): void {
@ -149,12 +149,12 @@ export default class GameLoop{
this.eventQueue.update(deltaT); this.eventQueue.update(deltaT);
this.inputReceiver.update(deltaT); this.inputReceiver.update(deltaT);
this.recorder.update(deltaT); this.recorder.update(deltaT);
this.gameState.update(deltaT); this.sceneManager.update(deltaT);
} }
render(): void { render(): void {
this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT); this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT);
this.gameState.render(this.ctx); this.sceneManager.render(this.ctx);
Debug.render(this.ctx); Debug.render(this.ctx);
} }
} }

View File

@ -1,6 +1,6 @@
import GameNode from "./GameNode"; import GameNode from "./GameNode";
import Vec2 from "../DataTypes/Vec2"; import Vec2 from "../DataTypes/Vec2";
import Scene from "../GameState/Scene"; import Layer from "../Scene/Layer";
export default abstract class CanvasNode extends GameNode{ export default abstract class CanvasNode extends GameNode{
protected size: Vec2; protected size: Vec2;

View File

@ -4,14 +4,14 @@ import Vec2 from "../DataTypes/Vec2";
import Map from "../DataTypes/Map"; import Map from "../DataTypes/Map";
import Receiver from "../Events/Receiver"; import Receiver from "../Events/Receiver";
import GameEvent from "../Events/GameEvent"; import GameEvent from "../Events/GameEvent";
import Scene from "../GameState/Scene"; import Layer from "../Scene/Layer";
export default abstract class GameNode{ export default abstract class GameNode{
private eventQueue: EventQueue; private eventQueue: EventQueue;
protected input: InputReceiver; protected input: InputReceiver;
protected position: Vec2; protected position: Vec2;
private receiver: Receiver; private receiver: Receiver;
protected scene: Scene; protected scene: Layer;
constructor(){ constructor(){
this.eventQueue = EventQueue.getInstance(); this.eventQueue = EventQueue.getInstance();
@ -19,11 +19,11 @@ export default abstract class GameNode{
this.position = new Vec2(0, 0); this.position = new Vec2(0, 0);
} }
init(scene: Scene){ init(scene: Layer){
this.scene = scene; this.scene = scene;
} }
getScene(): Scene { getScene(): Layer {
return this.scene; return this.scene;
} }

View File

@ -2,38 +2,28 @@ import Vec2 from "../DataTypes/Vec2";
import GameNode from "./GameNode"; import GameNode from "./GameNode";
import Tileset from "../DataTypes/Tilesets/Tileset"; import Tileset from "../DataTypes/Tilesets/Tileset";
import { TiledTilemapData, TiledLayerData } from "../DataTypes/Tilesets/TiledData" import { TiledTilemapData, TiledLayerData } from "../DataTypes/Tilesets/TiledData"
import TileLayer from "../DataTypes/Tilesets/TileLayer";
/** /**
* Represents one layer of tiles * Represents one layer of tiles
*/ */
export default abstract class Tilemap extends GameNode { export default abstract class Tilemap extends GameNode {
protected data: number[]; protected tilesets: Array<Tileset>;
protected collisionData: number[];
protected tilesets: Tileset[];
protected worldSize: Vec2; protected worldSize: Vec2;
protected tileSize: Vec2; protected tileSize: Vec2;
protected visible: boolean;
protected collidable: boolean;
protected scale: Vec2; protected scale: Vec2;
protected layers: Array<TileLayer>;
// TODO: Make this no longer be specific to Tiled // TODO: Make this no longer be specific to Tiled
constructor(tilemapData: TiledTilemapData, layerData: TiledLayerData) { constructor(tilemapData: TiledTilemapData) {
super(); super();
this.tilesets = new Array<Tileset>(); this.tilesets = new Array<Tileset>();
this.worldSize = new Vec2(0, 0); this.worldSize = new Vec2(0, 0);
this.tileSize = new Vec2(0, 0); this.tileSize = new Vec2(0, 0);
this.parseTilemapData(tilemapData, layerData); this.parseTilemapData(tilemapData);
this.scale = new Vec2(4, 4); this.scale = new Vec2(4, 4);
} }
isCollidable(): boolean {
return this.collidable;
}
isVisible(): boolean {
return this.visible;
}
getTilesets(): Tileset[] { getTilesets(): Tileset[] {
return this.tilesets; return this.tilesets;
} }
@ -56,24 +46,11 @@ export default abstract class Tilemap extends GameNode {
abstract getTileAt(worldCoords: Vec2): number; 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 * Sets up the tileset using the data loaded from file
*/ */
// TODO: This shouldn't use tiled data specifically - it should be more general // 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; abstract render(ctx: CanvasRenderingContext2D, origin: Vec2, viewportSize: Vec2): void;
} }

View File

@ -2,27 +2,33 @@ import Tilemap from "../Tilemap";
import Vec2 from "../../DataTypes/Vec2"; import Vec2 from "../../DataTypes/Vec2";
import { TiledTilemapData, TiledLayerData } from "../../DataTypes/Tilesets/TiledData"; import { TiledTilemapData, TiledLayerData } from "../../DataTypes/Tilesets/TiledData";
import Tileset from "../../DataTypes/Tilesets/Tileset"; import Tileset from "../../DataTypes/Tilesets/Tileset";
import TileLayer from "../../DataTypes/Tilesets/TileLayer";
export default class OrthogonalTilemap extends Tilemap { 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.worldSize.set(tilemapData.width, tilemapData.height);
this.tileSize.set(tilemapData.tilewidth, tilemapData.tileheight); this.tileSize.set(tilemapData.tilewidth, tilemapData.tileheight);
this.data = layer.data; for(let layerData of tilemapData.layers){
this.collisionData = this.data.map(tile => tile !== 0 ? 1 : 0); let layer = new TileLayer();
this.visible = layer.visible; layer.data = layer.data;
this.collidable = false; layer.visible = layer.visible;
if(layer.properties){ layer.collidable = false;
for(let item of layer.properties){ if(layerData.properties){
for(let item of layerData.properties){
if(item.name === "Collidable"){ if(item.name === "Collidable"){
this.collidable = item.value; layer.collidable = item.value;
} }
} }
} }
this.layers.push(layer);
}
tilemapData.tilesets.forEach(tilesetData => this.tilesets.push(new Tileset(tilesetData))); 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 { getTileAt(worldCoords: Vec2): number {
let localCoords = this.getColRowAt(worldCoords); let localCoords = this.getColRowAt(worldCoords);
if(localCoords.x < 0 || localCoords.x >= this.worldSize.x || localCoords.y < 0 || localCoords.y >= this.worldSize.y){ 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 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 { isTileCollidable(indexOrCol: number, row?: number): boolean {
let index = 0;
if(row){ if(row){
if(indexOrCol < 0 || indexOrCol >= this.worldSize.x || row < 0 || row >= this.worldSize.y){ 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 // There are no tiles in negative positions or out of bounds positions
return false; return false;
} }
return this.collisionData[row * this.worldSize.x + indexOrCol] === 1 && this.collidable; index = row * this.worldSize.x + indexOrCol;
} else { } 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 // Tiles that don't exist aren't collidable
return false; 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? // TODO: Should this throw an error if someone tries to access an out of bounds value?
@ -56,18 +78,14 @@ export default class OrthogonalTilemap extends Tilemap {
return new Vec2(col, row); 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 {} update(deltaT: number): void {}
// TODO: Don't render tiles that aren't on screen // TODO: Don't render tiles that aren't on screen
render(ctx: CanvasRenderingContext2D, origin: Vec2, viewportSize: Vec2) { render(ctx: CanvasRenderingContext2D, origin: Vec2, viewportSize: Vec2) {
for(let i = 0; i < this.data.length; i++){ for(let layer of this.layers){
let tileIndex = this.data[i]; if(layer.visible){
for(let i = 0; i < layer.data.length; i++){
let tileIndex = layer.data[i];
for(let tileset of this.tilesets){ for(let tileset of this.tilesets){
if(tileset.hasTile(tileIndex)){ if(tileset.hasTile(tileIndex)){
@ -77,3 +95,5 @@ export default class OrthogonalTilemap extends Tilemap {
} }
} }
} }
}
}

View File

@ -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 { export default class ResourceManager {
private static instance: ResourceManager; private static instance: ResourceManager;
private constructor(){}; private loading: boolean;
private imagesLoaded: number;
private imagesToLoad: number;
private imageLoadingQueue: Queue<{key: string, path: string}>;
private images: Map<HTMLImageElement>;
private tilemapsLoaded: number;
private tilemapsToLoad: number;
private tilemapLoadingQueue: Queue<{key: string, path: string}>;
private tilemaps: Map<TiledTilemapData>;
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 { static getInstance(): ResourceManager {
if(!this.instance){ if(!this.instance){
@ -11,11 +41,128 @@ export default class ResourceManager {
return this.instance; return this.instance;
} }
public loadTilemap(pathToTilemapJSON: string, callback: Function): void { public image(key: string, path: string): void {
this.loadTextFile(pathToTilemapJSON, (fileText: string) => { this.imageLoadingQueue.enqueue({key: key, path: path});
let tilemapObject = JSON.parse(fileText); }
callback(tilemapObject);
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 = <TiledTilemapData>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 { private loadTextFile(textFilePath: string, callback: Function): void {
@ -29,15 +176,4 @@ export default class ResourceManager {
}; };
xobj.send(null); 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;
}
} }

View File

@ -1,11 +1,11 @@
import Scene from "../Scene"; import Layer from "../Layer";
import Viewport from "../../SceneGraph/Viewport"; import Viewport from "../../SceneGraph/Viewport";
import CanvasItem from "../../Nodes/CanvasNode" import CanvasItem from "../../Nodes/CanvasNode"
export default class CanvasNodeFactory { export default class CanvasNodeFactory {
private scene: Scene; private scene: Layer;
constructor(scene: Scene){ constructor(scene: Layer){
this.scene = scene; this.scene = scene;
} }

View File

@ -1,14 +1,14 @@
import Scene from "../Scene"; import Layer from "../Layer";
import Viewport from "../../SceneGraph/Viewport"; import Viewport from "../../SceneGraph/Viewport";
import PhysicsNode from "../../Physics/PhysicsNode"; import PhysicsNode from "../../Physics/PhysicsNode";
import PhysicsManager from "../../Physics/PhysicsManager"; import PhysicsManager from "../../Physics/PhysicsManager";
import Tilemap from "../../Nodes/Tilemap"; import Tilemap from "../../Nodes/Tilemap";
export default class PhysicsNodeFactory { export default class PhysicsNodeFactory {
private scene: Scene; private scene: Layer;
private physicsManager: PhysicsManager; private physicsManager: PhysicsManager;
constructor(scene: Scene, physicsManager: PhysicsManager){ constructor(scene: Layer, physicsManager: PhysicsManager){
this.scene = scene; this.scene = scene;
this.physicsManager = physicsManager; this.physicsManager = physicsManager;
} }

View File

@ -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<T extends Tilemap>(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);
// })
// });
// }
// });
}
}

View File

@ -4,15 +4,15 @@ import SceneGraph from "../SceneGraph/SceneGraph";
import SceneGraphArray from "../SceneGraph/SceneGraphArray"; import SceneGraphArray from "../SceneGraph/SceneGraphArray";
import CanvasNode from "../Nodes/CanvasNode"; import CanvasNode from "../Nodes/CanvasNode";
import CanvasNodeFactory from "./Factories/CanvasNodeFactory"; import CanvasNodeFactory from "./Factories/CanvasNodeFactory";
import GameState from "./GameState"; import Scene from "./Scene";
import Tilemap from "../Nodes/Tilemap"; import Tilemap from "../Nodes/Tilemap";
import TilemapFactory from "./Factories/TilemapFactory"; import TilemapFactory from "./Factories/TilemapFactory";
import PhysicsManager from "../Physics/PhysicsManager"; import PhysicsManager from "../Physics/PhysicsManager";
import PhysicsNodeFactory from "./Factories/PhysicsNodeFactory"; import PhysicsNodeFactory from "./Factories/PhysicsNodeFactory";
import MathUtils from "../Utils/MathUtils"; import MathUtils from "../Utils/MathUtils";
export default class Scene { export default class Layer {
private gameState: GameState; private gameState: Scene;
private viewport: Viewport private viewport: Viewport
private parallax: Vec2; private parallax: Vec2;
private sceneGraph: SceneGraph; private sceneGraph: SceneGraph;
@ -27,7 +27,7 @@ export default class Scene {
public tilemap: TilemapFactory; public tilemap: TilemapFactory;
public physics: PhysicsNodeFactory; public physics: PhysicsNodeFactory;
constructor(viewport: Viewport, gameState: GameState){ constructor(viewport: Viewport, gameState: Scene){
this.gameState = gameState; this.gameState = gameState;
this.viewport = viewport; this.viewport = viewport;
this.parallax = new Vec2(1, 1); this.parallax = new Vec2(1, 1);
@ -114,9 +114,7 @@ export default class Scene {
// Render tilemaps // Render tilemaps
this.tilemaps.forEach(tilemap => { this.tilemaps.forEach(tilemap => {
if(tilemap.isReady() && tilemap.isVisible()){
tilemap.render(ctx, origin, size); tilemap.render(ctx, origin, size);
}
}); });
// Render visible set // Render visible set

41
src/Scene/Scene.ts Normal file
View File

@ -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<Layer>;
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));
}
}

39
src/Scene/SceneManager.ts Normal file
View File

@ -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<T extends Scene>(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);
}
}
}

View File

@ -2,15 +2,15 @@ import Viewport from "./Viewport";
import CanvasNode from "../Nodes/CanvasNode"; import CanvasNode from "../Nodes/CanvasNode";
import Map from "../DataTypes/Map"; import Map from "../DataTypes/Map";
import Vec2 from "../DataTypes/Vec2"; import Vec2 from "../DataTypes/Vec2";
import Scene from "../GameState/Scene"; import Layer from "../Scene/Layer";
export default abstract class SceneGraph{ export default abstract class SceneGraph{
protected viewport: Viewport; protected viewport: Viewport;
protected nodeMap: Map<CanvasNode>; protected nodeMap: Map<CanvasNode>;
protected idCounter: number; protected idCounter: number;
protected scene: Scene; protected scene: Layer;
constructor(viewport: Viewport, scene: Scene){ constructor(viewport: Viewport, scene: Layer){
this.viewport = viewport; this.viewport = viewport;
this.scene = scene; this.scene = scene;
this.nodeMap = new Map<CanvasNode>(); this.nodeMap = new Map<CanvasNode>();

View File

@ -1,13 +1,13 @@
import SceneGraph from "./SceneGraph"; import SceneGraph from "./SceneGraph";
import CanvasNode from "../Nodes/CanvasNode"; import CanvasNode from "../Nodes/CanvasNode";
import Viewport from "./Viewport"; import Viewport from "./Viewport";
import Scene from "../GameState/Scene"; import Layer from "../Scene/Layer";
export default class SceneGraphArray extends SceneGraph{ export default class SceneGraphArray extends SceneGraph{
private nodeList: Array<CanvasNode>; private nodeList: Array<CanvasNode>;
private turnOffViewportCulling_demoTool: boolean; private turnOffViewportCulling_demoTool: boolean;
constructor(viewport: Viewport, scene: Scene){ constructor(viewport: Viewport, scene: Layer){
super(viewport, scene); super(viewport, scene);
this.nodeList = new Array<CanvasNode>(); this.nodeList = new Array<CanvasNode>();

View File

@ -9,80 +9,80 @@ import OrthogonalTilemap from "./Nodes/Tilemaps/OrthogonalTilemap";
function main(){ function main(){
// Create the game object // Create the game object
let game = new GameLoop(); let game = new GameLoop();
let gameState = game.getGameState(); let gameState = game.getSceneManager();
let backgroundScene = gameState.createScene(); // let backgroundScene = gameState.createScene();
backgroundScene.setParallax(0.5, 0.8); // backgroundScene.setParallax(0.5, 0.8);
backgroundScene.setAlpha(0.5); // backgroundScene.setAlpha(0.5);
let mainScene = gameState.createScene(); // let mainScene = gameState.createScene();
let uiLayer = gameState.createScene(); // let uiLayer = gameState.createScene();
uiLayer.setParallax(0, 0); // uiLayer.setParallax(0, 0);
let pauseMenu = gameState.createScene(); // let pauseMenu = gameState.createScene();
pauseMenu.setParallax(0, 0); // pauseMenu.setParallax(0, 0);
// Initialize GameObjects // // Initialize GameObjects
let recordButton = uiLayer.canvasNode.add(Button); // let recordButton = uiLayer.canvasNode.add(Button);
recordButton.setSize(100, 50); // recordButton.setSize(100, 50);
recordButton.setText("Record"); // recordButton.setText("Record");
recordButton.setPosition(400, 30); // recordButton.setPosition(400, 30);
recordButton.onClickEventId = "record_button_press"; // recordButton.onClickEventId = "record_button_press";
let stopButton = uiLayer.canvasNode.add(Button); // let stopButton = uiLayer.canvasNode.add(Button);
stopButton.setSize(100, 50); // stopButton.setSize(100, 50);
stopButton.setText("Stop"); // stopButton.setText("Stop");
stopButton.setPosition(550, 30); // stopButton.setPosition(550, 30);
stopButton.onClickEventId = "stop_button_press"; // stopButton.onClickEventId = "stop_button_press";
let playButton = uiLayer.canvasNode.add(Button); // let playButton = uiLayer.canvasNode.add(Button);
playButton.setSize(100, 50); // playButton.setSize(100, 50);
playButton.setText("Play"); // playButton.setText("Play");
playButton.setPosition(700, 30); // playButton.setPosition(700, 30);
playButton.onClickEventId = "play_button_press"; // playButton.onClickEventId = "play_button_press";
let cycleFramerateButton = uiLayer.canvasNode.add(Button); // let cycleFramerateButton = uiLayer.canvasNode.add(Button);
cycleFramerateButton.setSize(150, 50); // cycleFramerateButton.setSize(150, 50);
cycleFramerateButton.setText("Cycle FPS"); // cycleFramerateButton.setText("Cycle FPS");
cycleFramerateButton.setPosition(5, 400); // cycleFramerateButton.setPosition(5, 400);
let i = 0; // let i = 0;
let fps = [15, 30, 60]; // let fps = [15, 30, 60];
cycleFramerateButton.onClick = () => { // cycleFramerateButton.onClick = () => {
game.setMaxFPS(fps[i]); // game.setMaxFPS(fps[i]);
i = (i + 1) % 3; // i = (i + 1) % 3;
} // }
let pauseButton = uiLayer.canvasNode.add(Button); // let pauseButton = uiLayer.canvasNode.add(Button);
pauseButton.setSize(100, 50); // pauseButton.setSize(100, 50);
pauseButton.setText("Pause"); // pauseButton.setText("Pause");
pauseButton.setPosition(700, 400); // pauseButton.setPosition(700, 400);
pauseButton.onClick = () => { // pauseButton.onClick = () => {
mainScene.setPaused(true); // mainScene.setPaused(true);
pauseMenu.enable(); // pauseMenu.enable();
} // }
let modalBackground = pauseMenu.canvasNode.add(UIElement); // let modalBackground = pauseMenu.canvasNode.add(UIElement);
modalBackground.setSize(400, 200); // modalBackground.setSize(400, 200);
modalBackground.setBackgroundColor(new Color(0, 0, 0, 0.4)); // modalBackground.setBackgroundColor(new Color(0, 0, 0, 0.4));
modalBackground.setPosition(200, 100); // modalBackground.setPosition(200, 100);
let resumeButton = pauseMenu.canvasNode.add(Button); // let resumeButton = pauseMenu.canvasNode.add(Button);
resumeButton.setSize(100, 50); // resumeButton.setSize(100, 50);
resumeButton.setText("Resume"); // resumeButton.setText("Resume");
resumeButton.setPosition(400, 200); // resumeButton.setPosition(400, 200);
resumeButton.onClick = () => { // resumeButton.onClick = () => {
mainScene.setPaused(false); // mainScene.setPaused(false);
pauseMenu.disable(); // pauseMenu.disable();
} // }
backgroundScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/Background.json"); // backgroundScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/Background.json");
mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/Platformer.json"); // mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/Platformer.json");
let player = mainScene.physics.add(Player, "platformer"); // let player = mainScene.physics.add(Player, "platformer");
// mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/TopDown.json"); // // mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/TopDown.json");
// let player = mainScene.physics.add(Player, "topdown"); // // let player = mainScene.physics.add(Player, "topdown");
mainScene.getViewport().follow(player); // mainScene.getViewport().follow(player);
pauseMenu.disable(); // pauseMenu.disable();
game.start(); game.start();
} }

View File

@ -18,11 +18,11 @@
"src/Events/GameEvent.ts", "src/Events/GameEvent.ts",
"src/Events/Receiver.ts", "src/Events/Receiver.ts",
"src/GameState/Factories/CanvasNodeFactory.ts", "src/Scene/Factories/CanvasNodeFactory.ts",
"src/GameState/Factories/PhysicsNodeFactory.ts", "src/Scene/Factories/PhysicsNodeFactory.ts",
"src/GameState/Factories/TilemapFactory.ts", "src/Scene/Factories/TilemapFactory.ts",
"src/GameState/GameState.ts", "src/Scene/Scene.ts",
"src/GameState/Scene.ts", "src/Scene/Layer.ts",
"src/Input/InputHandler.ts", "src/Input/InputHandler.ts",
"src/Input/InputReceiver.ts", "src/Input/InputReceiver.ts",