reworked scenes

This commit is contained in:
Joe Weaver 2020-09-06 18:07:09 -04:00
parent 214eba6e71
commit 2093d8e4ab
30 changed files with 468 additions and 359 deletions

View File

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

View File

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

View File

@ -73,6 +73,12 @@ export default class Vec2 {
return this; return this;
} }
mult(other: Vec2): Vec2 {
this.x *= other.x;
this.y *= other.y;
return this;
}
toString(): string { toString(): string {
return this.toFixed(); return this.toFixed();
} }

View File

@ -67,7 +67,7 @@ export default class GameLoop{
this.inputReceiver.setViewport(this.viewport); this.inputReceiver.setViewport(this.viewport);
this.recorder = new Recorder(); this.recorder = new Recorder();
this.resourceManager = ResourceManager.getInstance(); 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 { private initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D {

101
src/MainScene.ts Normal file
View File

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

View File

@ -31,5 +31,5 @@ export default abstract class CanvasNode extends GameNode{
return false; return false;
} }
abstract render(ctx: CanvasRenderingContext2D, origin: Vec2): void; abstract render(ctx: CanvasRenderingContext2D): void;
} }

View File

@ -4,6 +4,7 @@ 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 "../Scene/Scene";
import Layer from "../Scene/Layer"; import Layer from "../Scene/Layer";
export default abstract class GameNode{ export default abstract class GameNode{
@ -11,7 +12,8 @@ export default abstract class GameNode{
protected input: InputReceiver; protected input: InputReceiver;
protected position: Vec2; protected position: Vec2;
private receiver: Receiver; private receiver: Receiver;
protected scene: Layer; protected scene: Scene;
protected layer: Layer;
constructor(){ constructor(){
this.eventQueue = EventQueue.getInstance(); this.eventQueue = EventQueue.getInstance();
@ -19,14 +21,22 @@ export default abstract class GameNode{
this.position = new Vec2(0, 0); this.position = new Vec2(0, 0);
} }
init(scene: Layer){ setScene(scene: Scene): void {
this.scene = scene; this.scene = scene;
} }
getScene(): Layer { getScene(): Scene {
return this.scene; return this.scene;
} }
setLayer(layer: Layer): void {
this.layer = layer;
}
getLayer(): Layer {
return this.layer;
}
getPosition(): Vec2 { getPosition(): Vec2 {
return this.position; return this.position;
} }
@ -48,5 +58,10 @@ export default abstract class GameNode{
this.eventQueue.addEvent(event); 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; abstract update(deltaT: number): void;
} }

11
src/Nodes/Graphic.ts Normal file
View File

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

View File

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

View File

@ -2,7 +2,6 @@ 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
@ -12,15 +11,17 @@ export default abstract class Tilemap extends GameNode {
protected worldSize: Vec2; protected worldSize: Vec2;
protected tileSize: Vec2; protected tileSize: Vec2;
protected scale: Vec2; protected scale: Vec2;
protected layers: Array<TileLayer>; public data: Array<number>;
public collidable: boolean;
public visible: boolean;
// TODO: Make this no longer be specific to Tiled // TODO: Make this no longer be specific to Tiled
constructor(tilemapData: TiledTilemapData) { constructor(tilemapData: TiledTilemapData, layer: TiledLayerData) {
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); this.parseTilemapData(tilemapData, layer);
this.scale = new Vec2(4, 4); this.scale = new Vec2(4, 4);
} }
@ -44,13 +45,21 @@ export default abstract class Tilemap extends GameNode {
this.scale = scale; this.scale = scale;
} }
isCollidable(): boolean {
return this.collidable;
}
isVisible(): boolean {
return this.visible;
}
abstract getTileAt(worldCoords: Vec2): number; abstract getTileAt(worldCoords: Vec2): number;
/** /**
* 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): void; protected abstract parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void;
abstract render(ctx: CanvasRenderingContext2D, origin: Vec2, viewportSize: Vec2): void; abstract render(ctx: CanvasRenderingContext2D): void;
} }

View File

@ -2,33 +2,27 @@ 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): void { protected parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): 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);
for(let layerData of tilemapData.layers){ this.data = layer.data;
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"){
layer.collidable = item.value; this.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){
@ -36,14 +30,7 @@ export default class OrthogonalTilemap extends Tilemap {
return 0; return 0;
} }
// Return the top nonzero tile return this.data[localCoords.y * this.worldSize.x + localCoords.x]
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 {
@ -55,20 +42,15 @@ export default class OrthogonalTilemap extends Tilemap {
} }
index = row * this.worldSize.x + indexOrCol; index = row * this.worldSize.x + indexOrCol;
} else { } 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 // Tiles that don't exist aren't collidable
return false; return false;
} }
index = indexOrCol; index = indexOrCol;
} }
for(let layer of this.layers){ // TODO - Currently, all tiles in a collidable layer are collidable
if(layer.data[index] !== 0 && layer.collidable){ return this.data[index] !== 0 && this.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?
@ -81,11 +63,15 @@ export default class OrthogonalTilemap extends Tilemap {
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) {
for(let layer of this.layers){ let previousAlpha = ctx.globalAlpha;
if(layer.visible){ ctx.globalAlpha = this.getLayer().getAlpha();
for(let i = 0; i < layer.data.length; i++){
let tileIndex = layer.data[i]; let origin = this.getViewportOriginWithParallax();
if(this.visible){
for(let i = 0; i < this.data.length; i++){
let tileIndex = this.data[i];
for(let tileset of this.tilesets){ for(let tileset of this.tilesets){
if(tileset.hasTile(tileIndex)){ if(tileset.hasTile(tileIndex)){
@ -94,6 +80,7 @@ export default class OrthogonalTilemap extends Tilemap {
} }
} }
} }
}
ctx.globalAlpha = previousAlpha;
} }
} }

View File

@ -155,7 +155,12 @@ export default class UIElement extends CanvasNode{
return this.textColor.toStringRGBA(); 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; ctx.font = this.fontSize + "px " + this.font;
let offset = this.calculateOffset(ctx); let offset = this.calculateOffset(ctx);
@ -168,5 +173,7 @@ export default class UIElement extends CanvasNode{
ctx.fillStyle = this.calculateTextColor(); ctx.fillStyle = this.calculateTextColor();
ctx.fillText(this.text, this.position.x + offset.x - origin.x, this.position.y + offset.y - origin.y); ctx.fillText(this.text, this.position.x + offset.x - origin.x, this.position.y + offset.y - origin.y);
ctx.globalAlpha = previousAlpha;
} }
} }

View File

@ -215,8 +215,10 @@ export default class PhysicsManager {
update(deltaT: number): void { update(deltaT: number): void {
for(let node of this.physicsNodes){ for(let node of this.physicsNodes){
if(!node.getLayer().isPaused()){
node.update(deltaT); node.update(deltaT);
} }
}
let staticSet = new Array<PhysicsNode>(); let staticSet = new Array<PhysicsNode>();
let dynamicSet = new Array<PhysicsNode>(); let dynamicSet = new Array<PhysicsNode>();

View File

@ -2,7 +2,7 @@ import PhysicsNode from "./Physics/PhysicsNode";
import Vec2 from "./DataTypes/Vec2"; import Vec2 from "./DataTypes/Vec2";
import Debug from "./Debug/Debug"; import Debug from "./Debug/Debug";
import AABB from "./Physics/Colliders/AABB"; import AABB from "./Physics/Colliders/AABB";
import PlayerSprite from "./PlayerSprite"; import CanvasNode from "./Nodes/CanvasNode";
export default class Player extends PhysicsNode { export default class Player extends PhysicsNode {
velocity: Vec2; velocity: Vec2;
@ -26,8 +26,9 @@ export default class Player extends PhysicsNode {
} }
} }
create(): void { create(): void {};
let sprite = this.scene.canvasNode.add(PlayerSprite);
setSprite(sprite: CanvasNode): void {
sprite.setPosition(this.position); sprite.setPosition(this.position);
sprite.setSize(this.size); sprite.setSize(this.size);
this.children.push(sprite); this.children.push(sprite);

View File

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

View File

@ -45,6 +45,10 @@ export default class ResourceManager {
this.imageLoadingQueue.enqueue({key: key, path: path}); 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 { 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 { public tilemap(key: string, path: string): void {
// Add a function that loads the tilemap to the queue // Add a function that loads the tilemap to the queue
this.tilemapLoadingQueue.enqueue({key: key, path: path}); this.tilemapLoadingQueue.enqueue({key: key, path: path});
}
// this.tilemapLoadingQueue.enqueue((callback: Function) => { public getTilemap(key: string): TiledTilemapData{
// this.loadTilemap(path, (tilemapData: TiledTilemapData) => { return this.tilemaps.get(key);
// // 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 { loadResourcesFromQueue(callback: Function): void {

View File

@ -1,18 +1,55 @@
import Layer from "../Layer"; import Scene from "../Scene";
import Viewport from "../../SceneGraph/Viewport";
import CanvasItem from "../../Nodes/CanvasNode" 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 { 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.scene = scene;
this.sceneGraph = sceneGraph;
} }
add<T extends CanvasItem>(constr: new (...a: any) => T, ...args: any): T { addUIElement = <T extends UIElement>(constr: new (...a: any) => T, layer: Layer, ...args: any): T => {
let instance = new constr(...args); 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 = <T extends CanvasItem>(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 = <T extends Graphic>(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; return instance;
} }
} }

View File

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

View File

@ -1,28 +1,27 @@
import Layer from "../Layer"; import Scene from "../Scene";
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 Layer from "../Layer";
export default class PhysicsNodeFactory { export default class PhysicsNodeFactory {
private scene: Layer; private scene: Scene;
private physicsManager: PhysicsManager; private physicsManager: PhysicsManager;
constructor(scene: Layer, physicsManager: PhysicsManager){ init(scene: Scene, physicsManager: PhysicsManager): void {
this.scene = scene; this.scene = scene;
this.physicsManager = physicsManager; this.physicsManager = physicsManager;
} }
add<T extends PhysicsNode>(constr: new (...a: any) => T, ...args: any): T { // TODO: Currently this doesn't care about layers
add = <T extends PhysicsNode>(constr: new (...a: any) => T, layer: Layer, ...args: any): T => {
let instance = new constr(...args); let instance = new constr(...args);
instance.init(this.scene); instance.setScene(this.scene);
instance.addManager(this.physicsManager); instance.addManager(this.physicsManager);
instance.create(); instance.create();
layer.addNode(instance);
this.physicsManager.add(instance); this.physicsManager.add(instance);
return instance; return instance;
} }
addTilemap(tilemap: Tilemap): void {
this.physicsManager.addTilemap(tilemap);
}
} }

View File

@ -1,45 +1,55 @@
import Layer from "../Layer"; import Scene from "../Scene";
import Viewport from "../../SceneGraph/Viewport";
import Tilemap from "../../Nodes/Tilemap"; import Tilemap from "../../Nodes/Tilemap";
import PhysicsManager from "../../Physics/PhysicsManager";
import ResourceManager from "../../ResourceManager/ResourceManager"; 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 { export default class TilemapFactory {
private scene: Layer; private scene: Scene;
// TODO: get the resource manager OUT of here, it does not belong private tilemaps: Array<Tilemap>;
private physicsManager: PhysicsManager;
private resourceManager: ResourceManager; private resourceManager: ResourceManager;
constructor(scene: Layer){ init(scene: Scene, tilemaps: Array<Tilemap>, physicsManager: PhysicsManager): void {
this.scene = scene; this.scene = scene;
this.tilemaps = tilemaps;
this.physicsManager = physicsManager;
this.resourceManager = ResourceManager.getInstance(); this.resourceManager = ResourceManager.getInstance();
} }
add<T extends Tilemap>(constr: new (...a: any) => T, path: string, ...args: any): void { add = <T extends Tilemap>(key: string, constr: new (...a: any) => T, ...args: any): Array<Tilemap> => {
// this.resourceManager.loadTilemap(path, (tilemapData: TiledTilemapData) => { // Get Tilemap Data
// // For each of the layers in the tilemap, create a tilemap let tilemapData = this.resourceManager.getTilemap(key);
// for(let layer of tilemapData.layers){
// let tilemap = new constr(tilemapData, layer);
// tilemap.init(this.scene);
// // Add to scene // Get the return values
// this.scene.addTilemap(tilemap); let tilemaps = new Array<Tilemap>();
// if(tilemap.isCollidable()){ for(let layer of tilemapData.layers){
// // Register in physics as a tilemap // Create a new tilemap object for the layer
// this.scene.physics.addTilemap(tilemap); let tilemap = new constr(tilemapData, layer);
// } tilemap.setScene(this.scene);
// // Load images for the tilesets // Add tilemap to scene
// tilemap.getTilesets().forEach(tileset => { this.tilemaps.push(tilemap);
// let imagePath = StringUtils.getPathFromFilePath(path) + tileset.getImageUrl();
// this.resourceManager.loadImage(imagePath, (path: string, image: HTMLImageElement) => { // Create a new layer in the scene
// tileset.setImage(image); 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;
} }
} }

View File

@ -1,46 +1,23 @@
import Vec2 from "../DataTypes/Vec2"; 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 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 MathUtils from "../Utils/MathUtils";
import GameNode from "../Nodes/GameNode";
export default class Layer { export default class Layer {
private gameState: Scene; protected scene: Scene;
private viewport: Viewport protected parallax: Vec2;
private parallax: Vec2; protected paused: boolean;
private sceneGraph: SceneGraph; protected hidden: boolean;
private physicsManager: PhysicsManager; protected alpha: number;
private tilemaps: Array<Tilemap>; protected items: Array<GameNode>;
private paused: boolean;
private hidden: boolean;
private alpha: number;
// Factories constructor(scene: Scene){
public canvasNode: CanvasNodeFactory; this.scene = scene;
public tilemap: TilemapFactory;
public physics: PhysicsNodeFactory;
constructor(viewport: Viewport, gameState: Scene){
this.gameState = gameState;
this.viewport = viewport;
this.parallax = new Vec2(1, 1); this.parallax = new Vec2(1, 1);
this.sceneGraph = new SceneGraphArray(this.viewport, this);
this.tilemaps = new Array<Tilemap>();
this.paused = false; this.paused = false;
this.hidden = false; this.hidden = false;
this.physicsManager = new PhysicsManager(); this.alpha = 1;
this.items = new Array();
// Factories
this.canvasNode = new CanvasNodeFactory(this);
this.tilemap = new TilemapFactory(this);
this.physics = new PhysicsNodeFactory(this, this.physicsManager);
} }
setPaused(pauseValue: boolean): void { setPaused(pauseValue: boolean): void {
@ -55,6 +32,10 @@ export default class Layer {
this.alpha = MathUtils.clamp(alpha, 0, 1); this.alpha = MathUtils.clamp(alpha, 0, 1);
} }
getAlpha(): number {
return this.alpha;
}
setHidden(hidden: boolean): void { setHidden(hidden: boolean): void {
this.hidden = hidden; this.hidden = hidden;
} }
@ -73,10 +54,6 @@ export default class Layer {
this.hidden = false; this.hidden = false;
} }
getViewport(): Viewport {
return this.viewport;
}
setParallax(x: number, y: number): void { setParallax(x: number, y: number): void {
this.parallax.set(x, y); this.parallax.set(x, y);
} }
@ -85,42 +62,10 @@ export default class Layer {
return this.parallax; return this.parallax;
} }
add(child: CanvasNode): void { addNode(node: GameNode): void {
this.sceneGraph.addNode(child); this.items.push(node);
node.setLayer(this);
} }
addTilemap(tilemap: Tilemap): void { render(ctx: CanvasRenderingContext2D): 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;
}
}
} }

View File

@ -0,0 +1,3 @@
import Layer from "../Layer";
export default class ObjectLayer extends Layer {}

View File

@ -0,0 +1,6 @@
import Layer from "../Layer";
import Tilemap from "../../Nodes/Tilemap";
export default class TiledLayer extends Layer {
private tilemap: Tilemap;
}

View File

View File

@ -2,40 +2,102 @@ import Stack from "../DataTypes/Stack";
import Layer from "./Layer"; import Layer from "./Layer";
import Viewport from "../SceneGraph/Viewport"; import Viewport from "../SceneGraph/Viewport";
import Vec2 from "../DataTypes/Vec2"; 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{ export default class Scene{
private layers: Stack<Layer>; protected layers: Stack<Layer>;
private worldSize: Vec2; protected worldSize: Vec2;
private viewport: Viewport; protected viewport: Viewport;
private running: boolean; protected running: boolean;
protected game: GameLoop;
constructor(viewport: Viewport){ protected tilemaps: Array<Tilemap>;
protected sceneGraph: SceneGraph;
protected physicsManager: PhysicsManager;
public add: FactoryManager;
public load: ResourceManager;
constructor(viewport: Viewport, game: GameLoop){
this.layers = new Stack(10); this.layers = new Stack(10);
this.worldSize = new Vec2(1600, 1000); this.worldSize = new Vec2(1600, 1000);
this.viewport = viewport; this.viewport = viewport;
this.viewport.setBounds(0, 0, 2560, 1280); this.viewport.setBounds(0, 0, 2560, 1280);
this.running = false; 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 {} loadScene(): void {}
unloadScene(): 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 { setRunning(running: boolean): void {
this.running = running; this.running = running;
} }
isRunning(): boolean { isRunning(): boolean {
return this.isRunning(); return this.running;
} }
start(){} addLayer(): Layer {
let layer = new Layer(this);
update(deltaT: number): void { this.layers.push(layer);
this.layers.forEach((scene: Layer) => scene.update(deltaT)); return layer;
} }
render(ctx: CanvasRenderingContext2D): void { getViewport(): Viewport {
this.layers.forEach((scene: Layer) => scene.render(ctx)); return this.viewport;
} }
} }

View File

@ -1,20 +1,23 @@
import Scene from "./Scene"; import Scene from "./Scene";
import ResourceManager from "../ResourceManager/ResourceManager"; import ResourceManager from "../ResourceManager/ResourceManager";
import Viewport from "../SceneGraph/Viewport"; import Viewport from "../SceneGraph/Viewport";
import GameLoop from "../Loop/GameLoop";
export default class SceneManager{ export default class SceneManager{
private currentScene: Scene; private currentScene: Scene;
private viewport: Viewport; private viewport: Viewport;
private resourceManager: ResourceManager; private resourceManager: ResourceManager;
private game: GameLoop;
constructor(viewport: Viewport){ constructor(viewport: Viewport, game: GameLoop){
this.resourceManager = ResourceManager.getInstance(); this.resourceManager = ResourceManager.getInstance();
this.viewport = viewport; this.viewport = viewport;
this.game = game;
} }
public addScene<T extends Scene>(constr: new (...args: any) => T){ public addScene<T extends Scene>(constr: new (...args: any) => T){
let scene = new constr(this.viewport); let scene = new constr(this.viewport, this.game);
this.currentScene = scene; this.currentScene = scene;
// Enqueue all scene asset loads // Enqueue all scene asset loads
@ -22,7 +25,7 @@ export default class SceneManager{
// Load all assets // Load all assets
this.resourceManager.loadResourcesFromQueue(() => { this.resourceManager.loadResourcesFromQueue(() => {
scene.start(); scene.startScene();
scene.setRunning(true); scene.setRunning(true);
}) })
} }

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 Layer from "../Scene/Layer"; import Scene from "../Scene/Scene";
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: Layer; protected scene: Scene;
constructor(viewport: Viewport, scene: Layer){ constructor(viewport: Viewport, scene: Scene){
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 Layer from "../Scene/Layer"; import Scene from "../Scene/Scene";
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: Layer){ constructor(viewport: Viewport, scene: Scene){
super(viewport, scene); super(viewport, scene);
this.nodeList = new Array<CanvasNode>(); this.nodeList = new Array<CanvasNode>();
@ -41,9 +41,11 @@ export default class SceneGraphArray extends SceneGraph{
update(deltaT: number): void { update(deltaT: number): void {
for(let node of this.nodeList){ for(let node of this.nodeList){
if(!node.getLayer().isPaused()){
node.update(deltaT); node.update(deltaT);
} }
} }
}
getVisibleSet(): Array<CanvasNode> { getVisibleSet(): Array<CanvasNode> {
// If viewport culling is turned off for demonstration // If viewport culling is turned off for demonstration
@ -58,7 +60,7 @@ export default class SceneGraphArray extends SceneGraph{
let visibleSet = new Array<CanvasNode>(); let visibleSet = new Array<CanvasNode>();
for(let node of this.nodeList){ 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); visibleSet.push(node);
} }
} }

View File

@ -40,9 +40,10 @@ export default class Viewport{
} }
} }
includes(node: CanvasNode, parallax: Vec2): boolean { includes(node: CanvasNode): boolean {
let nodePos = node.getPosition(); let nodePos = node.getPosition();
let nodeSize = node.getSize(); let nodeSize = node.getSize();
let parallax = node.getLayer().getParallax();
let originX = this.position.x*parallax.x; let originX = this.position.x*parallax.x;
let originY = this.position.y*parallax.y; let originY = this.position.y*parallax.y;
if(nodePos.x + nodeSize.x > originX && nodePos.x < originX + this.size.x){ if(nodePos.x + nodeSize.x > originX && nodePos.x < originX + this.size.x){

View File

@ -1,90 +1,13 @@
import GameLoop from "./Loop/GameLoop"; 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 {} from "./index";
import OrthogonalTilemap from "./Nodes/Tilemaps/OrthogonalTilemap"; import MainScene from "./MainScene";
function main(){ function main(){
// Create the game object // Create the game object
let game = new GameLoop(); 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(); game.start();
let sm = game.getSceneManager();
sm.addScene(MainScene);
} }
CanvasRenderingContext2D.prototype.roundedRect = function(x: number, y: number, w: number, h: number, r: number): void { CanvasRenderingContext2D.prototype.roundedRect = function(x: number, y: number, w: number, h: number, r: number): void {