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 Scene from "./Scene";
import Viewport from "../SceneGraph/Viewport";
import Vec2 from "../DataTypes/Vec2";
export default class GameState{
private sceneStack: Stack<Scene>;
private sceneStack: Stack<Scene>;
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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<GameNode>;
protected nodeMap: Map<CanvasNode>;
protected idCounter: number;
constructor(viewport: Viewport){
this.viewport = viewport;
this.nodeMap = new Map<GameNode>();
this.nodeMap = new Map<CanvasNode>();
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<GameNode>;
abstract getVisibleSet(): Array<CanvasNode>;
}

View File

@ -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<CanvasNode>;
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<CanvasNode>();
this.turnOffViewportCulling_demoTool = false;
@ -57,7 +60,7 @@ export default class SceneGraphArray extends SceneGraph{
let visibleSet = new Array<CanvasNode>();
for(let node of this.nodeList){
if(this.viewport.includes(node)){
if(this.viewport.includes(node, this.scene.getParallax())){
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 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;
}
}

View File

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