From 4b8ebf360d226675d884be34d00c94a0bdd15078 Mon Sep 17 00:00:00 2001 From: Joe Weaver Date: Mon, 21 Dec 2020 12:32:32 -0500 Subject: [PATCH] improved visual debugging --- src/DataTypes/Graphs/PositionGraph.ts | 12 +- src/DataTypes/Interfaces/Descriptors.ts | 9 +- src/DataTypes/Vec2.ts | 15 +- src/Debug/Debug.ts | 128 ++++++++++++++++-- src/Loop/GameLoop.ts | 29 +++- src/Nodes/CanvasNode.ts | 15 +- src/Nodes/GameNode.ts | 22 ++- src/Nodes/Graphics/Rect.ts | 4 +- src/Nodes/Tilemap.ts | 6 + src/Nodes/Tilemaps/OrthogonalTilemap.ts | 23 +++- src/Nodes/UIElements/Label.ts | 9 -- src/Physics/BasicPhysicsManager.ts | 4 - src/Physics/PhysicsManager.ts | 10 +- src/Rendering/CanvasRenderer.ts | 12 +- .../CanvasRendering/GraphicRenderer.ts | 14 +- .../CanvasRendering/UIElementRenderer.ts | 12 +- src/Scene/Scene.ts | 9 +- src/SceneGraph/SceneGraph.ts | 6 + src/_DemoClasses/Enemies/Walk.ts | 1 - src/_DemoClasses/PhysicsTesting/TestScene.ts | 55 ++++++++ .../Player/PlayerStates/Platformer/Jump.ts | 39 +++++- src/index.html | 32 ++++- src/main.ts | 1 + 23 files changed, 383 insertions(+), 84 deletions(-) create mode 100644 src/_DemoClasses/PhysicsTesting/TestScene.ts diff --git a/src/DataTypes/Graphs/PositionGraph.ts b/src/DataTypes/Graphs/PositionGraph.ts index adbc64a..e7eb943 100644 --- a/src/DataTypes/Graphs/PositionGraph.ts +++ b/src/DataTypes/Graphs/PositionGraph.ts @@ -1,8 +1,8 @@ import Graph, { MAX_V } from "./Graph"; import Vec2 from "../Vec2"; -import { Debug_Renderable } from "../Interfaces/Descriptors"; +import { DebugRenderable } from "../Interfaces/Descriptors"; -export default class PositionGraph extends Graph implements Debug_Renderable{ +export default class PositionGraph extends Graph implements DebugRenderable{ positions: Array; constructor(directed: boolean = false){ @@ -38,9 +38,9 @@ export default class PositionGraph extends Graph implements Debug_Renderable{ return "Node " + index + " - " + this.positions[index].toString(); } - debug_render(ctx: CanvasRenderingContext2D, origin: Vec2, zoom: number): void { - for(let point of this.positions){ - ctx.fillRect((point.x - origin.x - 4)*zoom, (point.y - origin.y - 4)*zoom, 8, 8); - } + debugRender = (): void => { + // for(let point of this.positions){ + // ctx.fillRect((point.x - origin.x - 4)*zoom, (point.y - origin.y - 4)*zoom, 8, 8); + // } } } \ No newline at end of file diff --git a/src/DataTypes/Interfaces/Descriptors.ts b/src/DataTypes/Interfaces/Descriptors.ts index 7f260bf..6db66c6 100644 --- a/src/DataTypes/Interfaces/Descriptors.ts +++ b/src/DataTypes/Interfaces/Descriptors.ts @@ -156,12 +156,7 @@ export interface Updateable { update: (deltaT: number) => void; } -export interface Renderable { - /** Renders this object. */ - render: (ctx: CanvasRenderingContext2D) => void; -} - -export interface Debug_Renderable { +export interface DebugRenderable { /** Renders the debugging infor for this object. */ - debug_render: (ctx: CanvasRenderingContext2D, origin: Vec2, zoom: number) => void; + debugRender(): void; } diff --git a/src/DataTypes/Vec2.ts b/src/DataTypes/Vec2.ts index 69ae54f..299aeca 100644 --- a/src/DataTypes/Vec2.ts +++ b/src/DataTypes/Vec2.ts @@ -307,17 +307,24 @@ export default class Vec2 { * @param other The vector to check against */ equals(other: Vec2): boolean { - let xEq = Math.abs(this.x - other.x) < 0.00000001; - let yEq = Math.abs(this.y - other.y) < 0.00000001; + let xEq = Math.abs(this.x - other.x) < 0.0000001; + let yEq = Math.abs(this.y - other.y) < 0.0000001; return xEq && yEq; } /** - * Returns true if this vector is the zero vector + * Returns true if this vector is the zero vector exactly (not assured to be safe for floats). + */ + strictIsZero(): boolean { + return this.x === 0 && this.y === 0; + } + + /** + * Returns true if this x and y for this vector are both zero. */ isZero(): boolean { - return this.x === 0 && this.y === 0; + return Math.abs(this.x) < 0.0000001 && Math.abs(this.y) < 0.0000001; } /** diff --git a/src/Debug/Debug.ts b/src/Debug/Debug.ts index db56392..d7928d4 100644 --- a/src/Debug/Debug.ts +++ b/src/Debug/Debug.ts @@ -1,27 +1,135 @@ import Map from "../DataTypes/Map"; +import Vec2 from "../DataTypes/Vec2"; +import InputHandler from "../Input/InputHandler"; +import GameNode from "../Nodes/GameNode"; +import Color from "../Utils/Color"; + +type DebugRenderFunction = (ctx: CanvasRenderingContext2D) => void; export default class Debug { - // A map of log messages to display on the screen + /** A map of log messages to display on the screen */ private static logMessages: Map = new Map(); + /** An array of game nodes to render debug info for */ + private static nodes: Array; + + /** The rendering context for any debug messages */ + private static debugRenderingContext: CanvasRenderingContext2D; + + /** The size of the debug canvas */ + private static debugCanvasSize: Vec2; + + /** The rendering color for text */ + private static defaultTextColor: Color = Color.WHITE; + + /** + * Add a message to display on the debug screen + * @param id A unique ID for this message + * @param messages The messages to print to the debug screen + */ static log(id: string, ...messages: any): void { - let message = ""; - for(let i = 0; i < messages.length; i++){ - message += messages[i].toString(); - } + // let message = ""; + // for(let i = 0; i < messages.length; i++){ + // message += messages[i].toString(); + // } + // Join all messages with spaces + let message = messages.map((m: any) => m.toString()).join(" "); this.logMessages.add(id, message); } - // TODO: Create a method that can delete messages from the log + /** + * Deletes a a key from the log and stops it from keeping up space on the screen + * @param id + */ + static clearLogItem(id: string): void { + this.logMessages.delete(id); + } - static render(ctx: CanvasRenderingContext2D): void { + /** + * Sets the list of nodes to render with the debugger + * @param nodes The new list of nodes + */ + static setNodes(nodes: Array): void { + this.nodes = nodes; + } + + /** + * Draws a box at the specified position + * @param center The center of the box + * @param halfSize The dimensions of the box + * @param filled A boolean for whether or not the box is filled + */ + static drawBox(center: Vec2, halfSize: Vec2, filled: boolean, color: Color): void { + if(filled){ + this.debugRenderingContext.fillStyle = color.toString(); + this.debugRenderingContext.fillRect(center.x - halfSize.x, center.y - halfSize.y, halfSize.x*2, halfSize.y*2); + } else { + let lineWidth = 2; + this.debugRenderingContext.lineWidth = lineWidth; + this.debugRenderingContext.strokeStyle = color.toString(); + this.debugRenderingContext.strokeRect(center.x - halfSize.x, center.y - halfSize.y, halfSize.x*2, halfSize.y*2); + } + } + + static drawRay(from: Vec2, to: Vec2, color: Color): void { + this.debugRenderingContext.lineWidth = 2; + this.debugRenderingContext.strokeStyle = color.toString(); + + this.debugRenderingContext.beginPath(); + this.debugRenderingContext.moveTo(from.x, from.y); + this.debugRenderingContext.lineTo(to.x, to.y); + this.debugRenderingContext.closePath(); + this.debugRenderingContext.stroke(); + } + + static drawPoint(pos: Vec2, color: Color): void { + let pointSize = 6; + this.debugRenderingContext.fillStyle = color.toString(); + this.debugRenderingContext.fillRect(pos.x - pointSize/2, pos.y - pointSize/2, pointSize, pointSize); + } + + static setDefaultTextColor(color: Color): void { + this.defaultTextColor = color; + } + + static initializeDebugCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D { + canvas.width = width; + canvas.height = height; + + this.debugCanvasSize = new Vec2(width, height); + + this.debugRenderingContext = canvas.getContext("2d"); + + return this.debugRenderingContext; + } + + static clearCanvas(): void { + this.debugRenderingContext.clearRect(0, 0, this.debugCanvasSize.x, this.debugCanvasSize.y); + } + + static render(): void { + this.renderText(); + this.renderNodes(); + } + + static renderText(): void { let y = 20; - ctx.font = "20px Arial"; - ctx.fillStyle = "#000000"; + this.debugRenderingContext.font = "20px Arial"; + this.debugRenderingContext.fillStyle = this.defaultTextColor.toString(); + + // Draw all of the text this.logMessages.forEach((key: string) => { - ctx.fillText(this.logMessages.get(key), 10, y) + this.debugRenderingContext.fillText(this.logMessages.get(key), 10, y) y += 30; }); } + + static renderNodes(): void { + if(this.nodes){ + this.nodes.forEach(node => { + node.debugRender(); + }); + } + } } \ No newline at end of file diff --git a/src/Loop/GameLoop.ts b/src/Loop/GameLoop.ts index 675a578..6359d71 100644 --- a/src/Loop/GameLoop.ts +++ b/src/Loop/GameLoop.ts @@ -59,7 +59,8 @@ export default class GameLoop { private numUpdateSteps: number; // Game canvas and its width and height - readonly GAME_CANVAS: HTMLCanvasElement; + readonly GAME_CANVAS: HTMLCanvasElement; + readonly DEBUG_CANVAS: HTMLCanvasElement; readonly WIDTH: number; readonly HEIGHT: number; private viewport: Viewport; @@ -100,7 +101,7 @@ export default class GameLoop { // Get the game canvas and give it a background color this.GAME_CANVAS = document.getElementById("game-canvas"); - this.GAME_CANVAS.style.setProperty("background-color", "whitesmoke"); + this.DEBUG_CANVAS = document.getElementById("debug-canvas"); // Give the canvas a size and get the rendering context this.WIDTH = this.gameOptions.viewportSize.x; @@ -108,9 +109,14 @@ export default class GameLoop { // For now, just hard code a canvas renderer. We can do this with options later this.renderingManager = new CanvasRenderer(); + this.initializeGameWindow(); this.ctx = this.renderingManager.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT); this.clearColor = new Color(this.gameOptions.clearColor.r, this.gameOptions.clearColor.g, this.gameOptions.clearColor.b); + // Initialize debug canvas + + Debug.initializeDebugCanvas(this.DEBUG_CANVAS, this.WIDTH, this.HEIGHT); + // Size the viewport to the game canvas this.viewport = new Viewport(); this.viewport.setCanvasSize(this.WIDTH, this.HEIGHT); @@ -129,6 +135,17 @@ export default class GameLoop { Stats.initStats(); } + /** + * Set up the game window that holds the canvases + */ + private initializeGameWindow(): void { + const gameWindow = document.getElementById("game-window"); + + // Set the height of the game window + gameWindow.style.width = this.WIDTH + "px"; + gameWindow.style.height = this.HEIGHT + "px"; + } + /** * Changes the maximum allowed physics framerate of the game * @param initMax @@ -274,11 +291,17 @@ export default class GameLoop { * Clears the canvas and defers scene rendering to the sceneManager. Renders the debug */ render(): void { + // Clear the canvases this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT); + Debug.clearCanvas(); + + // Game Canvas this.ctx.fillStyle = this.clearColor.toString(); this.ctx.fillRect(0, 0, this.WIDTH, this.HEIGHT); this.sceneManager.render(); - Debug.render(this.ctx); + + // Debug render + Debug.render(); Stats.render(); } } diff --git a/src/Nodes/CanvasNode.ts b/src/Nodes/CanvasNode.ts index 84b8c34..93254d8 100644 --- a/src/Nodes/CanvasNode.ts +++ b/src/Nodes/CanvasNode.ts @@ -1,7 +1,9 @@ import GameNode from "./GameNode"; import Vec2 from "../DataTypes/Vec2"; -import { Region, Renderable } from "../DataTypes/Interfaces/Descriptors"; +import { Region } from "../DataTypes/Interfaces/Descriptors"; import AABB from "../DataTypes/Shapes/AABB"; +import Debug from "../Debug/Debug"; +import Color from "../Utils/Color"; /** * The representation of an object in the game world that can be drawn to the screen @@ -75,6 +77,12 @@ export default abstract class CanvasNode extends GameNode implements Region { return this._boundary; } + get sizeWithZoom(): Vec2 { + let zoom = this.scene.getViewScale(); + + return this.boundary.halfSize.clone().scaled(zoom, zoom); + } + /** * Returns true if the point (x, y) is inside of this canvas object * @param x @@ -83,4 +91,9 @@ export default abstract class CanvasNode extends GameNode implements Region { contains(x: number, y: number): boolean { return this._boundary.containsPoint(new Vec2(x, y)); } + + debugRender(): void { + super.debugRender(); + Debug.drawBox(this.relativePosition, this.sizeWithZoom, false, Color.GREEN); + } } \ No newline at end of file diff --git a/src/Nodes/GameNode.ts b/src/Nodes/GameNode.ts index 78ba44a..55f8039 100644 --- a/src/Nodes/GameNode.ts +++ b/src/Nodes/GameNode.ts @@ -4,17 +4,19 @@ import Receiver from "../Events/Receiver"; import Emitter from "../Events/Emitter"; import Scene from "../Scene/Scene"; import Layer from "../Scene/Layer"; -import { Physical, Positioned, isRegion, Unique, Updateable, Actor, AI, Debug_Renderable } from "../DataTypes/Interfaces/Descriptors" +import { Physical, Positioned, isRegion, Unique, Updateable, Actor, AI, DebugRenderable } from "../DataTypes/Interfaces/Descriptors" import Shape from "../DataTypes/Shapes/Shape"; import Map from "../DataTypes/Map"; import AABB from "../DataTypes/Shapes/AABB"; import NavigationPath from "../Pathfinding/NavigationPath"; import TweenManager from "../Rendering/Animations/TweenManager"; +import Debug from "../Debug/Debug"; +import Color from "../Utils/Color"; /** * The representation of an object in the game world */ -export default abstract class GameNode implements Positioned, Unique, Updateable, Physical, Actor { +export default abstract class GameNode implements Positioned, Unique, Updateable, Physical, Actor, DebugRenderable { /*---------- POSITIONED ----------*/ private _position: Vec2; @@ -79,6 +81,13 @@ export default abstract class GameNode implements Positioned, Unique, Updateable this.positionChanged(); } + get relativePosition(): Vec2 { + let origin = this.scene.getViewTranslation(this); + let zoom = this.scene.getViewScale(); + + return this.position.clone().sub(origin).scale(zoom); + } + /*---------- UNIQUE ----------*/ get id(): number { return this._id; @@ -249,6 +258,15 @@ export default abstract class GameNode implements Positioned, Unique, Updateable update(deltaT: number): void { this.tweens.update(deltaT); } + + debugRender(): void { + Debug.drawPoint(this.relativePosition, Color.GREEN); + + // If velocity is not zero, draw a vector for it + if(this._velocity && !this._velocity.isZero()){ + Debug.drawRay(this.relativePosition, this._velocity.clone().scaleTo(20).add(this.relativePosition), Color.GREEN); + } + } } export enum TweenableProperties{ diff --git a/src/Nodes/Graphics/Rect.ts b/src/Nodes/Graphics/Rect.ts index c97c6c9..4ea2c54 100644 --- a/src/Nodes/Graphics/Rect.ts +++ b/src/Nodes/Graphics/Rect.ts @@ -4,14 +4,14 @@ import Color from "../../Utils/Color"; export default class Rect extends Graphic { - protected borderColor: Color; + borderColor: Color; protected borderWidth: number; constructor(position: Vec2, size: Vec2){ super(); this.position = position; this.size = size; - this.borderColor = this.color; + this.borderColor = Color.TRANSPARENT; this.borderWidth = 0; } diff --git a/src/Nodes/Tilemap.ts b/src/Nodes/Tilemap.ts index 5a5fcbf..3cdb656 100644 --- a/src/Nodes/Tilemap.ts +++ b/src/Nodes/Tilemap.ts @@ -49,6 +49,12 @@ export default abstract class Tilemap extends CanvasNode { return this.tileSize.scaled(this.scale.x, this.scale.y); } + getTileSizeWithZoom(): Vec2 { + let zoom = this.scene.getViewScale(); + + return this.getTileSize().scale(zoom); + } + /** Adds this tilemap to the physics system */ addPhysics = (): void => { this.scene.getPhysicsManager().registerTilemap(this); diff --git a/src/Nodes/Tilemaps/OrthogonalTilemap.ts b/src/Nodes/Tilemaps/OrthogonalTilemap.ts index 0a7f18d..99eb532 100644 --- a/src/Nodes/Tilemaps/OrthogonalTilemap.ts +++ b/src/Nodes/Tilemaps/OrthogonalTilemap.ts @@ -1,7 +1,8 @@ import Tilemap from "../Tilemap"; import Vec2 from "../../DataTypes/Vec2"; import { TiledTilemapData, TiledLayerData } from "../../DataTypes/Tilesets/TiledData"; -import Tileset from "../../DataTypes/Tilesets/Tileset"; +import Debug from "../../Debug/Debug"; +import Color from "../../Utils/Color"; /** * The representation of an orthogonal tilemap - i.e. a top down or platformer tilemap @@ -46,6 +47,10 @@ export default class OrthogonalTilemap extends Tilemap { } } + getDimensions(): Vec2 { + return new Vec2(this.numCols, this.numRows); + } + getTileAtWorldPosition(worldCoords: Vec2): number { let localCoords = this.getColRowAt(worldCoords); return this.getTileAtRowCol(localCoords); @@ -128,4 +133,20 @@ export default class OrthogonalTilemap extends Tilemap { } update(deltaT: number): void {} + + debugRender(){ + let tileSize = this.getTileSizeWithZoom(); + let origin = this.relativePosition.sub(this.sizeWithZoom); + + for(let col = 0; col < this.numCols; col++){ + for(let row = 0; row < this.numRows; row++){ + if(this.isCollidable && this.isTileCollidable(col, row)){ + // Draw a box for this tile + let center = new Vec2(origin.x + (col + 0.5)*tileSize.x, origin.y + (row + 0.5)*tileSize.y); + + Debug.drawBox(center, tileSize.scaled(0.5), false, Color.BLUE); + } + } + } + } } \ No newline at end of file diff --git a/src/Nodes/UIElements/Label.ts b/src/Nodes/UIElements/Label.ts index 0071b7b..b290fab 100644 --- a/src/Nodes/UIElements/Label.ts +++ b/src/Nodes/UIElements/Label.ts @@ -100,13 +100,4 @@ export default class Label extends UIElement{ sizeToText(): void { this.sizeAssigned = false; } - - debug_render = (ctx: CanvasRenderingContext2D): void => { - let origin = this.scene.getViewTranslation(this); - - ctx.lineWidth = 4; - ctx.strokeStyle = "#00FF00" - let b = this.boundary; - ctx.strokeRect(b.x - b.hw - origin.x, b.y - b.hh - origin.y, b.hw*2, b.hh*2); - }; } \ No newline at end of file diff --git a/src/Physics/BasicPhysicsManager.ts b/src/Physics/BasicPhysicsManager.ts index a3d7587..a93a286 100644 --- a/src/Physics/BasicPhysicsManager.ts +++ b/src/Physics/BasicPhysicsManager.ts @@ -326,10 +326,6 @@ export default class BasicPhysicsManager extends PhysicsManager { } } } - - debug_render(ctx: CanvasRenderingContext2D): void { - - } } // Collision data objects for tilemaps diff --git a/src/Physics/PhysicsManager.ts b/src/Physics/PhysicsManager.ts index 0da7662..0863bfc 100644 --- a/src/Physics/PhysicsManager.ts +++ b/src/Physics/PhysicsManager.ts @@ -1,12 +1,12 @@ import GameNode from "../Nodes/GameNode"; import Vec2 from "../DataTypes/Vec2"; -import { Debug_Renderable, Updateable } from "../DataTypes/Interfaces/Descriptors"; +import { Updateable } from "../DataTypes/Interfaces/Descriptors"; import Tilemap from "../Nodes/Tilemap"; import Receiver from "../Events/Receiver"; import Emitter from "../Events/Emitter"; import Map from "../DataTypes/Map"; -export default abstract class PhysicsManager implements Updateable, Debug_Renderable { +export default abstract class PhysicsManager implements Updateable { protected receiver: Receiver; protected emitter: Emitter; @@ -43,12 +43,6 @@ export default abstract class PhysicsManager implements Updateable, Debug_Render */ abstract update(deltaT: number): void; - /** - * Renders any debug shapes or graphics - * @param ctx - */ - abstract debug_render(ctx: CanvasRenderingContext2D): void; - setLayer(node: GameNode, layer: string): void { node.physicsLayer = this.layerMap.get(layer); } diff --git a/src/Rendering/CanvasRenderer.ts b/src/Rendering/CanvasRenderer.ts index 019b458..d373399 100644 --- a/src/Rendering/CanvasRenderer.ts +++ b/src/Rendering/CanvasRenderer.ts @@ -199,9 +199,9 @@ export default class CanvasRenderer extends RenderingManager { protected renderGraphic(graphic: Graphic): void { if(graphic instanceof Point){ - this.graphicRenderer.renderPoint(graphic, this.origin, this.zoom); + this.graphicRenderer.renderPoint(graphic, this.zoom); } else if(graphic instanceof Rect){ - this.graphicRenderer.renderRect(graphic, this.origin, this.zoom); + this.graphicRenderer.renderRect(graphic, this.zoom); } } @@ -213,13 +213,13 @@ export default class CanvasRenderer extends RenderingManager { protected renderUIElement(uiElement: UIElement): void { if(uiElement instanceof Label){ - this.uiElementRenderer.renderLabel(uiElement, this.origin, this.zoom); + this.uiElementRenderer.renderLabel(uiElement); } else if(uiElement instanceof Button){ - this.uiElementRenderer.renderButton(uiElement, this.origin, this.zoom); + this.uiElementRenderer.renderButton(uiElement); } else if(uiElement instanceof Slider){ - this.uiElementRenderer.renderSlider(uiElement, this.origin, this.zoom); + this.uiElementRenderer.renderSlider(uiElement); } else if(uiElement instanceof TextInput){ - this.uiElementRenderer.renderTextInput(uiElement, this.origin, this.zoom); + this.uiElementRenderer.renderTextInput(uiElement); } } } \ No newline at end of file diff --git a/src/Rendering/CanvasRendering/GraphicRenderer.ts b/src/Rendering/CanvasRendering/GraphicRenderer.ts index a18bce9..4835272 100644 --- a/src/Rendering/CanvasRendering/GraphicRenderer.ts +++ b/src/Rendering/CanvasRendering/GraphicRenderer.ts @@ -18,22 +18,24 @@ export default class GraphicRenderer { this.scene = scene; } - renderPoint(point: Point, origin: Vec2, zoom: number): void { + renderPoint(point: Point, zoom: number): void { this.ctx.fillStyle = point.color.toStringRGBA(); this.ctx.fillRect((-point.size.x/2)*zoom, (-point.size.y/2)*zoom, point.size.x*zoom, point.size.y*zoom); } - renderRect(rect: Rect, origin: Vec2, zoom: number): void { + renderRect(rect: Rect, zoom: number): void { // Draw the interior of the rect if(rect.color.a !== 0){ this.ctx.fillStyle = rect.color.toStringRGB(); this.ctx.fillRect((-rect.size.x/2)*zoom, (-rect.size.y/2)*zoom, rect.size.x*zoom, rect.size.y*zoom); } - // Draw the border of the rect - this.ctx.strokeStyle = rect.getBorderColor().toStringRGB(); - this.ctx.lineWidth = rect.getBorderWidth(); - this.ctx.strokeRect((-rect.size.x/2)*zoom, (-rect.size.y/2)*zoom, rect.size.x*zoom, rect.size.y*zoom); + // Draw the border of the rect if it isn't transparent + if(rect.borderColor.a !== 0){ + this.ctx.strokeStyle = rect.getBorderColor().toStringRGB(); + this.ctx.lineWidth = rect.getBorderWidth(); + this.ctx.strokeRect((-rect.size.x/2)*zoom, (-rect.size.y/2)*zoom, rect.size.x*zoom, rect.size.y*zoom); + } } } \ No newline at end of file diff --git a/src/Rendering/CanvasRendering/UIElementRenderer.ts b/src/Rendering/CanvasRendering/UIElementRenderer.ts index 6bb2bf1..818eb60 100644 --- a/src/Rendering/CanvasRendering/UIElementRenderer.ts +++ b/src/Rendering/CanvasRendering/UIElementRenderer.ts @@ -21,7 +21,7 @@ export default class UIElementRenderer { this.scene = scene; } - renderLabel(label: Label, origin: Vec2, zoom: number): void { + renderLabel(label: Label): void { // If the size is unassigned (by the user or automatically) assign it label.handleInitialSizing(this.ctx); @@ -51,11 +51,11 @@ export default class UIElementRenderer { this.ctx.globalAlpha = previousAlpha; } - renderButton(button: Button, origin: Vec2, zoom: number): void { - this.renderLabel(button, origin, zoom); + renderButton(button: Button): void { + this.renderLabel(button); } - renderSlider(slider: Slider, origin: Vec2, zoom: number): void { + renderSlider(slider: Slider): void { // Grab the global alpha so we can adjust it for this render let previousAlpha = this.ctx.globalAlpha; this.ctx.globalAlpha = slider.getLayer().getAlpha(); @@ -82,13 +82,13 @@ export default class UIElementRenderer { this.ctx.globalAlpha = previousAlpha; } - renderTextInput(textInput: TextInput, origin: Vec2, zoom: number): void { + renderTextInput(textInput: TextInput): void { // Show a cursor sometimes if(textInput.focused && textInput.cursorCounter % 60 > 30){ textInput.text += "|"; } - this.renderLabel(textInput, origin, zoom); + this.renderLabel(textInput); if(textInput.focused){ if(textInput.cursorCounter % 60 > 30){ diff --git a/src/Scene/Scene.ts b/src/Scene/Scene.ts index 0e9836b..b531974 100644 --- a/src/Scene/Scene.ts +++ b/src/Scene/Scene.ts @@ -12,7 +12,7 @@ import GameLoop from "../Loop/GameLoop"; import SceneManager from "./SceneManager"; import Receiver from "../Events/Receiver"; import Emitter from "../Events/Emitter"; -import { Renderable, Updateable } from "../DataTypes/Interfaces/Descriptors"; +import { Updateable } from "../DataTypes/Interfaces/Descriptors"; import NavigationManager from "../Pathfinding/NavigationManager"; import AIManager from "../AI/AIManager"; import Map from "../DataTypes/Map"; @@ -22,8 +22,9 @@ import CanvasNode from "../Nodes/CanvasNode"; import GameNode from "../Nodes/GameNode"; import ArrayUtils from "../Utils/ArrayUtils"; import RenderingManager from "../Rendering/RenderingManager"; +import Debug from "../Debug/Debug"; -export default class Scene implements Updateable, Renderable { +export default class Scene implements Updateable { /** The size of the game world. */ protected worldSize: Vec2; @@ -164,6 +165,10 @@ export default class Scene implements Updateable, Renderable { // Send the visible set, tilemaps, and uiLayers to the renderer this.renderingManager.render(visibleSet, this.tilemaps, this.uiLayers); + + let nodes = this.sceneGraph.getAllNodes(); + this.tilemaps.forEach(tilemap => tilemap.visible && tilemap.debugRender()); + Debug.setNodes(nodes); } setRunning(running: boolean): void { diff --git a/src/SceneGraph/SceneGraph.ts b/src/SceneGraph/SceneGraph.ts index e71e004..0851048 100644 --- a/src/SceneGraph/SceneGraph.ts +++ b/src/SceneGraph/SceneGraph.ts @@ -83,6 +83,12 @@ export default abstract class SceneGraph { abstract getNodesInRegion(boundary: AABB): Array; + getAllNodes(): Array { + let arr = new Array(); + this.nodeMap.forEach(key => arr.push(this.nodeMap.get(key))); + return arr; + } + /** * The specific implementation of getting a node at certain coordinates * @param x diff --git a/src/_DemoClasses/Enemies/Walk.ts b/src/_DemoClasses/Enemies/Walk.ts index 81a09f7..2b3469d 100644 --- a/src/_DemoClasses/Enemies/Walk.ts +++ b/src/_DemoClasses/Enemies/Walk.ts @@ -27,7 +27,6 @@ export default class Walk extends OnGround { } if(this.parent.jumpy && (Date.now() - this.time > 500)){ - console.log("Jump"); this.finished(GoombaStates.JUMP); this.parent.velocity.y = -300; } diff --git a/src/_DemoClasses/PhysicsTesting/TestScene.ts b/src/_DemoClasses/PhysicsTesting/TestScene.ts new file mode 100644 index 0000000..e85ab4b --- /dev/null +++ b/src/_DemoClasses/PhysicsTesting/TestScene.ts @@ -0,0 +1,55 @@ +import Vec2 from "../../DataTypes/Vec2"; +import InputHandler from "../../Input/InputHandler"; +import InputReceiver from "../../Input/InputReceiver"; +import { GraphicType } from "../../Nodes/Graphics/GraphicTypes"; +import BasicPhysicsManager from "../../Physics/BasicPhysicsManager"; +import Scene from "../../Scene/Scene"; +import Color from "../../Utils/Color"; + +export default class TestScene extends Scene { + + startScene(){ + // Opt into a custom physics manager + this.physicsManager = new BasicPhysicsManager(this.sceneOptions.physics); + + this.addLayer("main"); + + let player = this.add.graphic(GraphicType.RECT, "main", {position: new Vec2(100, 100), size: new Vec2(100, 100)}); + player.addPhysics(); + + player.update = (deltaT: number) => { + const input = InputReceiver.getInstance() + + let xDir = (input.isPressed("a") ? -1 : 0) + (input.isPressed("d") ? 1 : 0); + let yDir = (input.isPressed("w") ? -1 : 0) + (input.isPressed("s") ? 1 : 0); + + let dir = new Vec2(xDir, yDir); + dir.normalize(); + + if(!dir.isZero()){ + player.move(dir.scale(deltaT * 300)); + } + } + + let block = this.add.graphic(GraphicType.RECT, "main", {position: new Vec2(300, 500), size: new Vec2(100, 100)}); + block.color = Color.CYAN; + block.addPhysics(block.boundary, true, true); + + let movingBlock = this.add.graphic(GraphicType.RECT, "main", {position: new Vec2(500, 200), size: new Vec2(100, 100)}); + movingBlock.color = Color.CYAN; + movingBlock.addPhysics(); + + let timer = 0; + let dir = new Vec2(1, 0); + movingBlock.update = (deltaT: number) => { + if(timer > 0.5){ + timer = 0; + dir.scale(-1); + } + + movingBlock.move(dir.scaled(200*deltaT)); + + timer += deltaT; + } + } +} \ No newline at end of file diff --git a/src/_DemoClasses/Player/PlayerStates/Platformer/Jump.ts b/src/_DemoClasses/Player/PlayerStates/Platformer/Jump.ts index 0a977cb..3b4a02c 100644 --- a/src/_DemoClasses/Player/PlayerStates/Platformer/Jump.ts +++ b/src/_DemoClasses/Player/PlayerStates/Platformer/Jump.ts @@ -26,16 +26,45 @@ export default class Jump extends PlayerState { // Go up plus some extra pos.y -= (this.owner.collisionShape.halfSize.y + 10); - pos = this.parent.tilemap.getColRowAt(pos); - let tile = this.parent.tilemap.getTileAtRowCol(pos); + pos.x -= 16; + let rowCol = this.parent.tilemap.getColRowAt(pos); + let tile1 = this.parent.tilemap.getTileAtRowCol(rowCol); + pos.x += 16; + rowCol = this.parent.tilemap.getColRowAt(pos); + let tile2 = this.parent.tilemap.getTileAtRowCol(rowCol); + pos.x += 16; + rowCol = this.parent.tilemap.getColRowAt(pos); + let tile3 = this.parent.tilemap.getTileAtRowCol(rowCol); + let t1 = tile1 === 17; + let t2 = tile2 === 17; + let t3 = tile3 === 17; + let air1 = tile1 === 0; + let air2 = tile2 === 0; + let air3 = tile3 === 0; + let majority = (t1 && t2) || (t1 && t3) || (t2 && t3) || (t1 && t2 && t3); + let minorityButAir = (t1 && air2 && air3) || (air1 && t2 && air3) || (air1 && air2 && t3); + // If coin block, change to empty coin block - if(tile === 17){ - this.parent.tilemap.setTileAtRowCol(pos, 18); + if(majority || minorityButAir){ + if(minorityButAir){ + // Get the correct position + if(t1){ + pos.x -= 32; + } else if(t2){ + pos.x -= 16; + } + rowCol = this.parent.tilemap.getColRowAt(pos); + } else { + pos.x -= 16; + rowCol = this.parent.tilemap.getColRowAt(pos); + } + + this.parent.tilemap.setTileAtRowCol(rowCol, 18); this.emitter.fireEvent(MarioEvents.PLAYER_HIT_COIN_BLOCK); let tileSize = this.parent.tilemap.getTileSize(); - this.parent.coin.position.copy(pos.scale(tileSize.x, tileSize.y).add(tileSize.scaled(0.5))); + this.parent.coin.position.copy(rowCol.scale(tileSize.x, tileSize.y).add(tileSize.scaled(0.5))); // Animate collision this.parent.coin.tweens.add("coin", { diff --git a/src/index.html b/src/index.html index a20ad98..451a782 100644 --- a/src/index.html +++ b/src/index.html @@ -3,10 +3,40 @@ Game +
- + +
+ + + + +
+ +