From 98c23cda40a3a2acd85d68b45f18737606202d58 Mon Sep 17 00:00:00 2001 From: Joe Weaver Date: Mon, 10 Aug 2020 19:13:42 -0400 Subject: [PATCH] added canvasItem factory and added parallax --- src/GameState/Factories/CanvasNodeFactory.ts | 19 +++++ src/GameState/GameState.ts | 19 ++++- src/GameState/Scene.ts | 79 ++++++++++++++------ src/Nodes/CanvasNode.ts | 17 +++++ src/Nodes/ColoredCircle.ts | 14 +++- src/Nodes/GameNode.ts | 10 ++- src/Nodes/Player.ts | 4 +- src/Nodes/UIElement.ts | 22 +----- src/SceneGraph/SceneGraph.ts | 22 +++--- src/SceneGraph/SceneGraphArray.ts | 9 ++- src/SceneGraph/Viewport.ts | 8 +- src/main.ts | 55 +++++++++----- 12 files changed, 191 insertions(+), 87 deletions(-) create mode 100644 src/GameState/Factories/CanvasNodeFactory.ts diff --git a/src/GameState/Factories/CanvasNodeFactory.ts b/src/GameState/Factories/CanvasNodeFactory.ts new file mode 100644 index 0000000..ec7136c --- /dev/null +++ b/src/GameState/Factories/CanvasNodeFactory.ts @@ -0,0 +1,19 @@ +import Scene from "../Scene"; +import Viewport from "../../SceneGraph/Viewport"; +import CanvasItem from "../../Nodes/CanvasNode" + +export default class CanvasNodeFactory { + private scene: Scene; + private viewport: Viewport; + + constructor(scene: Scene, viewport: Viewport){ + this.scene = scene; + } + + add(constr: new (...a: any) => T, ...args: any): T { + let instance = new constr(...args); + instance.init(this.scene); + this.scene.add(instance); + return instance; + } +} \ No newline at end of file diff --git a/src/GameState/GameState.ts b/src/GameState/GameState.ts index 01ca81f..01bfe92 100644 --- a/src/GameState/GameState.ts +++ b/src/GameState/GameState.ts @@ -1,15 +1,28 @@ import Stack from "../DataTypes/Stack"; import Scene from "./Scene"; +import Viewport from "../SceneGraph/Viewport"; +import Vec2 from "../DataTypes/Vec2"; export default class GameState{ - private sceneStack: Stack; + private sceneStack: Stack; + private worldSize: Vec2; + private viewport: Viewport; constructor(){ this.sceneStack = new Stack(10); + this.worldSize = new Vec2(1600, 1000); + this.viewport = new Viewport(); + this.viewport.setSize(800, 500); + this.viewport.setBounds(0, 0, 1600, 1000); } - addScene(scene: Scene, pauseScenesBelow: boolean = true): void { - this.sceneStack.forEach((scene: Scene) => scene.setPaused(pauseScenesBelow)); + createScene(): Scene{ + let scene = new Scene(this.viewport, this); + this.addScene(scene); + return scene; + } + + addScene(scene: Scene): void { this.sceneStack.push(scene); } diff --git a/src/GameState/Scene.ts b/src/GameState/Scene.ts index 417b362..49db35e 100644 --- a/src/GameState/Scene.ts +++ b/src/GameState/Scene.ts @@ -2,22 +2,31 @@ import Vec2 from "../DataTypes/Vec2"; import Viewport from "../SceneGraph/Viewport"; import SceneGraph from "../SceneGraph/SceneGraph"; import SceneGraphArray from "../SceneGraph/SceneGraphArray"; -import GameNode from "../Nodes/GameNode"; +import CanvasNode from "../Nodes/CanvasNode"; +import CavnasNodeFactory from "./Factories/CanvasNodeFactory"; +import CanvasNodeFactory from "./Factories/CanvasNodeFactory"; +import GameState from "./GameState"; -export default class Scene{ - private viewport: Viewport - private worldSize: Vec2; - private sceneGraph: SceneGraph; - private paused: boolean; +export default class Scene { + private gameState: GameState; + private viewport: Viewport + private parallax: Vec2; + sceneGraph: SceneGraph; + private paused: boolean; + private hidden: boolean; + + // Factories + public canvas: CavnasNodeFactory; - constructor(){ - this.viewport = new Viewport(); - this.viewport.setSize(800, 500); - // TODO: Find a way to make this not a hard-coded value - this.worldSize = new Vec2(1600, 1000); - this.viewport.setBounds(0, 0, 1600, 1000); - this.sceneGraph = new SceneGraphArray(this.viewport); + constructor(viewport: Viewport, gameState: GameState){ + this.gameState = gameState; + this.viewport = viewport; + this.parallax = new Vec2(1, 1); + this.sceneGraph = new SceneGraphArray(this.viewport, this); this.paused = false; + this.hidden = false; + + this.canvas = new CanvasNodeFactory(this, this.viewport); } setPaused(pauseValue: boolean): void { @@ -27,19 +36,39 @@ export default class Scene{ isPaused(): boolean { return this.paused; } + + setHidden(hiddenValue: boolean): void { + this.hidden = hiddenValue; + } + + isHidden(): boolean { + return this.hidden; + } + + disable(): void { + this.paused = true; + this.hidden = true; + } + + enable(): void { + this.paused = false; + this.hidden = false; + } getViewport(): Viewport { return this.viewport; } - add(children: Array | GameNode): void { - if(children instanceof Array){ - for(let child of children){ - this.sceneGraph.addNode(child); - } - } else { - this.sceneGraph.addNode(children); - } + setParallax(x: number, y: number): void { + this.parallax.set(x, y); + } + + getParallax(): Vec2 { + return this.parallax; + } + + add(children: CanvasNode): void { + this.sceneGraph.addNode(children); } update(deltaT: number): void { @@ -50,7 +79,11 @@ export default class Scene{ } render(ctx: CanvasRenderingContext2D): void { - let visibleSet = this.sceneGraph.getVisibleSet(); - visibleSet.forEach(node => node.render(ctx, this.viewport.getPosition(), this.viewport.getSize())); + if(!this.hidden){ + let visibleSet = this.sceneGraph.getVisibleSet(); + let viewportOrigin = this.viewport.getPosition(); + let origin = new Vec2(viewportOrigin.x*this.parallax.x, viewportOrigin.y*this.parallax.y); + visibleSet.forEach(node => node.render(ctx, origin)); + } } } \ No newline at end of file diff --git a/src/Nodes/CanvasNode.ts b/src/Nodes/CanvasNode.ts index b89dfc2..ce1a760 100644 --- a/src/Nodes/CanvasNode.ts +++ b/src/Nodes/CanvasNode.ts @@ -1,18 +1,33 @@ import GameNode from "./GameNode"; import Vec2 from "../DataTypes/Vec2"; +import Viewport from "../SceneGraph/Viewport"; +import Scene from "../GameState/Scene"; export default abstract class CanvasNode extends GameNode{ protected size: Vec2; + protected scene: Scene; constructor(){ super(); this.size = new Vec2(0, 0); } + init(scene: Scene){ + this.scene = scene; + } + getSize(): Vec2 { return this.size; } + setSize(vecOrX: Vec2 | number, y: number = null): void { + if(vecOrX instanceof Vec2){ + this.size.set(vecOrX.x, vecOrX.y); + } else { + this.size.set(vecOrX, y); + } + } + contains(x: number, y: number): boolean { if(this.position.x < x && this.position.x + this.size.x > x){ if(this.position.y < y && this.position.y + this.size.y > y){ @@ -21,4 +36,6 @@ export default abstract class CanvasNode extends GameNode{ } return false; } + + abstract render(ctx: CanvasRenderingContext2D, origin: Vec2): void; } \ No newline at end of file diff --git a/src/Nodes/ColoredCircle.ts b/src/Nodes/ColoredCircle.ts index d7fe117..68b77f0 100644 --- a/src/Nodes/ColoredCircle.ts +++ b/src/Nodes/ColoredCircle.ts @@ -14,12 +14,20 @@ export default class ColoredCircle extends CanvasNode{ 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, viewportOrigin: Vec2, viewportSize: Vec2){ - ctx.fillStyle = this.color.toStringRGB(); + render(ctx: CanvasRenderingContext2D, origin: Vec2){ + ctx.fillStyle = this.color.toStringRGBA(); ctx.beginPath(); - ctx.arc(this.position.x + this.size.x/2 - viewportOrigin.x, this.position.y + this.size.y/2 - viewportOrigin.y, this.size.x/2, 0, Math.PI*2, false); + 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(); } diff --git a/src/Nodes/GameNode.ts b/src/Nodes/GameNode.ts index 2d03f83..97592e6 100644 --- a/src/Nodes/GameNode.ts +++ b/src/Nodes/GameNode.ts @@ -21,6 +21,14 @@ export default abstract class GameNode{ return this.position; } + setPosition(vecOrX: Vec2 | number, y: number = null): void { + if(vecOrX instanceof Vec2){ + this.position.set(vecOrX.x, vecOrX.y); + } else { + this.position.set(vecOrX, y); + } + } + subscribe(eventType: string){ this.eventQueue.subscribe(this.receiver, eventType); } @@ -31,6 +39,4 @@ export default abstract class GameNode{ } abstract update(deltaT: number): void; - - abstract render(ctx: CanvasRenderingContext2D, viewportOrigin: Vec2, viewportSize: Vec2): void; } \ No newline at end of file diff --git a/src/Nodes/Player.ts b/src/Nodes/Player.ts index 32d7c47..78103b2 100644 --- a/src/Nodes/Player.ts +++ b/src/Nodes/Player.ts @@ -25,8 +25,8 @@ export default class Player extends CanvasNode{ this.position = this.position.add(this.velocity.scale(deltaT)); } - render(ctx: CanvasRenderingContext2D, viewportOrigin: Vec2, viewportSize: Vec2){ + render(ctx: CanvasRenderingContext2D, origin: Vec2){ ctx.fillStyle = "#FF0000"; - ctx.fillRect(this.position.x - viewportOrigin.x, this.position.y - viewportOrigin.y, this.size.x, this.size.y); + 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/UIElement.ts b/src/Nodes/UIElement.ts index 51c9534..bb45190 100644 --- a/src/Nodes/UIElement.ts +++ b/src/Nodes/UIElement.ts @@ -51,22 +51,6 @@ export default class UIElement extends CanvasNode{ this.isEntered = false; } - setPosition(vecOrX: Vec2 | number, y: number = null): void { - if(vecOrX instanceof Vec2){ - this.position.set(vecOrX.x, vecOrX.y); - } else { - this.position.set(vecOrX, y); - } - } - - setSize(vecOrX: Vec2 | number, y: number = null): void { - if(vecOrX instanceof Vec2){ - this.size.set(vecOrX.x, vecOrX.y); - } else { - this.size.set(vecOrX, y); - } - } - setText(text: string): void { this.text = text; } @@ -163,14 +147,14 @@ export default class UIElement extends CanvasNode{ return this.textColor.toStringRGBA(); } - render(ctx: CanvasRenderingContext2D, viewportOrigin: Vec2, viewportSize: Vec2): void { + render(ctx: CanvasRenderingContext2D, origin: Vec2): void { ctx.font = this.fontSize + "px " + this.font; let offset = this.calculateOffset(ctx); ctx.fillStyle = this.calculateBackgroundColor(); - ctx.fillRect(this.position.x - viewportOrigin.x, this.position.y - viewportOrigin.y, this.size.x, this.size.y); + ctx.fillRect(this.position.x - origin.x, this.position.y - origin.y, this.size.x, this.size.y); ctx.fillStyle = this.calculateTextColor(); - ctx.fillText(this.text, this.position.x + offset.x - viewportOrigin.x, this.position.y + offset.y - viewportOrigin.y); + ctx.fillText(this.text, this.position.x + offset.x - origin.x, this.position.y + offset.y - origin.y); } } \ No newline at end of file diff --git a/src/SceneGraph/SceneGraph.ts b/src/SceneGraph/SceneGraph.ts index 187780a..0509ea5 100644 --- a/src/SceneGraph/SceneGraph.ts +++ b/src/SceneGraph/SceneGraph.ts @@ -1,29 +1,29 @@ import Viewport from "./Viewport"; -import GameNode from "../Nodes/GameNode"; +import CanvasNode from "../Nodes/CanvasNode"; import Map from "../DataTypes/Map"; import Vec2 from "../DataTypes/Vec2"; export default abstract class SceneGraph{ protected viewport: Viewport; - protected nodeMap: Map; + protected nodeMap: Map; protected idCounter: number; constructor(viewport: Viewport){ this.viewport = viewport; - this.nodeMap = new Map(); + this.nodeMap = new Map(); this.idCounter = 0; } - addNode(node: GameNode): number { + addNode(node: CanvasNode): number { this.nodeMap.add(this.idCounter.toString(), node); this.addNodeSpecific(node, this.idCounter.toString()); this.idCounter += 1; return this.idCounter - 1; }; - protected abstract addNodeSpecific(node: GameNode, id: string): void; + protected abstract addNodeSpecific(node: CanvasNode, id: string): void; - removeNode(node: GameNode): void { + removeNode(node: CanvasNode): void { // Find and remove node in O(n) // TODO: Can this be better? let id = this.nodeMap.keys().filter((key: string) => this.nodeMap.get(key) === node)[0]; @@ -33,13 +33,13 @@ export default abstract class SceneGraph{ } }; - protected abstract removeNodeSpecific(node: GameNode, id: string): void; + protected abstract removeNodeSpecific(node: CanvasNode, id: string): void; - getNode(id: string): GameNode{ + getNode(id: string): CanvasNode{ return this.nodeMap.get(id); }; - getNodeAt(vecOrX: Vec2 | number, y: number = null): GameNode{ + getNodeAt(vecOrX: Vec2 | number, y: number = null): CanvasNode{ if(vecOrX instanceof Vec2){ return this.getNodeAtCoords(vecOrX.x, vecOrX.y); } else { @@ -47,9 +47,9 @@ export default abstract class SceneGraph{ } } - protected abstract getNodeAtCoords(x: number, y: number): GameNode; + protected abstract getNodeAtCoords(x: number, y: number): CanvasNode; abstract update(deltaT: number): void; - abstract getVisibleSet(): Array; + abstract getVisibleSet(): Array; } \ No newline at end of file diff --git a/src/SceneGraph/SceneGraphArray.ts b/src/SceneGraph/SceneGraphArray.ts index 7d70705..b89e547 100644 --- a/src/SceneGraph/SceneGraphArray.ts +++ b/src/SceneGraph/SceneGraphArray.ts @@ -1,13 +1,16 @@ import SceneGraph from "./SceneGraph"; import CanvasNode from "../Nodes/CanvasNode"; import Viewport from "./Viewport"; +import Scene from "../GameState/Scene"; export default class SceneGraphArray extends SceneGraph{ private nodeList: Array; - private turnOffViewportCulling_demoTool: boolean; + private turnOffViewportCulling_demoTool: boolean; + private scene: Scene; - constructor(viewport: Viewport){ + constructor(viewport: Viewport, scene: Scene){ super(viewport); + this.scene = scene; this.nodeList = new Array(); this.turnOffViewportCulling_demoTool = false; @@ -57,7 +60,7 @@ export default class SceneGraphArray extends SceneGraph{ let visibleSet = new Array(); for(let node of this.nodeList){ - if(this.viewport.includes(node)){ + if(this.viewport.includes(node, this.scene.getParallax())){ visibleSet.push(node); } } diff --git a/src/SceneGraph/Viewport.ts b/src/SceneGraph/Viewport.ts index 95bd5c0..b717b54 100644 --- a/src/SceneGraph/Viewport.ts +++ b/src/SceneGraph/Viewport.ts @@ -40,11 +40,13 @@ export default class Viewport{ } } - includes(node: CanvasNode): boolean { + includes(node: CanvasNode, parallax: Vec2): boolean { let nodePos = node.getPosition(); let nodeSize = node.getSize(); - if(nodePos.x + nodeSize.x > this.position.x && nodePos.x < this.position.x + this.size.x){ - if(nodePos.y + nodeSize.y > this.position.y && nodePos.y < this.position.y + this.size.y){ + 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){ + if(nodePos.y + nodeSize.y > originY && nodePos.y < originY + this.size.y){ return true; } } diff --git a/src/main.ts b/src/main.ts index 18f2d9c..4f02899 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,32 +9,41 @@ import Button from "./Nodes/UIElements/Button"; function main(){ // Create the game object let game = new GameLoop(); + let gameState = game.getGameState(); - let mainScene = new Scene(); - let pauseMenu = new Scene(); + let backgroundScene = gameState.createScene(); + backgroundScene.setParallax(0.5, 0.5); + let mainScene = gameState.createScene(); + let foregroundLayer = gameState.createScene(); + foregroundLayer.setParallax(1.5, 1.5); + let uiLayer = gameState.createScene(); + uiLayer.setParallax(0, 0); + let pauseMenu = gameState.createScene(); + pauseMenu.setParallax(0, 0); // Initialize GameObjects - let player = new Player(); + let player = mainScene.canvas.add(Player); + mainScene.getViewport().follow(player); - let recordButton = new Button(); + let recordButton = uiLayer.canvas.add(Button); recordButton.setSize(100, 50); recordButton.setText("Record"); recordButton.setPosition(400, 30); recordButton.onClickEventId = "record_button_press"; - let stopButton = new Button(); + let stopButton = uiLayer.canvas.add(Button); stopButton.setSize(100, 50); stopButton.setText("Stop"); stopButton.setPosition(550, 30); stopButton.onClickEventId = "stop_button_press"; - let playButton = new Button(); + let playButton = uiLayer.canvas.add(Button); playButton.setSize(100, 50); playButton.setText("Play"); playButton.setPosition(700, 30); playButton.onClickEventId = "play_button_press"; - let cycleFramerateButton = new Button(); + let cycleFramerateButton = uiLayer.canvas.add(Button); cycleFramerateButton.setSize(150, 50); cycleFramerateButton.setText("Cycle FPS"); cycleFramerateButton.setPosition(5, 400); @@ -45,38 +54,48 @@ function main(){ i = (i + 1) % 3; } - let pauseButton = new Button(); + let pauseButton = uiLayer.canvas.add(Button); pauseButton.setSize(100, 50); pauseButton.setText("Pause"); pauseButton.setPosition(700, 400); pauseButton.onClick = () => { - game.getGameState().addScene(pauseMenu); + mainScene.setPaused(true); + pauseMenu.enable(); } - let modalBackground = new UIElement(); + let modalBackground = pauseMenu.canvas.add(UIElement); modalBackground.setSize(400, 200); modalBackground.setBackgroundColor(new Color(0, 0, 0, 0.4)); modalBackground.setPosition(200, 100); - let resumeButton = new Button(); + let resumeButton = pauseMenu.canvas.add(Button); resumeButton.setSize(100, 50); resumeButton.setText("Resume"); resumeButton.setPosition(400, 200); resumeButton.onClick = () => { - game.getGameState().removeScene(); + mainScene.setPaused(false); + pauseMenu.disable(); } - let lotsOfCircs = []; for(let i = 0; i < 10; i++){ - lotsOfCircs.push(new ColoredCircle()); + mainScene.canvas.add(ColoredCircle); } + for(let i = 0; i < 20; i++){ + let cc = backgroundScene.canvas.add(ColoredCircle); + cc.setSize(30, 30); + cc.setColor(cc.getColor().darken().darken()) + cc.getColor().a = 0.8; + } - mainScene.add([...lotsOfCircs, player, recordButton, stopButton, playButton, cycleFramerateButton, pauseButton]); - mainScene.getViewport().follow(player); - pauseMenu.add([modalBackground, resumeButton]); + for(let i = 0; i < 30; i++){ + let cc = foregroundLayer.canvas.add(ColoredCircle); + cc.setSize(80, 80); + cc.setColor(cc.getColor().lighten().lighten()) + cc.getColor().a = 0.5; + } - game.getGameState().changeScene(mainScene); + pauseMenu.disable(); game.start(); }