added canvasItem factory and added parallax

This commit is contained in:
Joe Weaver 2020-08-10 19:13:42 -04:00
parent b33b7b0a21
commit 98c23cda40
12 changed files with 191 additions and 87 deletions

View File

@ -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<T extends CanvasItem>(constr: new (...a: any) => T, ...args: any): T {
let instance = new constr(...args);
instance.init(this.scene);
this.scene.add(instance);
return instance;
}
}

View File

@ -1,15 +1,28 @@
import Stack from "../DataTypes/Stack"; import Stack from "../DataTypes/Stack";
import Scene from "./Scene"; import Scene from "./Scene";
import Viewport from "../SceneGraph/Viewport";
import Vec2 from "../DataTypes/Vec2";
export default class GameState{ export default class GameState{
private sceneStack: Stack<Scene>; private sceneStack: Stack<Scene>;
private worldSize: Vec2;
private viewport: Viewport;
constructor(){ constructor(){
this.sceneStack = new Stack(10); 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 { createScene(): Scene{
this.sceneStack.forEach((scene: Scene) => scene.setPaused(pauseScenesBelow)); let scene = new Scene(this.viewport, this);
this.addScene(scene);
return scene;
}
addScene(scene: Scene): void {
this.sceneStack.push(scene); this.sceneStack.push(scene);
} }

View File

@ -2,22 +2,31 @@ import Vec2 from "../DataTypes/Vec2";
import Viewport from "../SceneGraph/Viewport"; import Viewport from "../SceneGraph/Viewport";
import SceneGraph from "../SceneGraph/SceneGraph"; import SceneGraph from "../SceneGraph/SceneGraph";
import SceneGraphArray from "../SceneGraph/SceneGraphArray"; 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{ export default class Scene {
private viewport: Viewport private gameState: GameState;
private worldSize: Vec2; private viewport: Viewport
private sceneGraph: SceneGraph; private parallax: Vec2;
private paused: boolean; sceneGraph: SceneGraph;
private paused: boolean;
private hidden: boolean;
// Factories
public canvas: CavnasNodeFactory;
constructor(){ constructor(viewport: Viewport, gameState: GameState){
this.viewport = new Viewport(); this.gameState = gameState;
this.viewport.setSize(800, 500); this.viewport = viewport;
// TODO: Find a way to make this not a hard-coded value this.parallax = new Vec2(1, 1);
this.worldSize = new Vec2(1600, 1000); this.sceneGraph = new SceneGraphArray(this.viewport, this);
this.viewport.setBounds(0, 0, 1600, 1000);
this.sceneGraph = new SceneGraphArray(this.viewport);
this.paused = false; this.paused = false;
this.hidden = false;
this.canvas = new CanvasNodeFactory(this, this.viewport);
} }
setPaused(pauseValue: boolean): void { setPaused(pauseValue: boolean): void {
@ -27,19 +36,39 @@ export default class Scene{
isPaused(): boolean { isPaused(): boolean {
return this.paused; 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 { getViewport(): Viewport {
return this.viewport; return this.viewport;
} }
add(children: Array<GameNode> | GameNode): void { setParallax(x: number, y: number): void {
if(children instanceof Array){ this.parallax.set(x, y);
for(let child of children){ }
this.sceneGraph.addNode(child);
} getParallax(): Vec2 {
} else { return this.parallax;
this.sceneGraph.addNode(children); }
}
add(children: CanvasNode): void {
this.sceneGraph.addNode(children);
} }
update(deltaT: number): void { update(deltaT: number): void {
@ -50,7 +79,11 @@ export default class Scene{
} }
render(ctx: CanvasRenderingContext2D): void { render(ctx: CanvasRenderingContext2D): void {
let visibleSet = this.sceneGraph.getVisibleSet(); if(!this.hidden){
visibleSet.forEach(node => node.render(ctx, this.viewport.getPosition(), this.viewport.getSize())); 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));
}
} }
} }

View File

@ -1,18 +1,33 @@
import GameNode from "./GameNode"; import GameNode from "./GameNode";
import Vec2 from "../DataTypes/Vec2"; import Vec2 from "../DataTypes/Vec2";
import Viewport from "../SceneGraph/Viewport";
import Scene from "../GameState/Scene";
export default abstract class CanvasNode extends GameNode{ export default abstract class CanvasNode extends GameNode{
protected size: Vec2; protected size: Vec2;
protected scene: Scene;
constructor(){ constructor(){
super(); super();
this.size = new Vec2(0, 0); this.size = new Vec2(0, 0);
} }
init(scene: Scene){
this.scene = scene;
}
getSize(): Vec2 { getSize(): Vec2 {
return this.size; 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 { contains(x: number, y: number): boolean {
if(this.position.x < x && this.position.x + this.size.x > x){ if(this.position.x < x && this.position.x + this.size.x > x){
if(this.position.y < y && this.position.y + this.size.y > y){ 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; return false;
} }
abstract render(ctx: CanvasRenderingContext2D, origin: Vec2): void;
} }

View File

@ -14,12 +14,20 @@ export default class ColoredCircle extends CanvasNode{
this.size = new Vec2(50, 50); this.size = new Vec2(50, 50);
} }
setColor(color: Color): void {
this.color = color;
}
getColor(): Color {
return this.color;
}
update(deltaT: number): void {} update(deltaT: number): void {}
render(ctx: CanvasRenderingContext2D, viewportOrigin: Vec2, viewportSize: Vec2){ render(ctx: CanvasRenderingContext2D, origin: Vec2){
ctx.fillStyle = this.color.toStringRGB(); ctx.fillStyle = this.color.toStringRGBA();
ctx.beginPath(); 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.fill();
ctx.closePath(); ctx.closePath();
} }

View File

@ -21,6 +21,14 @@ export default abstract class GameNode{
return this.position; 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){ subscribe(eventType: string){
this.eventQueue.subscribe(this.receiver, eventType); this.eventQueue.subscribe(this.receiver, eventType);
} }
@ -31,6 +39,4 @@ export default abstract class GameNode{
} }
abstract update(deltaT: number): void; abstract update(deltaT: number): void;
abstract render(ctx: CanvasRenderingContext2D, viewportOrigin: Vec2, viewportSize: Vec2): void;
} }

View File

@ -25,8 +25,8 @@ export default class Player extends CanvasNode{
this.position = this.position.add(this.velocity.scale(deltaT)); 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.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);
} }
} }

View File

@ -51,22 +51,6 @@ export default class UIElement extends CanvasNode{
this.isEntered = false; 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 { setText(text: string): void {
this.text = text; this.text = text;
} }
@ -163,14 +147,14 @@ export default class UIElement extends CanvasNode{
return this.textColor.toStringRGBA(); 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; ctx.font = this.fontSize + "px " + this.font;
let offset = this.calculateOffset(ctx); let offset = this.calculateOffset(ctx);
ctx.fillStyle = this.calculateBackgroundColor(); 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.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);
} }
} }

View File

@ -1,29 +1,29 @@
import Viewport from "./Viewport"; import Viewport from "./Viewport";
import GameNode from "../Nodes/GameNode"; 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";
export default abstract class SceneGraph{ export default abstract class SceneGraph{
protected viewport: Viewport; protected viewport: Viewport;
protected nodeMap: Map<GameNode>; protected nodeMap: Map<CanvasNode>;
protected idCounter: number; protected idCounter: number;
constructor(viewport: Viewport){ constructor(viewport: Viewport){
this.viewport = viewport; this.viewport = viewport;
this.nodeMap = new Map<GameNode>(); this.nodeMap = new Map<CanvasNode>();
this.idCounter = 0; this.idCounter = 0;
} }
addNode(node: GameNode): number { addNode(node: CanvasNode): number {
this.nodeMap.add(this.idCounter.toString(), node); this.nodeMap.add(this.idCounter.toString(), node);
this.addNodeSpecific(node, this.idCounter.toString()); this.addNodeSpecific(node, this.idCounter.toString());
this.idCounter += 1; this.idCounter += 1;
return 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) // Find and remove node in O(n)
// TODO: Can this be better? // TODO: Can this be better?
let id = this.nodeMap.keys().filter((key: string) => this.nodeMap.get(key) === node)[0]; 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); 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){ if(vecOrX instanceof Vec2){
return this.getNodeAtCoords(vecOrX.x, vecOrX.y); return this.getNodeAtCoords(vecOrX.x, vecOrX.y);
} else { } 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 update(deltaT: number): void;
abstract getVisibleSet(): Array<GameNode>; abstract getVisibleSet(): Array<CanvasNode>;
} }

View File

@ -1,13 +1,16 @@
import SceneGraph from "./SceneGraph"; import SceneGraph from "./SceneGraph";
import CanvasNode from "../Nodes/CanvasNode"; import CanvasNode from "../Nodes/CanvasNode";
import Viewport from "./Viewport"; import Viewport from "./Viewport";
import Scene from "../GameState/Scene";
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;
private scene: Scene;
constructor(viewport: Viewport){ constructor(viewport: Viewport, scene: Scene){
super(viewport); super(viewport);
this.scene = scene;
this.nodeList = new Array<CanvasNode>(); this.nodeList = new Array<CanvasNode>();
this.turnOffViewportCulling_demoTool = false; this.turnOffViewportCulling_demoTool = false;
@ -57,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)){ if(this.viewport.includes(node, this.scene.getParallax())){
visibleSet.push(node); visibleSet.push(node);
} }
} }

View File

@ -40,11 +40,13 @@ export default class Viewport{
} }
} }
includes(node: CanvasNode): boolean { includes(node: CanvasNode, parallax: Vec2): boolean {
let nodePos = node.getPosition(); let nodePos = node.getPosition();
let nodeSize = node.getSize(); let nodeSize = node.getSize();
if(nodePos.x + nodeSize.x > this.position.x && nodePos.x < this.position.x + this.size.x){ let originX = this.position.x*parallax.x;
if(nodePos.y + nodeSize.y > this.position.y && nodePos.y < this.position.y + this.size.y){ 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; return true;
} }
} }

View File

@ -9,32 +9,41 @@ import Button from "./Nodes/UIElements/Button";
function main(){ function main(){
// Create the game object // Create the game object
let game = new GameLoop(); let game = new GameLoop();
let gameState = game.getGameState();
let mainScene = new Scene(); let backgroundScene = gameState.createScene();
let pauseMenu = new Scene(); 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 // 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.setSize(100, 50);
recordButton.setText("Record"); recordButton.setText("Record");
recordButton.setPosition(400, 30); recordButton.setPosition(400, 30);
recordButton.onClickEventId = "record_button_press"; recordButton.onClickEventId = "record_button_press";
let stopButton = new Button(); let stopButton = uiLayer.canvas.add(Button);
stopButton.setSize(100, 50); stopButton.setSize(100, 50);
stopButton.setText("Stop"); stopButton.setText("Stop");
stopButton.setPosition(550, 30); stopButton.setPosition(550, 30);
stopButton.onClickEventId = "stop_button_press"; stopButton.onClickEventId = "stop_button_press";
let playButton = new Button(); let playButton = uiLayer.canvas.add(Button);
playButton.setSize(100, 50); playButton.setSize(100, 50);
playButton.setText("Play"); playButton.setText("Play");
playButton.setPosition(700, 30); playButton.setPosition(700, 30);
playButton.onClickEventId = "play_button_press"; playButton.onClickEventId = "play_button_press";
let cycleFramerateButton = new Button(); let cycleFramerateButton = uiLayer.canvas.add(Button);
cycleFramerateButton.setSize(150, 50); cycleFramerateButton.setSize(150, 50);
cycleFramerateButton.setText("Cycle FPS"); cycleFramerateButton.setText("Cycle FPS");
cycleFramerateButton.setPosition(5, 400); cycleFramerateButton.setPosition(5, 400);
@ -45,38 +54,48 @@ function main(){
i = (i + 1) % 3; i = (i + 1) % 3;
} }
let pauseButton = new Button(); let pauseButton = uiLayer.canvas.add(Button);
pauseButton.setSize(100, 50); pauseButton.setSize(100, 50);
pauseButton.setText("Pause"); pauseButton.setText("Pause");
pauseButton.setPosition(700, 400); pauseButton.setPosition(700, 400);
pauseButton.onClick = () => { pauseButton.onClick = () => {
game.getGameState().addScene(pauseMenu); mainScene.setPaused(true);
pauseMenu.enable();
} }
let modalBackground = new UIElement(); let modalBackground = pauseMenu.canvas.add(UIElement);
modalBackground.setSize(400, 200); modalBackground.setSize(400, 200);
modalBackground.setBackgroundColor(new Color(0, 0, 0, 0.4)); modalBackground.setBackgroundColor(new Color(0, 0, 0, 0.4));
modalBackground.setPosition(200, 100); modalBackground.setPosition(200, 100);
let resumeButton = new Button(); let resumeButton = pauseMenu.canvas.add(Button);
resumeButton.setSize(100, 50); resumeButton.setSize(100, 50);
resumeButton.setText("Resume"); resumeButton.setText("Resume");
resumeButton.setPosition(400, 200); resumeButton.setPosition(400, 200);
resumeButton.onClick = () => { resumeButton.onClick = () => {
game.getGameState().removeScene(); mainScene.setPaused(false);
pauseMenu.disable();
} }
let lotsOfCircs = [];
for(let i = 0; i < 10; i++){ 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]); for(let i = 0; i < 30; i++){
mainScene.getViewport().follow(player); let cc = foregroundLayer.canvas.add(ColoredCircle);
pauseMenu.add([modalBackground, resumeButton]); cc.setSize(80, 80);
cc.setColor(cc.getColor().lighten().lighten())
cc.getColor().a = 0.5;
}
game.getGameState().changeScene(mainScene); pauseMenu.disable();
game.start(); game.start();
} }