From 2093d8e4abdef115f3bc507e6dcf3985b1e36c96 Mon Sep 17 00:00:00 2001 From: Joe Weaver Date: Sun, 6 Sep 2020 18:07:09 -0400 Subject: [PATCH] reworked scenes --- src/ColoredCircle.ts | 33 ------- src/DataTypes/Tilesets/TileLayer.ts | 5 -- src/DataTypes/Vec2.ts | 6 ++ src/Loop/GameLoop.ts | 2 +- src/MainScene.ts | 101 ++++++++++++++++++++++ src/Nodes/CanvasNode.ts | 2 +- src/Nodes/GameNode.ts | 21 ++++- src/Nodes/Graphic.ts | 11 +++ src/Nodes/Graphics/Rect.ts | 21 +++++ src/Nodes/Tilemap.ts | 21 +++-- src/Nodes/Tilemaps/OrthogonalTilemap.ts | 65 ++++++-------- src/Nodes/UIElement.ts | 9 +- src/Physics/PhysicsManager.ts | 4 +- src/Player.ts | 7 +- src/PlayerSprite.ts | 18 ---- src/ResourceManager/ResourceManager.ts | 26 ++---- src/Scene/Factories/CanvasNodeFactory.ts | 51 +++++++++-- src/Scene/Factories/FactoryManager.ts | 26 ++++++ src/Scene/Factories/PhysicsNodeFactory.ts | 25 +++--- src/Scene/Factories/TilemapFactory.ts | 74 +++++++++------- src/Scene/Layer.ts | 93 ++++---------------- src/Scene/Layers/ObjectLayer.ts | 3 + src/Scene/Layers/TiledLayer.ts | 6 ++ src/Scene/Layers/UILayer.ts | 0 src/Scene/Scene.ts | 86 +++++++++++++++--- src/Scene/SceneManager.ts | 9 +- src/SceneGraph/SceneGraph.ts | 6 +- src/SceneGraph/SceneGraphArray.ts | 10 ++- src/SceneGraph/Viewport.ts | 3 +- src/main.ts | 83 +----------------- 30 files changed, 468 insertions(+), 359 deletions(-) delete mode 100644 src/ColoredCircle.ts delete mode 100644 src/DataTypes/Tilesets/TileLayer.ts create mode 100644 src/MainScene.ts create mode 100644 src/Nodes/Graphic.ts create mode 100644 src/Nodes/Graphics/Rect.ts delete mode 100644 src/PlayerSprite.ts create mode 100644 src/Scene/Factories/FactoryManager.ts create mode 100644 src/Scene/Layers/ObjectLayer.ts create mode 100644 src/Scene/Layers/TiledLayer.ts create mode 100644 src/Scene/Layers/UILayer.ts diff --git a/src/ColoredCircle.ts b/src/ColoredCircle.ts deleted file mode 100644 index f9f94c0..0000000 --- a/src/ColoredCircle.ts +++ /dev/null @@ -1,33 +0,0 @@ -import CanvasNode from "./Nodes/CanvasNode"; -import Color from "./Utils/Color"; -import Vec2 from "./DataTypes/Vec2"; -import RandUtils from "./Utils/RandUtils"; - -export default class ColoredCircle extends CanvasNode{ - private color: Color; - - constructor(){ - super(); - this.position = new Vec2(RandUtils.randInt(0, 1000), RandUtils.randInt(0, 1000)); - this.color = RandUtils.randColor(); - this.size = new Vec2(50, 50); - } - - setColor(color: Color): void { - this.color = color; - } - - getColor(): Color { - return this.color; - } - - update(deltaT: number): void {} - - render(ctx: CanvasRenderingContext2D, origin: Vec2){ - ctx.fillStyle = this.color.toStringRGBA(); - ctx.beginPath(); - ctx.arc(this.position.x + this.size.x/2 - origin.x, this.position.y + this.size.y/2 - origin.y, this.size.x/2, 0, Math.PI*2, false); - ctx.fill(); - ctx.closePath(); - } -} \ No newline at end of file diff --git a/src/DataTypes/Tilesets/TileLayer.ts b/src/DataTypes/Tilesets/TileLayer.ts deleted file mode 100644 index 77803ea..0000000 --- a/src/DataTypes/Tilesets/TileLayer.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default class TileLayer { - public data: Array; - public collidable: boolean; - public visible: boolean; -} \ No newline at end of file diff --git a/src/DataTypes/Vec2.ts b/src/DataTypes/Vec2.ts index 462dafa..aecfc6d 100644 --- a/src/DataTypes/Vec2.ts +++ b/src/DataTypes/Vec2.ts @@ -73,6 +73,12 @@ export default class Vec2 { return this; } + mult(other: Vec2): Vec2 { + this.x *= other.x; + this.y *= other.y; + return this; + } + toString(): string { return this.toFixed(); } diff --git a/src/Loop/GameLoop.ts b/src/Loop/GameLoop.ts index 689ae9f..7db6974 100644 --- a/src/Loop/GameLoop.ts +++ b/src/Loop/GameLoop.ts @@ -67,7 +67,7 @@ export default class GameLoop{ this.inputReceiver.setViewport(this.viewport); this.recorder = new Recorder(); this.resourceManager = ResourceManager.getInstance(); - this.sceneManager = new SceneManager(this.viewport); + this.sceneManager = new SceneManager(this.viewport, this); } private initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D { diff --git a/src/MainScene.ts b/src/MainScene.ts new file mode 100644 index 0000000..06d318d --- /dev/null +++ b/src/MainScene.ts @@ -0,0 +1,101 @@ +import Scene from "./Scene/Scene"; +import OrthogonalTilemap from "./Nodes/Tilemaps/OrthogonalTilemap"; +import Player from "./Player"; +import Rect from "./Nodes/Graphics/Rect"; +import Color from "./Utils/Color"; +import Vec2 from "./DataTypes/Vec2"; +import UIElement from "./Nodes/UIElement"; +import Button from "./Nodes/UIElements/Button"; +import Layer from "./Scene/Layer"; + +export default class MainScene extends Scene { + + loadScene(){ + this.load.tilemap("platformer", "assets/tilemaps/Platformer.json"); + this.load.tilemap("background", "assets/tilemaps/Background.json"); + } + + startScene(){ + // Add the background tilemap + let backgroundTilemap = this.add.tilemap("background", OrthogonalTilemap)[0]; + // ...and make it have parallax + backgroundTilemap.getLayer().setParallax(0.5, 0.8); + backgroundTilemap.getLayer().setAlpha(0.5); + + // Add the tilemap + this.add.tilemap("platformer", OrthogonalTilemap); + + // Create the main game layer + let mainLayer = this.addLayer(); + + // Add a player + let player = this.add.physics(Player, mainLayer, "platformer"); + let playerSprite = this.add.graphic(Rect, mainLayer, new Vec2(0, 0), new Vec2(50, 50)); + playerSprite.setColor(new Color(255, 0, 0)); + player.setSprite(playerSprite); + + + this.viewport.follow(player); + + // Initialize UI + let uiLayer = this.addLayer(); + uiLayer.setParallax(0, 0); + + let recordButton = this.add.uiElement(Button, uiLayer); + recordButton.setSize(100, 50); + recordButton.setText("Record"); + recordButton.setPosition(400, 30); + recordButton.onClickEventId = "record_button_press"; + + let stopButton = this.add.uiElement(Button, uiLayer); + stopButton.setSize(100, 50); + stopButton.setText("Stop"); + stopButton.setPosition(550, 30); + stopButton.onClickEventId = "stop_button_press"; + + let playButton = this.add.uiElement(Button, uiLayer); + playButton.setSize(100, 50); + playButton.setText("Play"); + playButton.setPosition(700, 30); + playButton.onClickEventId = "play_button_press"; + + let cycleFramerateButton = this.add.uiElement(Button, uiLayer); + cycleFramerateButton.setSize(150, 50); + cycleFramerateButton.setText("Cycle FPS"); + cycleFramerateButton.setPosition(5, 400); + let i = 0; + let fps = [15, 30, 60]; + cycleFramerateButton.onClick = () => { + this.game.setMaxFPS(fps[i]); + i = (i + 1) % 3; + } + + // Pause Menu + let pauseLayer = this.addLayer(); + pauseLayer.setParallax(0, 0); + pauseLayer.disable(); + + let pauseButton = this.add.uiElement(Button, uiLayer); + pauseButton.setSize(100, 50); + pauseButton.setText("Pause"); + pauseButton.setPosition(700, 400); + pauseButton.onClick = () => { + this.layers.forEach((layer: Layer) => layer.setPaused(true)); + pauseLayer.enable(); + } + + let modalBackground = this.add.uiElement(UIElement, pauseLayer); + modalBackground.setSize(400, 200); + modalBackground.setBackgroundColor(new Color(0, 0, 0, 0.4)); + modalBackground.setPosition(200, 100); + + let resumeButton = this.add.uiElement(Button, pauseLayer); + resumeButton.setSize(100, 50); + resumeButton.setText("Resume"); + resumeButton.setPosition(400, 200); + resumeButton.onClick = () => { + this.layers.forEach((layer: Layer) => layer.setPaused(false)); + pauseLayer.disable(); + } + } +} \ No newline at end of file diff --git a/src/Nodes/CanvasNode.ts b/src/Nodes/CanvasNode.ts index 77bbd62..da52122 100644 --- a/src/Nodes/CanvasNode.ts +++ b/src/Nodes/CanvasNode.ts @@ -31,5 +31,5 @@ export default abstract class CanvasNode extends GameNode{ return false; } - abstract render(ctx: CanvasRenderingContext2D, origin: Vec2): void; + abstract render(ctx: CanvasRenderingContext2D): void; } \ No newline at end of file diff --git a/src/Nodes/GameNode.ts b/src/Nodes/GameNode.ts index 082fc45..1e32abe 100644 --- a/src/Nodes/GameNode.ts +++ b/src/Nodes/GameNode.ts @@ -4,6 +4,7 @@ import Vec2 from "../DataTypes/Vec2"; import Map from "../DataTypes/Map"; import Receiver from "../Events/Receiver"; import GameEvent from "../Events/GameEvent"; +import Scene from "../Scene/Scene"; import Layer from "../Scene/Layer"; export default abstract class GameNode{ @@ -11,7 +12,8 @@ export default abstract class GameNode{ protected input: InputReceiver; protected position: Vec2; private receiver: Receiver; - protected scene: Layer; + protected scene: Scene; + protected layer: Layer; constructor(){ this.eventQueue = EventQueue.getInstance(); @@ -19,13 +21,21 @@ export default abstract class GameNode{ this.position = new Vec2(0, 0); } - init(scene: Layer){ + setScene(scene: Scene): void { this.scene = scene; } - getScene(): Layer { + getScene(): Scene { return this.scene; } + + setLayer(layer: Layer): void { + this.layer = layer; + } + + getLayer(): Layer { + return this.layer; + } getPosition(): Vec2 { return this.position; @@ -48,5 +58,10 @@ export default abstract class GameNode{ this.eventQueue.addEvent(event); } + // TODO - This doesn't seem ideal. Is there a better way to do this? + getViewportOriginWithParallax(){ + return this.scene.getViewport().getPosition().clone().mult(this.layer.getParallax()); + } + abstract update(deltaT: number): void; } \ No newline at end of file diff --git a/src/Nodes/Graphic.ts b/src/Nodes/Graphic.ts new file mode 100644 index 0000000..40f1089 --- /dev/null +++ b/src/Nodes/Graphic.ts @@ -0,0 +1,11 @@ +import CanvasNode from "./CanvasNode"; +import Color from "../Utils/Color"; + +export default abstract class Graphic extends CanvasNode { + + color: Color; + + setColor(color: Color){ + this.color = color; + } +} \ No newline at end of file diff --git a/src/Nodes/Graphics/Rect.ts b/src/Nodes/Graphics/Rect.ts new file mode 100644 index 0000000..f488962 --- /dev/null +++ b/src/Nodes/Graphics/Rect.ts @@ -0,0 +1,21 @@ +import Graphic from "../Graphic"; +import Vec2 from "../../DataTypes/Vec2"; + +export default class Rect extends Graphic { + + constructor(position: Vec2, size: Vec2){ + super(); + this.position = position; + this.size = size; + } + + update(deltaT: number): void {} + + render(ctx: CanvasRenderingContext2D): void { + let origin = this.getViewportOriginWithParallax(); + + ctx.fillStyle = this.color.toStringRGBA(); + ctx.fillRect(this.position.x - origin.x, this.position.y - origin.y, this.size.x, this.size.y); + } + +} \ No newline at end of file diff --git a/src/Nodes/Tilemap.ts b/src/Nodes/Tilemap.ts index 06d1d3e..fa10074 100644 --- a/src/Nodes/Tilemap.ts +++ b/src/Nodes/Tilemap.ts @@ -2,7 +2,6 @@ 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 @@ -12,15 +11,17 @@ export default abstract class Tilemap extends GameNode { protected worldSize: Vec2; protected tileSize: Vec2; protected scale: Vec2; - protected layers: Array; + public data: Array; + public collidable: boolean; + public visible: boolean; // TODO: Make this no longer be specific to Tiled - constructor(tilemapData: TiledTilemapData) { + constructor(tilemapData: TiledTilemapData, layer: TiledLayerData) { super(); this.tilesets = new Array(); this.worldSize = new Vec2(0, 0); this.tileSize = new Vec2(0, 0); - this.parseTilemapData(tilemapData); + this.parseTilemapData(tilemapData, layer); this.scale = new Vec2(4, 4); } @@ -44,13 +45,21 @@ export default abstract class Tilemap extends GameNode { this.scale = scale; } + isCollidable(): boolean { + return this.collidable; + } + + isVisible(): boolean { + return this.visible; + } + abstract getTileAt(worldCoords: Vec2): number; /** * 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): void; + protected abstract parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void; - abstract render(ctx: CanvasRenderingContext2D, origin: Vec2, viewportSize: Vec2): void; + abstract render(ctx: CanvasRenderingContext2D): void; } \ No newline at end of file diff --git a/src/Nodes/Tilemaps/OrthogonalTilemap.ts b/src/Nodes/Tilemaps/OrthogonalTilemap.ts index 0f131b5..c2dfdb2 100644 --- a/src/Nodes/Tilemaps/OrthogonalTilemap.ts +++ b/src/Nodes/Tilemaps/OrthogonalTilemap.ts @@ -2,33 +2,27 @@ 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): void { + protected parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void { this.worldSize.set(tilemapData.width, tilemapData.height); this.tileSize.set(tilemapData.tilewidth, tilemapData.tileheight); - 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.data = layer.data; + this.visible = layer.visible; + this.collidable = false; + if(layer.properties){ + for(let item of layer.properties){ + if(item.name === "Collidable"){ + this.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){ @@ -36,14 +30,7 @@ export default class OrthogonalTilemap extends Tilemap { return 0; } - // 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; + return this.data[localCoords.y * this.worldSize.x + localCoords.x] } isTileCollidable(indexOrCol: number, row?: number): boolean { @@ -55,20 +42,15 @@ export default class OrthogonalTilemap extends Tilemap { } index = row * this.worldSize.x + indexOrCol; } else { - if(indexOrCol < 0 || indexOrCol >= this.layers[0].data.length){ + if(indexOrCol < 0 || indexOrCol >= this.data.length){ // Tiles that don't exist aren't collidable return false; } index = indexOrCol; } - for(let layer of this.layers){ - if(layer.data[index] !== 0 && layer.collidable){ - return true; - } - } - - return false; + // TODO - Currently, all tiles in a collidable layer are collidable + return this.data[index] !== 0 && this.collidable; } // TODO: Should this throw an error if someone tries to access an out of bounds value? @@ -81,19 +63,24 @@ export default class OrthogonalTilemap extends Tilemap { update(deltaT: number): void {} // TODO: Don't render tiles that aren't on screen - render(ctx: CanvasRenderingContext2D, origin: Vec2, viewportSize: Vec2) { - for(let layer of this.layers){ - if(layer.visible){ - for(let i = 0; i < layer.data.length; i++){ - let tileIndex = layer.data[i]; + render(ctx: CanvasRenderingContext2D) { + let previousAlpha = ctx.globalAlpha; + ctx.globalAlpha = this.getLayer().getAlpha(); + + let origin = this.getViewportOriginWithParallax(); - for(let tileset of this.tilesets){ - if(tileset.hasTile(tileIndex)){ - tileset.renderTile(ctx, tileIndex, i, this.worldSize, origin, this.scale); - } + if(this.visible){ + for(let i = 0; i < this.data.length; i++){ + let tileIndex = this.data[i]; + + for(let tileset of this.tilesets){ + if(tileset.hasTile(tileIndex)){ + tileset.renderTile(ctx, tileIndex, i, this.worldSize, origin, this.scale); } } } } + + ctx.globalAlpha = previousAlpha; } } \ No newline at end of file diff --git a/src/Nodes/UIElement.ts b/src/Nodes/UIElement.ts index 201c9af..d8f59b4 100644 --- a/src/Nodes/UIElement.ts +++ b/src/Nodes/UIElement.ts @@ -155,7 +155,12 @@ export default class UIElement extends CanvasNode{ return this.textColor.toStringRGBA(); } - render(ctx: CanvasRenderingContext2D, origin: Vec2): void { + render(ctx: CanvasRenderingContext2D): void { + let previousAlpha = ctx.globalAlpha; + ctx.globalAlpha = this.getLayer().getAlpha(); + + let origin = this.scene.getViewport().getPosition().clone().mult(this.layer.getParallax()); + ctx.font = this.fontSize + "px " + this.font; let offset = this.calculateOffset(ctx); @@ -168,5 +173,7 @@ export default class UIElement extends CanvasNode{ ctx.fillStyle = this.calculateTextColor(); ctx.fillText(this.text, this.position.x + offset.x - origin.x, this.position.y + offset.y - origin.y); + + ctx.globalAlpha = previousAlpha; } } \ No newline at end of file diff --git a/src/Physics/PhysicsManager.ts b/src/Physics/PhysicsManager.ts index 9754169..0b932fc 100644 --- a/src/Physics/PhysicsManager.ts +++ b/src/Physics/PhysicsManager.ts @@ -215,7 +215,9 @@ export default class PhysicsManager { update(deltaT: number): void { for(let node of this.physicsNodes){ - node.update(deltaT); + if(!node.getLayer().isPaused()){ + node.update(deltaT); + } } let staticSet = new Array(); diff --git a/src/Player.ts b/src/Player.ts index 8783b88..912f5c4 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -2,7 +2,7 @@ import PhysicsNode from "./Physics/PhysicsNode"; import Vec2 from "./DataTypes/Vec2"; import Debug from "./Debug/Debug"; import AABB from "./Physics/Colliders/AABB"; -import PlayerSprite from "./PlayerSprite"; +import CanvasNode from "./Nodes/CanvasNode"; export default class Player extends PhysicsNode { velocity: Vec2; @@ -26,8 +26,9 @@ export default class Player extends PhysicsNode { } } - create(): void { - let sprite = this.scene.canvasNode.add(PlayerSprite); + create(): void {}; + + setSprite(sprite: CanvasNode): void { sprite.setPosition(this.position); sprite.setSize(this.size); this.children.push(sprite); diff --git a/src/PlayerSprite.ts b/src/PlayerSprite.ts deleted file mode 100644 index 39e2efa..0000000 --- a/src/PlayerSprite.ts +++ /dev/null @@ -1,18 +0,0 @@ -import CanvasNode from "./Nodes/CanvasNode"; -import Vec2 from "./DataTypes/Vec2"; -import Debug from "./Debug/Debug"; - -export default class Player extends CanvasNode{ - debug: Debug; - - constructor(){ - super(); - }; - - update(deltaT: number): void {} - - render(ctx: CanvasRenderingContext2D, origin: Vec2){ - ctx.fillStyle = "#FF0000"; - ctx.fillRect(this.position.x - origin.x, this.position.y - origin.y, this.size.x, this.size.y); - } -} \ No newline at end of file diff --git a/src/ResourceManager/ResourceManager.ts b/src/ResourceManager/ResourceManager.ts index eda4cc9..d82e865 100644 --- a/src/ResourceManager/ResourceManager.ts +++ b/src/ResourceManager/ResourceManager.ts @@ -45,6 +45,10 @@ export default class ResourceManager { this.imageLoadingQueue.enqueue({key: key, path: path}); } + public getImage(key: string){ + return this.images.get(key); + } + public spritesheet(key: string, path: string, frames: {hFrames: number, vFrames: number}): void { } @@ -57,26 +61,10 @@ export default class ResourceManager { 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); - // }); - // }); + public getTilemap(key: string): TiledTilemapData{ + return this.tilemaps.get(key); } loadResourcesFromQueue(callback: Function): void { diff --git a/src/Scene/Factories/CanvasNodeFactory.ts b/src/Scene/Factories/CanvasNodeFactory.ts index cb1e998..add97e5 100644 --- a/src/Scene/Factories/CanvasNodeFactory.ts +++ b/src/Scene/Factories/CanvasNodeFactory.ts @@ -1,18 +1,55 @@ -import Layer from "../Layer"; -import Viewport from "../../SceneGraph/Viewport"; +import Scene from "../Scene"; import CanvasItem from "../../Nodes/CanvasNode" +import SceneGraph from "../../SceneGraph/SceneGraph"; +import UIElement from "../../Nodes/UIElement"; +import Layer from "../Layer"; +import Graphic from "../../Nodes/Graphic"; export default class CanvasNodeFactory { - private scene: Layer; + private scene: Scene; + private sceneGraph: SceneGraph; - constructor(scene: Layer){ + init(scene: Scene, sceneGraph: SceneGraph): void { this.scene = scene; + this.sceneGraph = sceneGraph; } - add(constr: new (...a: any) => T, ...args: any): T { + addUIElement = (constr: new (...a: any) => T, layer: Layer, ...args: any): T => { let instance = new constr(...args); - instance.init(this.scene); - this.scene.add(instance); + + // Add instance to scene + instance.setScene(this.scene); + this.sceneGraph.addNode(instance); + + // Add instance to layer + layer.addNode(instance); + + return instance; + } + + addSprite = (constr: new (...a: any) => T, layer: Layer, ...args: any): T => { + let instance = new constr(...args); + + // Add instance to scene + instance.setScene(this.scene); + this.sceneGraph.addNode(instance); + + // Add instance to layer + layer.addNode(instance); + + return instance; + } + + addGraphic = (constr: new (...a: any) => T, layer: Layer, ...args: any): T => { + let instance = new constr(...args); + + // Add instance to scene + instance.setScene(this.scene); + this.sceneGraph.addNode(instance); + + // Add instance to layer + layer.addNode(instance); + return instance; } } \ No newline at end of file diff --git a/src/Scene/Factories/FactoryManager.ts b/src/Scene/Factories/FactoryManager.ts new file mode 100644 index 0000000..8b420aa --- /dev/null +++ b/src/Scene/Factories/FactoryManager.ts @@ -0,0 +1,26 @@ +import Scene from "../Scene"; +import PhysicsNodeFactory from "./PhysicsNodeFactory"; +import CanvasNodeFactory from "./CanvasNodeFactory"; +import TilemapFactory from "./TilemapFactory"; +import PhysicsManager from "../../Physics/PhysicsManager"; +import SceneGraph from "../../SceneGraph/SceneGraph"; +import Tilemap from "../../Nodes/Tilemap"; + +export default class FactoryManager { + + private canvasNodeFactory: CanvasNodeFactory = new CanvasNodeFactory();; + private physicsNodeFactory: PhysicsNodeFactory = new PhysicsNodeFactory();; + private tilemapFactory: TilemapFactory = new TilemapFactory();; + + constructor(scene: Scene, sceneGraph: SceneGraph, physicsManager: PhysicsManager, tilemaps: Array){ + this.canvasNodeFactory.init(scene, sceneGraph); + this.physicsNodeFactory.init(scene, physicsManager); + this.tilemapFactory.init(scene, tilemaps, physicsManager); + } + + uiElement = this.canvasNodeFactory.addUIElement; + sprite = this.canvasNodeFactory.addSprite; + graphic = this.canvasNodeFactory.addGraphic; + physics = this.physicsNodeFactory.add; + tilemap = this.tilemapFactory.add; +} \ No newline at end of file diff --git a/src/Scene/Factories/PhysicsNodeFactory.ts b/src/Scene/Factories/PhysicsNodeFactory.ts index fc143cd..8ac9826 100644 --- a/src/Scene/Factories/PhysicsNodeFactory.ts +++ b/src/Scene/Factories/PhysicsNodeFactory.ts @@ -1,28 +1,27 @@ -import Layer from "../Layer"; -import Viewport from "../../SceneGraph/Viewport"; +import Scene from "../Scene"; import PhysicsNode from "../../Physics/PhysicsNode"; import PhysicsManager from "../../Physics/PhysicsManager"; -import Tilemap from "../../Nodes/Tilemap"; +import Layer from "../Layer"; export default class PhysicsNodeFactory { - private scene: Layer; + private scene: Scene; private physicsManager: PhysicsManager; - constructor(scene: Layer, physicsManager: PhysicsManager){ - this.scene = scene; + init(scene: Scene, physicsManager: PhysicsManager): void { + this.scene = scene; this.physicsManager = physicsManager; } - add(constr: new (...a: any) => T, ...args: any): T { + // TODO: Currently this doesn't care about layers + add = (constr: new (...a: any) => T, layer: Layer, ...args: any): T => { let instance = new constr(...args); - instance.init(this.scene); + instance.setScene(this.scene); instance.addManager(this.physicsManager); - instance.create(); + instance.create(); + + layer.addNode(instance); + this.physicsManager.add(instance); return instance; } - - addTilemap(tilemap: Tilemap): void { - this.physicsManager.addTilemap(tilemap); - } } \ No newline at end of file diff --git a/src/Scene/Factories/TilemapFactory.ts b/src/Scene/Factories/TilemapFactory.ts index e504350..6392755 100644 --- a/src/Scene/Factories/TilemapFactory.ts +++ b/src/Scene/Factories/TilemapFactory.ts @@ -1,45 +1,55 @@ -import Layer from "../Layer"; -import Viewport from "../../SceneGraph/Viewport"; +import Scene from "../Scene"; import Tilemap from "../../Nodes/Tilemap"; +import PhysicsManager from "../../Physics/PhysicsManager"; 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 scene: Scene; + private tilemaps: Array; + private physicsManager: PhysicsManager; private resourceManager: ResourceManager; - - constructor(scene: Layer){ + + init(scene: Scene, tilemaps: Array, physicsManager: PhysicsManager): void { this.scene = scene; + this.tilemaps = tilemaps; + this.physicsManager = physicsManager; 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 = (key: string, constr: new (...a: any) => T, ...args: any): Array => { + // Get Tilemap Data + let tilemapData = this.resourceManager.getTilemap(key); - // // Add to scene - // this.scene.addTilemap(tilemap); + // Get the return values + let tilemaps = new Array(); - // if(tilemap.isCollidable()){ - // // Register in physics as a tilemap - // this.scene.physics.addTilemap(tilemap); - // } + for(let layer of tilemapData.layers){ + // Create a new tilemap object for the layer + let tilemap = new constr(tilemapData, layer); + tilemap.setScene(this.scene); - // // 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); - // }) - // }); - // } - // }); + // Add tilemap to scene + this.tilemaps.push(tilemap); + + // Create a new layer in the scene + let sceneLayer = this.scene.addLayer(); + sceneLayer.addNode(tilemap); + + // Register tilemap with physics if it's collidable + if(tilemap.isCollidable()){ + this.physicsManager.addTilemap(tilemap); + } + + // Assign each tileset it's image + tilemap.getTilesets().forEach(tileset => { + let image = this.resourceManager.getImage(tileset.getImageUrl()); + tileset.setImage(image); + }); + + // Update the return value + tilemaps.push(tilemap); + } + + return tilemaps; } } \ No newline at end of file diff --git a/src/Scene/Layer.ts b/src/Scene/Layer.ts index 176d441..9ef9b4f 100644 --- a/src/Scene/Layer.ts +++ b/src/Scene/Layer.ts @@ -1,46 +1,23 @@ import Vec2 from "../DataTypes/Vec2"; -import Viewport from "../SceneGraph/Viewport"; -import SceneGraph from "../SceneGraph/SceneGraph"; -import SceneGraphArray from "../SceneGraph/SceneGraphArray"; -import CanvasNode from "../Nodes/CanvasNode"; -import CanvasNodeFactory from "./Factories/CanvasNodeFactory"; 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"; +import GameNode from "../Nodes/GameNode"; export default class Layer { - private gameState: Scene; - private viewport: Viewport - private parallax: Vec2; - private sceneGraph: SceneGraph; - private physicsManager: PhysicsManager; - private tilemaps: Array; - private paused: boolean; - private hidden: boolean; - private alpha: number; - - // Factories - public canvasNode: CanvasNodeFactory; - public tilemap: TilemapFactory; - public physics: PhysicsNodeFactory; + protected scene: Scene; + protected parallax: Vec2; + protected paused: boolean; + protected hidden: boolean; + protected alpha: number; + protected items: Array; - constructor(viewport: Viewport, gameState: Scene){ - this.gameState = gameState; - this.viewport = viewport; + constructor(scene: Scene){ + this.scene = scene; this.parallax = new Vec2(1, 1); - this.sceneGraph = new SceneGraphArray(this.viewport, this); - this.tilemaps = new Array(); this.paused = false; this.hidden = false; - this.physicsManager = new PhysicsManager(); - - // Factories - this.canvasNode = new CanvasNodeFactory(this); - this.tilemap = new TilemapFactory(this); - this.physics = new PhysicsNodeFactory(this, this.physicsManager); + this.alpha = 1; + this.items = new Array(); } setPaused(pauseValue: boolean): void { @@ -55,6 +32,10 @@ export default class Layer { this.alpha = MathUtils.clamp(alpha, 0, 1); } + getAlpha(): number { + return this.alpha; + } + setHidden(hidden: boolean): void { this.hidden = hidden; } @@ -72,10 +53,6 @@ export default class Layer { this.paused = false; this.hidden = false; } - - getViewport(): Viewport { - return this.viewport; - } setParallax(x: number, y: number): void { this.parallax.set(x, y); @@ -85,42 +62,10 @@ export default class Layer { return this.parallax; } - add(child: CanvasNode): void { - this.sceneGraph.addNode(child); + addNode(node: GameNode): void { + this.items.push(node); + node.setLayer(this); } - addTilemap(tilemap: Tilemap): void { - this.tilemaps.push(tilemap); - } - - update(deltaT: number): void { - if(!this.paused){ - this.viewport.update(deltaT); - this.physicsManager.update(deltaT); - this.sceneGraph.update(deltaT); - this.tilemaps.forEach((tilemap: Tilemap) => tilemap.update(deltaT)); - } - } - - render(ctx: CanvasRenderingContext2D): void { - if(!this.hidden){ - let previousAlpha = ctx.globalAlpha; - ctx.globalAlpha = this.alpha; - - let visibleSet = this.sceneGraph.getVisibleSet(); - let viewportOrigin = this.viewport.getPosition(); - let origin = new Vec2(viewportOrigin.x*this.parallax.x, viewportOrigin.y*this.parallax.y); - let size = this.viewport.getSize(); - - // Render tilemaps - this.tilemaps.forEach(tilemap => { - tilemap.render(ctx, origin, size); - }); - - // Render visible set - visibleSet.forEach(node => node.render(ctx, origin)); - - ctx.globalAlpha = previousAlpha; - } - } + render(ctx: CanvasRenderingContext2D): void {} } \ No newline at end of file diff --git a/src/Scene/Layers/ObjectLayer.ts b/src/Scene/Layers/ObjectLayer.ts new file mode 100644 index 0000000..2185a80 --- /dev/null +++ b/src/Scene/Layers/ObjectLayer.ts @@ -0,0 +1,3 @@ +import Layer from "../Layer"; + +export default class ObjectLayer extends Layer {} \ No newline at end of file diff --git a/src/Scene/Layers/TiledLayer.ts b/src/Scene/Layers/TiledLayer.ts new file mode 100644 index 0000000..f536854 --- /dev/null +++ b/src/Scene/Layers/TiledLayer.ts @@ -0,0 +1,6 @@ +import Layer from "../Layer"; +import Tilemap from "../../Nodes/Tilemap"; + +export default class TiledLayer extends Layer { + private tilemap: Tilemap; +} \ No newline at end of file diff --git a/src/Scene/Layers/UILayer.ts b/src/Scene/Layers/UILayer.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/Scene/Scene.ts b/src/Scene/Scene.ts index 2ff04d5..ce68630 100644 --- a/src/Scene/Scene.ts +++ b/src/Scene/Scene.ts @@ -2,40 +2,102 @@ import Stack from "../DataTypes/Stack"; import Layer from "./Layer"; import Viewport from "../SceneGraph/Viewport"; import Vec2 from "../DataTypes/Vec2"; +import SceneGraph from "../SceneGraph/SceneGraph"; +import PhysicsManager from "../Physics/PhysicsManager"; +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"; export default class Scene{ - private layers: Stack; - private worldSize: Vec2; - private viewport: Viewport; - private running: boolean; + protected layers: Stack; + protected worldSize: Vec2; + protected viewport: Viewport; + protected running: boolean; + protected game: GameLoop; - constructor(viewport: Viewport){ + protected tilemaps: Array; + protected sceneGraph: SceneGraph; + protected physicsManager: PhysicsManager; + + public add: FactoryManager; + public load: ResourceManager; + + constructor(viewport: Viewport, game: GameLoop){ this.layers = new Stack(10); this.worldSize = new Vec2(1600, 1000); this.viewport = viewport; this.viewport.setBounds(0, 0, 2560, 1280); this.running = false; + this.game = game; + + this.tilemaps = new Array(); + this.sceneGraph = new SceneGraphArray(this.viewport, this); + this.physicsManager = new PhysicsManager(); + + // Factories for this scene + this.add = new FactoryManager(this, this.sceneGraph, this.physicsManager, this.tilemaps); + this.load = ResourceManager.getInstance(); } loadScene(): void {} unloadScene(): void {} + startScene(): void {} + + updateScene(delta: number): void {} + + update(deltaT: number): void { + this.updateScene(deltaT); + + // Update all physics objects + this.physicsManager.update(deltaT); + + // Update all canvas objects + this.sceneGraph.update(deltaT); + + // Update all tilemaps + this.tilemaps.forEach(tilemap => { + if(!tilemap.getLayer().isPaused()){ + tilemap.update(deltaT); + } + }); + + // Update viewport + this.viewport.update(deltaT); + } + + render(ctx: CanvasRenderingContext2D): void { + // For webGL, pass a visible set to the renderer + // We need to keep track of the order of things. + let visibleSet = this.sceneGraph.getVisibleSet(); + + // Render tilemaps + this.tilemaps.forEach(tilemap => { + tilemap.render(ctx); + }); + + // Render visible set + visibleSet.forEach(node => node.render(ctx)); + } + setRunning(running: boolean): void { this.running = running; } isRunning(): boolean { - return this.isRunning(); + return this.running; } - start(){} - - update(deltaT: number): void { - this.layers.forEach((scene: Layer) => scene.update(deltaT)); + addLayer(): Layer { + let layer = new Layer(this); + this.layers.push(layer); + return layer; } - render(ctx: CanvasRenderingContext2D): void { - this.layers.forEach((scene: Layer) => scene.render(ctx)); + getViewport(): Viewport { + return this.viewport; } } \ No newline at end of file diff --git a/src/Scene/SceneManager.ts b/src/Scene/SceneManager.ts index dd0b622..ba2327f 100644 --- a/src/Scene/SceneManager.ts +++ b/src/Scene/SceneManager.ts @@ -1,20 +1,23 @@ import Scene from "./Scene"; import ResourceManager from "../ResourceManager/ResourceManager"; import Viewport from "../SceneGraph/Viewport"; +import GameLoop from "../Loop/GameLoop"; export default class SceneManager{ private currentScene: Scene; private viewport: Viewport; private resourceManager: ResourceManager; + private game: GameLoop; - constructor(viewport: Viewport){ + constructor(viewport: Viewport, game: GameLoop){ this.resourceManager = ResourceManager.getInstance(); this.viewport = viewport; + this.game = game; } public addScene(constr: new (...args: any) => T){ - let scene = new constr(this.viewport); + let scene = new constr(this.viewport, this.game); this.currentScene = scene; // Enqueue all scene asset loads @@ -22,7 +25,7 @@ export default class SceneManager{ // Load all assets this.resourceManager.loadResourcesFromQueue(() => { - scene.start(); + scene.startScene(); scene.setRunning(true); }) } diff --git a/src/SceneGraph/SceneGraph.ts b/src/SceneGraph/SceneGraph.ts index fd51261..dcc84bd 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 Layer from "../Scene/Layer"; +import Scene from "../Scene/Scene"; export default abstract class SceneGraph{ protected viewport: Viewport; protected nodeMap: Map; protected idCounter: number; - protected scene: Layer; + protected scene: Scene; - constructor(viewport: Viewport, scene: Layer){ + constructor(viewport: Viewport, scene: Scene){ this.viewport = viewport; this.scene = scene; this.nodeMap = new Map(); diff --git a/src/SceneGraph/SceneGraphArray.ts b/src/SceneGraph/SceneGraphArray.ts index 3467960..b3db413 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 Layer from "../Scene/Layer"; +import Scene from "../Scene/Scene"; export default class SceneGraphArray extends SceneGraph{ private nodeList: Array; private turnOffViewportCulling_demoTool: boolean; - constructor(viewport: Viewport, scene: Layer){ + constructor(viewport: Viewport, scene: Scene){ super(viewport, scene); this.nodeList = new Array(); @@ -41,7 +41,9 @@ export default class SceneGraphArray extends SceneGraph{ update(deltaT: number): void { for(let node of this.nodeList){ - node.update(deltaT); + if(!node.getLayer().isPaused()){ + node.update(deltaT); + } } } @@ -58,7 +60,7 @@ export default class SceneGraphArray extends SceneGraph{ let visibleSet = new Array(); for(let node of this.nodeList){ - if(this.viewport.includes(node, this.scene.getParallax())){ + if(!node.getLayer().isHidden() && this.viewport.includes(node)){ visibleSet.push(node); } } diff --git a/src/SceneGraph/Viewport.ts b/src/SceneGraph/Viewport.ts index 39e18fc..e048d1b 100644 --- a/src/SceneGraph/Viewport.ts +++ b/src/SceneGraph/Viewport.ts @@ -40,9 +40,10 @@ export default class Viewport{ } } - includes(node: CanvasNode, parallax: Vec2): boolean { + includes(node: CanvasNode): boolean { let nodePos = node.getPosition(); let nodeSize = node.getSize(); + let parallax = node.getLayer().getParallax(); let originX = this.position.x*parallax.x; let originY = this.position.y*parallax.y; if(nodePos.x + nodeSize.x > originX && nodePos.x < originX + this.size.x){ diff --git a/src/main.ts b/src/main.ts index 6836d08..83608a6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,90 +1,13 @@ import GameLoop from "./Loop/GameLoop"; -import Player from "./Player"; -import UIElement from "./Nodes/UIElement"; -import Color from "./Utils/Color"; -import Button from "./Nodes/UIElements/Button"; import {} from "./index"; -import OrthogonalTilemap from "./Nodes/Tilemaps/OrthogonalTilemap"; +import MainScene from "./MainScene"; function main(){ // Create the game object let game = new GameLoop(); - 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); - - // // 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 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 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 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"); - - // // mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/TopDown.json"); - // // let player = mainScene.physics.add(Player, "topdown"); - - // mainScene.getViewport().follow(player); - - // pauseMenu.disable(); - game.start(); + let sm = game.getSceneManager(); + sm.addScene(MainScene); } CanvasRenderingContext2D.prototype.roundedRect = function(x: number, y: number, w: number, h: number, r: number): void {