added canvasItem factory and added parallax
This commit is contained in:
parent
b33b7b0a21
commit
98c23cda40
19
src/GameState/Factories/CanvasNodeFactory.ts
Normal file
19
src/GameState/Factories/CanvasNodeFactory.ts
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
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);
|
||||
// Factories
|
||||
public canvas: CavnasNodeFactory;
|
||||
|
||||
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 {
|
||||
|
@ -28,18 +37,38 @@ export default class Scene{
|
|||
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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
55
src/main.ts
55
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();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user