improved visual debugging

This commit is contained in:
Joe Weaver 2020-12-21 12:32:32 -05:00
parent b8849b4c84
commit 4b8ebf360d
23 changed files with 383 additions and 84 deletions

View File

@ -1,8 +1,8 @@
import Graph, { MAX_V } from "./Graph"; import Graph, { MAX_V } from "./Graph";
import Vec2 from "../Vec2"; 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<Vec2>; positions: Array<Vec2>;
constructor(directed: boolean = false){ constructor(directed: boolean = false){
@ -38,9 +38,9 @@ export default class PositionGraph extends Graph implements Debug_Renderable{
return "Node " + index + " - " + this.positions[index].toString(); return "Node " + index + " - " + this.positions[index].toString();
} }
debug_render(ctx: CanvasRenderingContext2D, origin: Vec2, zoom: number): void { debugRender = (): void => {
for(let point of this.positions){ // for(let point of this.positions){
ctx.fillRect((point.x - origin.x - 4)*zoom, (point.y - origin.y - 4)*zoom, 8, 8); // ctx.fillRect((point.x - origin.x - 4)*zoom, (point.y - origin.y - 4)*zoom, 8, 8);
} // }
} }
} }

View File

@ -156,12 +156,7 @@ export interface Updateable {
update: (deltaT: number) => void; update: (deltaT: number) => void;
} }
export interface Renderable { export interface DebugRenderable {
/** Renders this object. */
render: (ctx: CanvasRenderingContext2D) => void;
}
export interface Debug_Renderable {
/** Renders the debugging infor for this object. */ /** Renders the debugging infor for this object. */
debug_render: (ctx: CanvasRenderingContext2D, origin: Vec2, zoom: number) => void; debugRender(): void;
} }

View File

@ -307,17 +307,24 @@ export default class Vec2 {
* @param other The vector to check against * @param other The vector to check against
*/ */
equals(other: Vec2): boolean { equals(other: Vec2): boolean {
let xEq = Math.abs(this.x - other.x) < 0.00000001; let xEq = Math.abs(this.x - other.x) < 0.0000001;
let yEq = Math.abs(this.y - other.y) < 0.00000001; let yEq = Math.abs(this.y - other.y) < 0.0000001;
return xEq && yEq; 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 { isZero(): boolean {
return this.x === 0 && this.y === 0; return Math.abs(this.x) < 0.0000001 && Math.abs(this.y) < 0.0000001;
} }
/** /**

View File

@ -1,27 +1,135 @@
import Map from "../DataTypes/Map"; 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 { 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<string> = new Map(); private static logMessages: Map<string> = new Map();
/** An array of game nodes to render debug info for */
private static nodes: Array<GameNode>;
/** 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 { static log(id: string, ...messages: any): void {
let message = ""; // let message = "";
for(let i = 0; i < messages.length; i++){ // for(let i = 0; i < messages.length; i++){
message += messages[i].toString(); // message += messages[i].toString();
} // }
// Join all messages with spaces
let message = messages.map((m: any) => m.toString()).join(" ");
this.logMessages.add(id, message); 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<GameNode>): 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; let y = 20;
ctx.font = "20px Arial"; this.debugRenderingContext.font = "20px Arial";
ctx.fillStyle = "#000000"; this.debugRenderingContext.fillStyle = this.defaultTextColor.toString();
// Draw all of the text
this.logMessages.forEach((key: string) => { this.logMessages.forEach((key: string) => {
ctx.fillText(this.logMessages.get(key), 10, y) this.debugRenderingContext.fillText(this.logMessages.get(key), 10, y)
y += 30; y += 30;
}); });
} }
static renderNodes(): void {
if(this.nodes){
this.nodes.forEach(node => {
node.debugRender();
});
}
}
} }

View File

@ -60,6 +60,7 @@ export default class GameLoop {
// Game canvas and its width and height // Game canvas and its width and height
readonly GAME_CANVAS: HTMLCanvasElement; readonly GAME_CANVAS: HTMLCanvasElement;
readonly DEBUG_CANVAS: HTMLCanvasElement;
readonly WIDTH: number; readonly WIDTH: number;
readonly HEIGHT: number; readonly HEIGHT: number;
private viewport: Viewport; private viewport: Viewport;
@ -100,7 +101,7 @@ export default class GameLoop {
// Get the game canvas and give it a background color // Get the game canvas and give it a background color
this.GAME_CANVAS = <HTMLCanvasElement>document.getElementById("game-canvas"); this.GAME_CANVAS = <HTMLCanvasElement>document.getElementById("game-canvas");
this.GAME_CANVAS.style.setProperty("background-color", "whitesmoke"); this.DEBUG_CANVAS = <HTMLCanvasElement>document.getElementById("debug-canvas");
// Give the canvas a size and get the rendering context // Give the canvas a size and get the rendering context
this.WIDTH = this.gameOptions.viewportSize.x; 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 // For now, just hard code a canvas renderer. We can do this with options later
this.renderingManager = new CanvasRenderer(); this.renderingManager = new CanvasRenderer();
this.initializeGameWindow();
this.ctx = this.renderingManager.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT); 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); 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 // Size the viewport to the game canvas
this.viewport = new Viewport(); this.viewport = new Viewport();
this.viewport.setCanvasSize(this.WIDTH, this.HEIGHT); this.viewport.setCanvasSize(this.WIDTH, this.HEIGHT);
@ -129,6 +135,17 @@ export default class GameLoop {
Stats.initStats(); 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 * Changes the maximum allowed physics framerate of the game
* @param initMax * @param initMax
@ -274,11 +291,17 @@ export default class GameLoop {
* Clears the canvas and defers scene rendering to the sceneManager. Renders the debug * Clears the canvas and defers scene rendering to the sceneManager. Renders the debug
*/ */
render(): void { render(): void {
// Clear the canvases
this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT); this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT);
Debug.clearCanvas();
// Game Canvas
this.ctx.fillStyle = this.clearColor.toString(); this.ctx.fillStyle = this.clearColor.toString();
this.ctx.fillRect(0, 0, this.WIDTH, this.HEIGHT); this.ctx.fillRect(0, 0, this.WIDTH, this.HEIGHT);
this.sceneManager.render(); this.sceneManager.render();
Debug.render(this.ctx);
// Debug render
Debug.render();
Stats.render(); Stats.render();
} }
} }

View File

@ -1,7 +1,9 @@
import GameNode from "./GameNode"; import GameNode from "./GameNode";
import Vec2 from "../DataTypes/Vec2"; 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 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 * 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; 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 * Returns true if the point (x, y) is inside of this canvas object
* @param x * @param x
@ -83,4 +91,9 @@ export default abstract class CanvasNode extends GameNode implements Region {
contains(x: number, y: number): boolean { contains(x: number, y: number): boolean {
return this._boundary.containsPoint(new Vec2(x, y)); return this._boundary.containsPoint(new Vec2(x, y));
} }
debugRender(): void {
super.debugRender();
Debug.drawBox(this.relativePosition, this.sizeWithZoom, false, Color.GREEN);
}
} }

View File

@ -4,17 +4,19 @@ import Receiver from "../Events/Receiver";
import Emitter from "../Events/Emitter"; import Emitter from "../Events/Emitter";
import Scene from "../Scene/Scene"; import Scene from "../Scene/Scene";
import Layer from "../Scene/Layer"; 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 Shape from "../DataTypes/Shapes/Shape";
import Map from "../DataTypes/Map"; import Map from "../DataTypes/Map";
import AABB from "../DataTypes/Shapes/AABB"; import AABB from "../DataTypes/Shapes/AABB";
import NavigationPath from "../Pathfinding/NavigationPath"; import NavigationPath from "../Pathfinding/NavigationPath";
import TweenManager from "../Rendering/Animations/TweenManager"; 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 * 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 ----------*/ /*---------- POSITIONED ----------*/
private _position: Vec2; private _position: Vec2;
@ -79,6 +81,13 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
this.positionChanged(); 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 ----------*/ /*---------- UNIQUE ----------*/
get id(): number { get id(): number {
return this._id; return this._id;
@ -249,6 +258,15 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
update(deltaT: number): void { update(deltaT: number): void {
this.tweens.update(deltaT); 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{ export enum TweenableProperties{

View File

@ -4,14 +4,14 @@ import Color from "../../Utils/Color";
export default class Rect extends Graphic { export default class Rect extends Graphic {
protected borderColor: Color; borderColor: Color;
protected borderWidth: number; protected borderWidth: number;
constructor(position: Vec2, size: Vec2){ constructor(position: Vec2, size: Vec2){
super(); super();
this.position = position; this.position = position;
this.size = size; this.size = size;
this.borderColor = this.color; this.borderColor = Color.TRANSPARENT;
this.borderWidth = 0; this.borderWidth = 0;
} }

View File

@ -49,6 +49,12 @@ export default abstract class Tilemap extends CanvasNode {
return this.tileSize.scaled(this.scale.x, this.scale.y); 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 */ /** Adds this tilemap to the physics system */
addPhysics = (): void => { addPhysics = (): void => {
this.scene.getPhysicsManager().registerTilemap(this); this.scene.getPhysicsManager().registerTilemap(this);

View File

@ -1,7 +1,8 @@
import Tilemap from "../Tilemap"; import Tilemap from "../Tilemap";
import Vec2 from "../../DataTypes/Vec2"; import Vec2 from "../../DataTypes/Vec2";
import { TiledTilemapData, TiledLayerData } from "../../DataTypes/Tilesets/TiledData"; 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 * 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 { getTileAtWorldPosition(worldCoords: Vec2): number {
let localCoords = this.getColRowAt(worldCoords); let localCoords = this.getColRowAt(worldCoords);
return this.getTileAtRowCol(localCoords); return this.getTileAtRowCol(localCoords);
@ -128,4 +133,20 @@ export default class OrthogonalTilemap extends Tilemap {
} }
update(deltaT: number): void {} 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);
}
}
}
}
} }

View File

@ -100,13 +100,4 @@ export default class Label extends UIElement{
sizeToText(): void { sizeToText(): void {
this.sizeAssigned = false; 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);
};
} }

View File

@ -326,10 +326,6 @@ export default class BasicPhysicsManager extends PhysicsManager {
} }
} }
} }
debug_render(ctx: CanvasRenderingContext2D): void {
}
} }
// Collision data objects for tilemaps // Collision data objects for tilemaps

View File

@ -1,12 +1,12 @@
import GameNode from "../Nodes/GameNode"; import GameNode from "../Nodes/GameNode";
import Vec2 from "../DataTypes/Vec2"; 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 Tilemap from "../Nodes/Tilemap";
import Receiver from "../Events/Receiver"; import Receiver from "../Events/Receiver";
import Emitter from "../Events/Emitter"; import Emitter from "../Events/Emitter";
import Map from "../DataTypes/Map"; 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 receiver: Receiver;
protected emitter: Emitter; protected emitter: Emitter;
@ -43,12 +43,6 @@ export default abstract class PhysicsManager implements Updateable, Debug_Render
*/ */
abstract update(deltaT: number): void; 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 { setLayer(node: GameNode, layer: string): void {
node.physicsLayer = this.layerMap.get(layer); node.physicsLayer = this.layerMap.get(layer);
} }

View File

@ -199,9 +199,9 @@ export default class CanvasRenderer extends RenderingManager {
protected renderGraphic(graphic: Graphic): void { protected renderGraphic(graphic: Graphic): void {
if(graphic instanceof Point){ if(graphic instanceof Point){
this.graphicRenderer.renderPoint(<Point>graphic, this.origin, this.zoom); this.graphicRenderer.renderPoint(<Point>graphic, this.zoom);
} else if(graphic instanceof Rect){ } else if(graphic instanceof Rect){
this.graphicRenderer.renderRect(<Rect>graphic, this.origin, this.zoom); this.graphicRenderer.renderRect(<Rect>graphic, this.zoom);
} }
} }
@ -213,13 +213,13 @@ export default class CanvasRenderer extends RenderingManager {
protected renderUIElement(uiElement: UIElement): void { protected renderUIElement(uiElement: UIElement): void {
if(uiElement instanceof Label){ if(uiElement instanceof Label){
this.uiElementRenderer.renderLabel(uiElement, this.origin, this.zoom); this.uiElementRenderer.renderLabel(uiElement);
} else if(uiElement instanceof Button){ } else if(uiElement instanceof Button){
this.uiElementRenderer.renderButton(uiElement, this.origin, this.zoom); this.uiElementRenderer.renderButton(uiElement);
} else if(uiElement instanceof Slider){ } else if(uiElement instanceof Slider){
this.uiElementRenderer.renderSlider(uiElement, this.origin, this.zoom); this.uiElementRenderer.renderSlider(uiElement);
} else if(uiElement instanceof TextInput){ } else if(uiElement instanceof TextInput){
this.uiElementRenderer.renderTextInput(uiElement, this.origin, this.zoom); this.uiElementRenderer.renderTextInput(uiElement);
} }
} }
} }

View File

@ -18,22 +18,24 @@ export default class GraphicRenderer {
this.scene = scene; 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.fillStyle = point.color.toStringRGBA();
this.ctx.fillRect((-point.size.x/2)*zoom, (-point.size.y/2)*zoom, this.ctx.fillRect((-point.size.x/2)*zoom, (-point.size.y/2)*zoom,
point.size.x*zoom, point.size.y*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 // Draw the interior of the rect
if(rect.color.a !== 0){ if(rect.color.a !== 0){
this.ctx.fillStyle = rect.color.toStringRGB(); 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); 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 // Draw the border of the rect if it isn't transparent
if(rect.borderColor.a !== 0){
this.ctx.strokeStyle = rect.getBorderColor().toStringRGB(); this.ctx.strokeStyle = rect.getBorderColor().toStringRGB();
this.ctx.lineWidth = rect.getBorderWidth(); 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); this.ctx.strokeRect((-rect.size.x/2)*zoom, (-rect.size.y/2)*zoom, rect.size.x*zoom, rect.size.y*zoom);
} }
} }
}

View File

@ -21,7 +21,7 @@ export default class UIElementRenderer {
this.scene = scene; 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 // If the size is unassigned (by the user or automatically) assign it
label.handleInitialSizing(this.ctx); label.handleInitialSizing(this.ctx);
@ -51,11 +51,11 @@ export default class UIElementRenderer {
this.ctx.globalAlpha = previousAlpha; this.ctx.globalAlpha = previousAlpha;
} }
renderButton(button: Button, origin: Vec2, zoom: number): void { renderButton(button: Button): void {
this.renderLabel(button, origin, zoom); 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 // Grab the global alpha so we can adjust it for this render
let previousAlpha = this.ctx.globalAlpha; let previousAlpha = this.ctx.globalAlpha;
this.ctx.globalAlpha = slider.getLayer().getAlpha(); this.ctx.globalAlpha = slider.getLayer().getAlpha();
@ -82,13 +82,13 @@ export default class UIElementRenderer {
this.ctx.globalAlpha = previousAlpha; this.ctx.globalAlpha = previousAlpha;
} }
renderTextInput(textInput: TextInput, origin: Vec2, zoom: number): void { renderTextInput(textInput: TextInput): void {
// Show a cursor sometimes // Show a cursor sometimes
if(textInput.focused && textInput.cursorCounter % 60 > 30){ if(textInput.focused && textInput.cursorCounter % 60 > 30){
textInput.text += "|"; textInput.text += "|";
} }
this.renderLabel(textInput, origin, zoom); this.renderLabel(textInput);
if(textInput.focused){ if(textInput.focused){
if(textInput.cursorCounter % 60 > 30){ if(textInput.cursorCounter % 60 > 30){

View File

@ -12,7 +12,7 @@ import GameLoop from "../Loop/GameLoop";
import SceneManager from "./SceneManager"; import SceneManager from "./SceneManager";
import Receiver from "../Events/Receiver"; import Receiver from "../Events/Receiver";
import Emitter from "../Events/Emitter"; import Emitter from "../Events/Emitter";
import { Renderable, Updateable } from "../DataTypes/Interfaces/Descriptors"; import { Updateable } from "../DataTypes/Interfaces/Descriptors";
import NavigationManager from "../Pathfinding/NavigationManager"; import NavigationManager from "../Pathfinding/NavigationManager";
import AIManager from "../AI/AIManager"; import AIManager from "../AI/AIManager";
import Map from "../DataTypes/Map"; import Map from "../DataTypes/Map";
@ -22,8 +22,9 @@ import CanvasNode from "../Nodes/CanvasNode";
import GameNode from "../Nodes/GameNode"; import GameNode from "../Nodes/GameNode";
import ArrayUtils from "../Utils/ArrayUtils"; import ArrayUtils from "../Utils/ArrayUtils";
import RenderingManager from "../Rendering/RenderingManager"; 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. */ /** The size of the game world. */
protected worldSize: Vec2; protected worldSize: Vec2;
@ -164,6 +165,10 @@ export default class Scene implements Updateable, Renderable {
// Send the visible set, tilemaps, and uiLayers to the renderer // Send the visible set, tilemaps, and uiLayers to the renderer
this.renderingManager.render(visibleSet, this.tilemaps, this.uiLayers); 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 { setRunning(running: boolean): void {

View File

@ -83,6 +83,12 @@ export default abstract class SceneGraph {
abstract getNodesInRegion(boundary: AABB): Array<CanvasNode>; abstract getNodesInRegion(boundary: AABB): Array<CanvasNode>;
getAllNodes(): Array<CanvasNode> {
let arr = new Array<CanvasNode>();
this.nodeMap.forEach(key => arr.push(this.nodeMap.get(key)));
return arr;
}
/** /**
* The specific implementation of getting a node at certain coordinates * The specific implementation of getting a node at certain coordinates
* @param x * @param x

View File

@ -27,7 +27,6 @@ export default class Walk extends OnGround {
} }
if(this.parent.jumpy && (Date.now() - this.time > 500)){ if(this.parent.jumpy && (Date.now() - this.time > 500)){
console.log("Jump");
this.finished(GoombaStates.JUMP); this.finished(GoombaStates.JUMP);
this.parent.velocity.y = -300; this.parent.velocity.y = -300;
} }

View File

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

View File

@ -26,16 +26,45 @@ export default class Jump extends PlayerState {
// Go up plus some extra // Go up plus some extra
pos.y -= (this.owner.collisionShape.halfSize.y + 10); pos.y -= (this.owner.collisionShape.halfSize.y + 10);
pos = this.parent.tilemap.getColRowAt(pos); pos.x -= 16;
let tile = this.parent.tilemap.getTileAtRowCol(pos); 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 coin block, change to empty coin block
if(tile === 17){ if(majority || minorityButAir){
this.parent.tilemap.setTileAtRowCol(pos, 18); 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); this.emitter.fireEvent(MarioEvents.PLAYER_HIT_COIN_BLOCK);
let tileSize = this.parent.tilemap.getTileSize(); 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 // Animate collision
this.parent.coin.tweens.add("coin", { this.parent.coin.tweens.add("coin", {

View File

@ -3,10 +3,40 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>Game</title> <title>Game</title>
<style>
#game-window {
position: relative;
}
#game-canvas {
width: 100%;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
}
#debug-canvas {
width: 100%;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
pointer-events: none;
}
</style>
</head> </head>
<body> <body>
<div style="display: flex; flex-direction: row;"> <div style="display: flex; flex-direction: row;">
<!-- The main window of the game. This contains the rendered game and any debug text -->
<div id="game-window">
<!-- This is the canvas where the actual game is rendered -->
<canvas id="game-canvas"></canvas> <canvas id="game-canvas"></canvas>
<!-- This is the canvas where the debug text and graphics are rendered -->
<canvas id="debug-canvas"></canvas>
</div>
<!-- This contains stats about the game for development purposes, such as the average update time -->
<div> <div>
<canvas id="stats-canvas"></canvas> <canvas id="stats-canvas"></canvas>
<select name="Display" id="chart-option"> <select name="Display" id="chart-option">

View File

@ -2,6 +2,7 @@ import GameLoop from "./Loop/GameLoop";
import {} from "./index"; import {} from "./index";
import MainMenu from "./_DemoClasses/Mario/MainMenu"; import MainMenu from "./_DemoClasses/Mario/MainMenu";
import Level1 from "./_DemoClasses/Mario/Level1"; import Level1 from "./_DemoClasses/Mario/Level1";
import TestScene from "./_DemoClasses/PhysicsTesting/TestScene";
function main(){ function main(){
// Create the game object // Create the game object