abstracted rendering to renderingManager

This commit is contained in:
Joe Weaver 2020-11-24 13:25:36 -05:00
parent 3d275ba7f9
commit b6a0aa569a
21 changed files with 503 additions and 223 deletions

View File

@ -9,6 +9,8 @@ import SceneManager from "../Scene/SceneManager";
import AudioManager from "../Sound/AudioManager"; import AudioManager from "../Sound/AudioManager";
import Stats from "../Debug/Stats"; import Stats from "../Debug/Stats";
import ArrayUtils from "../Utils/ArrayUtils"; import ArrayUtils from "../Utils/ArrayUtils";
import RenderingManager from "../Rendering/RenderingManager";
import CanvasRenderer from "../Rendering/CanvasRenderer";
export default class GameLoop { export default class GameLoop {
gameOptions: GameOptions; gameOptions: GameOptions;
@ -70,6 +72,7 @@ export default class GameLoop {
private resourceManager: ResourceManager; private resourceManager: ResourceManager;
private sceneManager: SceneManager; private sceneManager: SceneManager;
private audioManager: AudioManager; private audioManager: AudioManager;
private renderingManager: RenderingManager;
constructor(options?: Record<string, any>){ constructor(options?: Record<string, any>){
// Typecast the config object to a GameConfig object // Typecast the config object to a GameConfig object
@ -100,7 +103,10 @@ export default class GameLoop {
// 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;
this.HEIGHT = this.gameOptions.viewportSize.y; this.HEIGHT = this.gameOptions.viewportSize.y;
this.ctx = this.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT);
// For now, just hard code a canvas renderer. We can do this with options later
this.renderingManager = new CanvasRenderer();
this.ctx = this.renderingManager.initializeCanvas(this.GAME_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();
@ -114,24 +120,12 @@ export default class GameLoop {
this.inputReceiver.setViewport(this.viewport); this.inputReceiver.setViewport(this.viewport);
this.recorder = new Recorder(); this.recorder = new Recorder();
this.resourceManager = ResourceManager.getInstance(); this.resourceManager = ResourceManager.getInstance();
this.sceneManager = new SceneManager(this.viewport, this); this.sceneManager = new SceneManager(this.viewport, this, this.renderingManager);
this.audioManager = AudioManager.getInstance(); this.audioManager = AudioManager.getInstance();
Stats.initStats(); Stats.initStats();
} }
private initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D {
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext("2d");
// For crisp pixel art
ctx.imageSmoothingEnabled = false;
return ctx;
}
// TODO - This currently also changes the rendering framerate
/** /**
* Changes the maximum allowed physics framerate of the game * Changes the maximum allowed physics framerate of the game
* @param initMax * @param initMax
@ -278,7 +272,7 @@ export default class GameLoop {
*/ */
render(): void { render(): void {
this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT); this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT);
this.sceneManager.render(this.ctx); this.sceneManager.render();
Debug.render(this.ctx); Debug.render(this.ctx);
Stats.render(); Stats.render();
} }

View File

@ -6,7 +6,7 @@ import AABB from "../DataTypes/Shapes/AABB";
/** /**
* 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
*/ */
export default abstract class CanvasNode extends GameNode implements Region, Renderable { export default abstract class CanvasNode extends GameNode implements Region {
private _size: Vec2; private _size: Vec2;
private _scale: Vec2; private _scale: Vec2;
private _boundary: AABB; private _boundary: AABB;
@ -75,6 +75,4 @@ export default abstract class CanvasNode extends GameNode implements Region, Ren
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));
} }
abstract render(ctx: CanvasRenderingContext2D): void;
} }

View File

@ -6,7 +6,7 @@ import Color from "../Utils/Color";
*/ */
export default abstract class Graphic extends CanvasNode { export default abstract class Graphic extends CanvasNode {
protected color: Color; color: Color;
constructor(){ constructor(){
super(); super();

View File

@ -10,14 +10,4 @@ export default class Point extends Graphic {
} }
update(deltaT: number): void {} update(deltaT: number): void {}
render(ctx: CanvasRenderingContext2D): void {
let origin = this.scene.getViewTranslation(this);
let zoom = this.scene.getViewScale();
ctx.fillStyle = this.color.toStringRGBA();
ctx.fillRect((this.position.x - origin.x - this.size.x/2)*zoom, (this.position.y - origin.y - this.size.y/2)*zoom,
this.size.x*zoom, this.size.y*zoom);
}
} }

View File

@ -23,6 +23,10 @@ export default class Rect extends Graphic {
this.borderColor = color; this.borderColor = color;
} }
getBorderColor(): Color {
return this.borderColor;
}
/**Sets the border width of this rectangle /**Sets the border width of this rectangle
* *
* @param width The width of the rectangle in pixels * @param width The width of the rectangle in pixels
@ -31,20 +35,9 @@ export default class Rect extends Graphic {
this.borderWidth = width; this.borderWidth = width;
} }
update(deltaT: number): void {} getBorderWidth(): number {
return this.borderWidth;
render(ctx: CanvasRenderingContext2D): void {
let origin = this.scene.getViewTranslation(this);
let zoom = this.scene.getViewScale();
if(this.color.a !== 0){
ctx.fillStyle = this.color.toStringRGB();
ctx.fillRect((this.position.x - this.size.x/2 - origin.x)*zoom, (this.position.y - this.size.y/2 - origin.y)*zoom, this.size.x*zoom, this.size.y*zoom);
}
ctx.strokeStyle = this.borderColor.toStringRGB();
ctx.lineWidth = this.borderWidth;
ctx.strokeRect((this.position.x - this.size.x/2 - origin.x)*zoom, (this.position.y - this.size.y/2 - origin.y)*zoom, this.size.x*zoom, this.size.y*zoom);
} }
update(deltaT: number): void {}
} }

View File

@ -6,8 +6,8 @@ import Vec2 from "../../DataTypes/Vec2";
* The representation of a sprite - an in-game image * The representation of a sprite - an in-game image
*/ */
export default class Sprite extends CanvasNode { export default class Sprite extends CanvasNode {
private imageId: string; imageId: string;
private imageOffset: Vec2; imageOffset: Vec2;
constructor(imageId: string){ constructor(imageId: string){
super(); super();
@ -26,20 +26,4 @@ export default class Sprite extends CanvasNode {
} }
update(deltaT: number): void {} update(deltaT: number): void {}
render(ctx: CanvasRenderingContext2D): void {
let image = ResourceManager.getInstance().getImage(this.imageId);
let origin = this.scene.getViewTranslation(this);
let zoom = this.scene.getViewScale();
ctx.drawImage(image,
this.imageOffset.x, this.imageOffset.y, this.size.x, this.size.y,
(this.position.x - origin.x - this.size.x*this.scale.x/2)*zoom, (this.position.y - origin.y - this.size.y*this.scale.y/2)*zoom,
this.size.x * this.scale.x*zoom, this.size.y * this.scale.y*zoom);
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*zoom, b.hh*2*zoom);
}
} }

View File

@ -84,6 +84,4 @@ export default abstract class Tilemap extends CanvasNode {
*/ */
// TODO: This shouldn't use tiled data specifically - it should be more general // TODO: This shouldn't use tiled data specifically - it should be more general
protected abstract parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void; protected abstract parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void;
abstract render(ctx: CanvasRenderingContext2D): void;
} }

View File

@ -128,27 +128,4 @@ export default class OrthogonalTilemap extends Tilemap {
} }
update(deltaT: number): void {} update(deltaT: number): void {}
// TODO: Don't render tiles that aren't on screen
render(ctx: CanvasRenderingContext2D) {
let previousAlpha = ctx.globalAlpha;
ctx.globalAlpha = this.getLayer().getAlpha();
let origin = this.scene.getViewTranslation(this);
let zoom = this.scene.getViewScale();
if(this.visible){
for(let i = 0; i < this.data.length; i++){
let tileIndex = this.data[i];
for(let tileset of this.tilesets){
if(tileset.hasTile(tileIndex)){
tileset.renderTile(ctx, tileIndex, i, this.numCols, origin, this.scale, zoom);
}
}
}
}
ctx.globalAlpha = previousAlpha;
}
} }

View File

@ -7,11 +7,11 @@ import Vec2 from "../DataTypes/Vec2";
*/ */
export default abstract class UIElement extends CanvasNode { export default abstract class UIElement extends CanvasNode {
// Style attributes // Style attributes
protected backgroundColor: Color; backgroundColor: Color;
protected borderColor: Color; borderColor: Color;
protected borderRadius: number; borderRadius: number;
protected borderWidth: number; borderWidth: number;
protected padding: Vec2; padding: Vec2;
// EventAttributes // EventAttributes
onClick: Function; onClick: Function;
@ -115,14 +115,14 @@ export default abstract class UIElement extends CanvasNode {
/** /**
* Overridable method for calculating background color - useful for elements that want to be colored on different after certain events * Overridable method for calculating background color - useful for elements that want to be colored on different after certain events
*/ */
protected calculateBackgroundColor(): string { calculateBackgroundColor(): string {
return this.backgroundColor.toStringRGBA(); return this.backgroundColor.toStringRGBA();
} }
/** /**
* Overridable method for calculating border color - useful for elements that want to be colored on different after certain events * Overridable method for calculating border color - useful for elements that want to be colored on different after certain events
*/ */
protected calculateBorderColor(): string { calculateBorderColor(): string {
return this.borderColor.toStringRGBA(); return this.borderColor.toStringRGBA();
} }
} }

View File

@ -12,7 +12,7 @@ export default class Button extends Label {
this.textColor = new Color(255, 255, 255); this.textColor = new Color(255, 255, 255);
} }
protected calculateBackgroundColor(): string { calculateBackgroundColor(): string {
// Change the background color if clicked or hovered // Change the background color if clicked or hovered
if(this.isEntered && !this.isClicked){ if(this.isEntered && !this.isClicked){
return this.backgroundColor.lighten().toStringRGBA(); return this.backgroundColor.lighten().toStringRGBA();

View File

@ -3,8 +3,8 @@ import Color from "../../Utils/Color";
import UIElement from "../UIElement"; import UIElement from "../UIElement";
export default class Label extends UIElement{ export default class Label extends UIElement{
protected textColor: Color; textColor: Color;
protected text: string; text: string;
protected font: string; protected font: string;
protected fontSize: number; protected fontSize: number;
protected hAlign: string; protected hAlign: string;
@ -33,10 +33,14 @@ export default class Label extends UIElement{
this.textColor = color; this.textColor = color;
} }
getFontString(): string {
return this.fontSize + "px " + this.font;
}
/** /**
* Overridable method for calculating text color - useful for elements that want to be colored on different after certain events * Overridable method for calculating text color - useful for elements that want to be colored on different after certain events
*/ */
protected calculateTextColor(): string { calculateTextColor(): string {
return this.textColor.toStringRGBA(); return this.textColor.toStringRGBA();
} }
@ -47,9 +51,8 @@ export default class Label extends UIElement{
/** /**
* Calculate the offset of the text - this is useful for rendering text with different alignments * Calculate the offset of the text - this is useful for rendering text with different alignments
*
*/ */
protected calculateTextOffset(ctx: CanvasRenderingContext2D): Vec2 { calculateTextOffset(ctx: CanvasRenderingContext2D): Vec2 {
let textWidth = this.calculateTextWidth(ctx); let textWidth = this.calculateTextWidth(ctx);
let offset = new Vec2(0, 0); let offset = new Vec2(0, 0);
@ -80,49 +83,22 @@ export default class Label extends UIElement{
this.sizeAssigned = true; this.sizeAssigned = true;
} }
protected autoSize(ctx: CanvasRenderingContext2D){ protected autoSize(ctx: CanvasRenderingContext2D): void {
let width = this.calculateTextWidth(ctx); let width = this.calculateTextWidth(ctx);
let height = this.fontSize; let height = this.fontSize;
this.size.set(width + this.padding.x*2, height + this.padding.y*2); this.size.set(width + this.padding.x*2, height + this.padding.y*2);
this.sizeAssigned = true; this.sizeAssigned = true;
} }
/** On the next render, size this element to it's current text using its current font size */ handleInitialSizing(ctx: CanvasRenderingContext2D): void {
sizeToText(): void {
this.sizeAssigned = false;
}
render(ctx: CanvasRenderingContext2D): void {
// If the size is unassigned (by the user or automatically) assign it
if(!this.sizeAssigned){ if(!this.sizeAssigned){
this.autoSize(ctx); this.autoSize(ctx);
} }
}
// Grab the global alpha so we can adjust it for this render /** On the next render, size this element to it's current text using its current font size */
let previousAlpha = ctx.globalAlpha; sizeToText(): void {
this.sizeAssigned = false;
let origin = this.scene.getViewTranslation(this);
ctx.font = this.fontSize + "px " + this.font;
let offset = this.calculateTextOffset(ctx);
// Stroke and fill a rounded rect and give it text
ctx.globalAlpha = this.backgroundColor.a;
ctx.fillStyle = this.calculateBackgroundColor();
ctx.fillRoundedRect(this.position.x - origin.x - this.size.x/2, this.position.y - origin.y - this.size.y/2,
this.size.x, this.size.y, this.borderRadius);
ctx.strokeStyle = this.calculateBorderColor();
ctx.globalAlpha = this.borderColor.a;
ctx.lineWidth = this.borderWidth;
ctx.strokeRoundedRect(this.position.x - origin.x - this.size.x/2, this.position.y - origin.y - this.size.y/2,
this.size.x, this.size.y, this.borderRadius);
ctx.fillStyle = this.calculateTextColor();
ctx.globalAlpha = this.textColor.a;
ctx.fillText(this.text, this.position.x + offset.x - origin.x - this.size.x/2, this.position.y + offset.y - origin.y - this.size.y/2);
ctx.globalAlpha = previousAlpha;
} }
debug_render = (ctx: CanvasRenderingContext2D): void => { debug_render = (ctx: CanvasRenderingContext2D): void => {

View File

@ -6,8 +6,8 @@ import UIElement from "../UIElement";
export default class Slider extends UIElement { export default class Slider extends UIElement {
/** The value of the slider from [0, 1] */ /** The value of the slider from [0, 1] */
protected value: number; protected value: number;
protected nibColor: Color; public nibColor: Color;
protected sliderColor: Color; public sliderColor: Color;
public onValueChange: (value: number) => void; public onValueChange: (value: number) => void;
public onValueChangeEventId: string; public onValueChangeEventId: string;
@ -24,6 +24,10 @@ export default class Slider extends UIElement {
this.size.set(200, 20); this.size.set(200, 20);
} }
getValue(): number {
return this.value;
}
protected valueChanged(): void { protected valueChanged(): void {
if(this.onValueChange){ if(this.onValueChange){
this.onValueChange(this.value); this.onValueChange(this.value);
@ -43,34 +47,4 @@ export default class Slider extends UIElement {
this.valueChanged(); this.valueChanged();
} }
} }
render(ctx: CanvasRenderingContext2D): void {
// Grab the global alpha so we can adjust it for this render
let previousAlpha = ctx.globalAlpha;
ctx.globalAlpha = this.getLayer().getAlpha();
let origin = this.scene.getViewTranslation(this);
// Calcualate the slider size
let sliderSize = new Vec2(this.size.x, 2);
// Draw the slider
ctx.fillStyle = this.sliderColor.toString();
ctx.fillRoundedRect(this.position.x - origin.x - sliderSize.x/2, this.position.y - origin.y - sliderSize.y/2,
sliderSize.x, sliderSize.y, this.borderRadius);
// Calculate the nib size and position
let nibSize = new Vec2(10, this.size.y);
let x = MathUtils.lerp(this.position.x - this.size.x/2, this.position.x + this.size.x/2, this.value);
let nibPosition = new Vec2(x, this.position.y);
// Draw the nib
ctx.fillStyle = this.nibColor.toString();
ctx.fillRoundedRect(nibPosition.x - origin.x - nibSize.x/2, nibPosition.y - origin.y - nibSize.y/2,
nibSize.x, nibSize.y, this.borderRadius);
// Reset the alpha
ctx.globalAlpha = previousAlpha;
}
} }

View File

@ -58,24 +58,4 @@ export default class TextInput extends Label {
} }
} }
} }
render(ctx: CanvasRenderingContext2D): void {
// Show a cursor sometimes
if(this.focused && this.cursorCounter % 60 > 30){
this.text += "|";
}
super.render(ctx);
if(this.focused){
if(this.cursorCounter % 60 > 30){
this.text = this.text.substring(0, this.text.length - 1);
}
this.cursorCounter += 1;
if(this.cursorCounter >= 60){
this.cursorCounter = 0;
}
}
}
} }

View File

@ -0,0 +1,152 @@
import Map from "../DataTypes/Map";
import CanvasNode from "../Nodes/CanvasNode";
import Graphic from "../Nodes/Graphic";
import Point from "../Nodes/Graphics/Point";
import Rect from "../Nodes/Graphics/Rect";
import Sprite from "../Nodes/Sprites/Sprite";
import Tilemap from "../Nodes/Tilemap";
import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap";
import UIElement from "../Nodes/UIElement";
import UILayer from "../Scene/Layers/UILayer";
import Scene from "../Scene/Scene";
import GraphicRenderer from "./CanvasRendering/GraphicRenderer";
import RenderingManager from "./RenderingManager"
import TilemapRenderer from "./CanvasRendering/TilemapRenderer";
import UIElementRenderer from "./CanvasRendering/UIElementRenderer";
import Label from "../Nodes/UIElements/Label";
import Button from "../Nodes/UIElements/Button";
import Slider from "../Nodes/UIElements/Slider";
import TextInput from "../Nodes/UIElements/TextInput";
export default class CanvasRenderer extends RenderingManager {
protected ctx: CanvasRenderingContext2D;
protected graphicRenderer: GraphicRenderer;
protected tilemapRenderer: TilemapRenderer;
protected uiElementRenderer: UIElementRenderer;
constructor(){
super();;
}
setScene(scene: Scene){
this.scene = scene;
this.graphicRenderer.setScene(scene);
this.tilemapRenderer.setScene(scene);
this.uiElementRenderer.setScene(scene);
}
initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D {
canvas.width = width;
canvas.height = height;
this.ctx = canvas.getContext("2d");
this.graphicRenderer = new GraphicRenderer(this.ctx);
this.tilemapRenderer = new TilemapRenderer(this.ctx);
this.uiElementRenderer = new UIElementRenderer(this.ctx)
// For crisp pixel art
this.ctx.imageSmoothingEnabled = false;
return this.ctx;
}
render(visibleSet: CanvasNode[], tilemaps: Tilemap[], uiLayers: Map<UILayer>): void {
// Sort by depth, then by visible set by y-value
visibleSet.sort((a, b) => {
if(a.getLayer().getDepth() === b.getLayer().getDepth()){
return (a.boundary.bottom) - (b.boundary.bottom);
} else {
return a.getLayer().getDepth() - b.getLayer().getDepth();
}
});
// Render tilemaps
tilemaps.forEach(tilemap => {
this.renderTilemap(tilemap);
});
// Render visible set
visibleSet.forEach(node => {
if(node.visible){
this.renderNode(node);
}
});
// Render the uiLayers
uiLayers.forEach(key => uiLayers.get(key).getItems().forEach(node => this.renderNode(<CanvasNode>node)));
}
protected renderNode(node: CanvasNode): void {
if(node instanceof Sprite){
this.renderSprite(<Sprite>node);
} else if(node instanceof Graphic){
this.renderGraphic(<Graphic>node);
} else if(node instanceof UIElement){
this.renderUIElement(<UIElement>node);
}
}
protected renderSprite(sprite: Sprite): void {
// Get the image from the resource manager
let image = this.resourceManager.getImage(sprite.imageId);
// Calculate the origin of the viewport according to this sprite
let origin = this.scene.getViewTranslation(sprite);
// Get the zoom level of the scene
let zoom = this.scene.getViewScale();
/*
Coordinates in the space of the image:
image crop start -> x, y
image crop size -> w, h
Coordinates in the space of the world
image draw start -> x, y
image draw size -> w, h
*/
this.ctx.drawImage(image,
sprite.imageOffset.x, sprite.imageOffset.y,
sprite.size.x, sprite.size.y,
(sprite.position.x - origin.x - sprite.size.x*sprite.scale.x/2)*zoom, (sprite.position.y - origin.y - sprite.size.y*sprite.scale.y/2)*zoom,
sprite.size.x * sprite.scale.x*zoom, sprite.size.y * sprite.scale.y*zoom);
// Debug mode
if(this.debug){
this.ctx.lineWidth = 4;
this.ctx.strokeStyle = "#00FF00"
let b = sprite.boundary;
this.ctx.strokeRect(b.x - b.hw - origin.x, b.y - b.hh - origin.y, b.hw*2*zoom, b.hh*2*zoom);
}
}
protected renderAnimatedSprite(): void {
throw new Error("Method not implemented.");
}
protected renderGraphic(graphic: Graphic): void {
if(graphic instanceof Point){
this.graphicRenderer.renderPoint(<Point>graphic);
} else if(graphic instanceof Rect){
this.graphicRenderer.renderRect(<Rect>graphic);
}
}
protected renderTilemap(tilemap: Tilemap): void {
if(tilemap instanceof OrthogonalTilemap){
this.tilemapRenderer.renderOrthogonalTilemap(<OrthogonalTilemap>tilemap);
}
}
protected renderUIElement(uiElement: UIElement): void {
if(uiElement instanceof Label){
this.uiElementRenderer.renderLabel(uiElement);
} else if(uiElement instanceof Button){
this.uiElementRenderer.renderButton(uiElement);
} else if(uiElement instanceof Slider){
this.uiElementRenderer.renderSlider(uiElement);
} else if(uiElement instanceof TextInput){
this.uiElementRenderer.renderTextInput(uiElement);
}
}
}

View File

@ -0,0 +1,44 @@
import Point from "../../Nodes/Graphics/Point";
import Rect from "../../Nodes/Graphics/Rect";
import ResourceManager from "../../ResourceManager/ResourceManager";
import Scene from "../../Scene/Scene";
export default class GraphicRenderer {
protected resourceManager: ResourceManager;
protected scene: Scene;
protected ctx: CanvasRenderingContext2D;
constructor(ctx: CanvasRenderingContext2D){
this.resourceManager = ResourceManager.getInstance();
this.ctx = ctx;
}
setScene(scene: Scene): void {
this.scene = scene;
}
renderPoint(point: Point): void {
let origin = this.scene.getViewTranslation(point);
let zoom = this.scene.getViewScale();
this.ctx.fillStyle = point.color.toStringRGBA();
this.ctx.fillRect((point.position.x - origin.x - point.size.x/2)*zoom, (point.position.y - origin.y - point.size.y/2)*zoom,
point.size.x*zoom, point.size.y*zoom);
}
renderRect(rect: Rect): void {
let origin = this.scene.getViewTranslation(rect);
let zoom = this.scene.getViewScale();
// Draw the interior of the rect
if(rect.color.a !== 0){
this.ctx.fillStyle = rect.color.toStringRGB();
this.ctx.fillRect((rect.position.x - rect.size.x/2 - origin.x)*zoom, (rect.position.y - rect.size.y/2 - origin.y)*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.position.x - rect.size.x/2 - origin.x)*zoom, (rect.position.y - rect.size.y/2 - origin.y)*zoom, rect.size.x*zoom, rect.size.y*zoom);
}
}

View File

@ -0,0 +1,79 @@
import ResourceManager from "../../ResourceManager/ResourceManager";
import Scene from "../../Scene/Scene";
import OrthogonalTilemap from "../../Nodes/Tilemaps/OrthogonalTilemap";
import Vec2 from "../../DataTypes/Vec2";
import Tileset from "../../DataTypes/Tilesets/Tileset";
export default class TilemapRenderer {
protected resourceManager: ResourceManager;
protected scene: Scene;
protected ctx: CanvasRenderingContext2D;
constructor(ctx: CanvasRenderingContext2D){
this.resourceManager = ResourceManager.getInstance();
this.ctx = ctx;
}
setScene(scene: Scene): void {
this.scene = scene;
}
renderOrthogonalTilemap(tilemap: OrthogonalTilemap): void {
let previousAlpha = this.ctx.globalAlpha;
this.ctx.globalAlpha = tilemap.getLayer().getAlpha();
let origin = this.scene.getViewTranslation(tilemap);
let size = this.scene.getViewport().getHalfSize();
let zoom = this.scene.getViewScale();
let bottomRight = origin.clone().add(size.scaled(2*zoom));
if(tilemap.visible){
let minColRow = tilemap.getColRowAt(origin);
let maxColRow = tilemap.getColRowAt(bottomRight);
for(let x = minColRow.x; x <= maxColRow.x; x++){
for(let y = minColRow.y; y <= maxColRow.y; y++){
// Get the tile at this position
let tile = tilemap.getTileAtRowCol(new Vec2(x, y));
// Find the tileset that owns this tile index and render
for(let tileset of tilemap.getTilesets()){
if(tileset.hasTile(tile)){
this.renderTile(tileset, tile, x, y, origin, tilemap.scale, zoom);
}
}
}
}
}
this.ctx.globalAlpha = previousAlpha;
}
protected renderTile(tileset: Tileset, tileIndex: number, tilemapRow: number, tilemapCol: number, origin: Vec2, scale: Vec2, zoom: number): void {
let image = this.resourceManager.getImage(tileset.getImageKey());
// Get the true index
let index = tileIndex - tileset.getStartIndex();
// Get the row and col of the tile in image space
let row = Math.floor(index / tileset.getNumCols());
let col = index % tileset.getNumCols();
let width = tileset.getTileSize().x;
let height = tileset.getTileSize().y;
// Calculate the position to start a crop in the tileset image
let left = col * width;
let top = row * height;
// Calculate the position in the world to render the tile
let x = Math.floor(tilemapRow * width * scale.x);
let y = Math.floor(tilemapCol * height * scale.y);
// Render the tile
this.ctx.drawImage(image,
left, top,
width, height,
Math.floor((x - origin.x)*zoom), Math.floor((y - origin.y)*zoom),
Math.ceil(width * scale.x * zoom), Math.ceil(height * scale.y * zoom));
}
}

View File

@ -0,0 +1,110 @@
import Vec2 from "../../DataTypes/Vec2";
import Button from "../../Nodes/UIElements/Button";
import Label from "../../Nodes/UIElements/Label";
import Slider from "../../Nodes/UIElements/Slider";
import TextInput from "../../Nodes/UIElements/TextInput";
import ResourceManager from "../../ResourceManager/ResourceManager";
import Scene from "../../Scene/Scene";
import MathUtils from "../../Utils/MathUtils";
export default class UIElementRenderer {
protected resourceManager: ResourceManager;
protected scene: Scene;
protected ctx: CanvasRenderingContext2D;
constructor(ctx: CanvasRenderingContext2D){
this.resourceManager = ResourceManager.getInstance();
this.ctx = ctx;
}
setScene(scene: Scene): void {
this.scene = scene;
}
renderLabel(label: Label): void {
// If the size is unassigned (by the user or automatically) assign it
label.handleInitialSizing(this.ctx);
// Grab the global alpha so we can adjust it for this render
let previousAlpha = this.ctx.globalAlpha;
// Get the origin of the viewport according to this label
let origin = this.scene.getViewTranslation(label);
// Get the font and text position in label
this.ctx.font = label.getFontString();
let offset = label.calculateTextOffset(this.ctx);
// Stroke and fill a rounded rect and give it text
this.ctx.globalAlpha = label.backgroundColor.a;
this.ctx.fillStyle = label.calculateBackgroundColor();
this.ctx.fillRoundedRect(label.position.x - origin.x - label.size.x/2, label.position.y - origin.y - label.size.y/2,
label.size.x, label.size.y, label.borderRadius);
this.ctx.strokeStyle = label.calculateBorderColor();
this.ctx.globalAlpha = label.borderColor.a;
this.ctx.lineWidth = label.borderWidth;
this.ctx.strokeRoundedRect(label.position.x - origin.x - label.size.x/2, label.position.y - origin.y - label.size.y/2,
label.size.x, label.size.y, label.borderRadius);
this.ctx.fillStyle = label.calculateTextColor();
this.ctx.globalAlpha = label.textColor.a;
this.ctx.fillText(label.text, label.position.x + offset.x - origin.x - label.size.x/2, label.position.y + offset.y - origin.y - label.size.y/2);
this.ctx.globalAlpha = previousAlpha;
}
renderButton(button: Button): void {
this.renderLabel(button);
}
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();
let origin = this.scene.getViewTranslation(slider);
// Calcualate the slider size
let sliderSize = new Vec2(slider.size.x, 2);
// Draw the slider
this.ctx.fillStyle = slider.sliderColor.toString();
this.ctx.fillRoundedRect(slider.position.x - origin.x - sliderSize.x/2, slider.position.y - origin.y - sliderSize.y/2,
sliderSize.x, sliderSize.y, slider.borderRadius);
// Calculate the nib size and position
let nibSize = new Vec2(10, slider.size.y);
let x = MathUtils.lerp(slider.position.x - slider.size.x/2, slider.position.x + slider.size.x/2, slider.getValue());
let nibPosition = new Vec2(x, slider.position.y);
// Draw the nib
this.ctx.fillStyle = slider.nibColor.toString();
this.ctx.fillRoundedRect(nibPosition.x - origin.x - nibSize.x/2, nibPosition.y - origin.y - nibSize.y/2,
nibSize.x, nibSize.y, slider.borderRadius);
// Reset the alpha
this.ctx.globalAlpha = previousAlpha;
}
renderTextInput(textInput: TextInput): void {
// Show a cursor sometimes
if(textInput.focused && textInput.cursorCounter % 60 > 30){
textInput.text += "|";
}
this.renderLabel(textInput);
if(textInput.focused){
if(textInput.cursorCounter % 60 > 30){
textInput.text = textInput.text.substring(0, textInput.text.length - 1);
}
textInput.cursorCounter += 1;
if(textInput.cursorCounter >= 60){
textInput.cursorCounter = 0;
}
}
}
}

View File

@ -0,0 +1,39 @@
import Map from "../DataTypes/Map";
import CanvasNode from "../Nodes/CanvasNode";
import Graphic from "../Nodes/Graphic";
import Sprite from "../Nodes/Sprites/Sprite";
import Tilemap from "../Nodes/Tilemap";
import UIElement from "../Nodes/UIElement";
import ResourceManager from "../ResourceManager/ResourceManager";
import UILayer from "../Scene/Layers/UILayer";
import Scene from "../Scene/Scene";
export default abstract class RenderingManager {
// Give the renderer access to the resource manager
protected resourceManager: ResourceManager;
protected scene: Scene;
debug: boolean;
constructor(){
this.resourceManager = ResourceManager.getInstance();
this.debug = false;
}
setScene(scene: Scene): void {
this.scene = scene;
}
abstract initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): any;
abstract render(visibleSet: Array<CanvasNode>, tilemaps: Array<Tilemap>, uiLayers: Map<UILayer>): void;
protected abstract renderSprite(sprite: Sprite): void;
protected abstract renderAnimatedSprite(): void;
protected abstract renderGraphic(graphic: Graphic): void;
protected abstract renderTilemap(tilemap: Tilemap): void;
protected abstract renderUIElement(uiElement: UIElement): void;
}

View File

@ -21,6 +21,7 @@ import UILayer from "./Layers/UILayer";
import CanvasNode from "../Nodes/CanvasNode"; 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";
export default class Scene implements Updateable, Renderable { export default class Scene implements Updateable, Renderable {
/** The size of the game world. */ /** The size of the game world. */
@ -68,6 +69,9 @@ export default class Scene implements Updateable, Renderable {
/** The AI manager of the Scene */ /** The AI manager of the Scene */
protected aiManager: AIManager; protected aiManager: AIManager;
/** The renderingManager of the scene */
protected renderingManager: RenderingManager;
/** An interface that allows the adding of different nodes to the scene */ /** An interface that allows the adding of different nodes to the scene */
public add: FactoryManager; public add: FactoryManager;
@ -77,7 +81,7 @@ export default class Scene implements Updateable, Renderable {
/** The configuration options for this scene */ /** The configuration options for this scene */
public sceneOptions: SceneOptions; public sceneOptions: SceneOptions;
constructor(viewport: Viewport, sceneManager: SceneManager, game: GameLoop, options: Record<string, any>){ constructor(viewport: Viewport, sceneManager: SceneManager, renderingManager: RenderingManager, game: GameLoop, options: Record<string, any>){
this.sceneOptions = SceneOptions.parse(options); this.sceneOptions = SceneOptions.parse(options);
this.worldSize = new Vec2(500, 500); this.worldSize = new Vec2(500, 500);
@ -99,6 +103,7 @@ export default class Scene implements Updateable, Renderable {
this.physicsManager = new BasicPhysicsManager(this.sceneOptions.physics); this.physicsManager = new BasicPhysicsManager(this.sceneOptions.physics);
this.navManager = new NavigationManager(); this.navManager = new NavigationManager();
this.aiManager = new AIManager(); this.aiManager = new AIManager();
this.renderingManager = renderingManager;
this.add = new FactoryManager(this, this.tilemaps); this.add = new FactoryManager(this, this.tilemaps);
@ -143,9 +148,8 @@ export default class Scene implements Updateable, Renderable {
this.viewport.update(deltaT); this.viewport.update(deltaT);
} }
render(ctx: CanvasRenderingContext2D): void { render(): void {
// For webGL, pass a visible set to the renderer // Get the visible set of nodes
// We need to keep track of the order of things.
let visibleSet = this.sceneGraph.getVisibleSet(); let visibleSet = this.sceneGraph.getVisibleSet();
// Add parallax layer items to the visible set (we're rendering them all for now) // Add parallax layer items to the visible set (we're rendering them all for now)
@ -158,31 +162,8 @@ export default class Scene implements Updateable, Renderable {
} }
}); });
// Sort by depth, then by visible set by y-value // Send the visible set, tilemaps, and uiLayers to the renderer
visibleSet.sort((a, b) => { this.renderingManager.render(visibleSet, this.tilemaps, this.uiLayers);
if(a.getLayer().getDepth() === b.getLayer().getDepth()){
return (a.boundary.bottom) - (b.boundary.bottom);
} else {
return a.getLayer().getDepth() - b.getLayer().getDepth();
}
});
// Render scene graph for demo
this.sceneGraph.render(ctx);
// Render tilemaps
this.tilemaps.forEach(tilemap => {
tilemap.render(ctx);
});
// Render visible set
visibleSet.forEach(node => node.visible ? node.render(ctx) : "");
// Debug render the physicsManager
this.physicsManager.debug_render(ctx);
// Render the uiLayers
this.uiLayers.forEach(key => this.uiLayers.get(key).getItems().forEach(node => (<CanvasNode>node).render(ctx)));
} }
setRunning(running: boolean): void { setRunning(running: boolean): void {

View File

@ -2,19 +2,21 @@ import Scene from "./Scene";
import ResourceManager from "../ResourceManager/ResourceManager"; import ResourceManager from "../ResourceManager/ResourceManager";
import Viewport from "../SceneGraph/Viewport"; import Viewport from "../SceneGraph/Viewport";
import GameLoop from "../Loop/GameLoop"; import GameLoop from "../Loop/GameLoop";
import RenderingManager from "../Rendering/RenderingManager";
export default class SceneManager { export default class SceneManager {
protected currentScene: Scene;
protected viewport: Viewport;
protected resourceManager: ResourceManager;
protected game: GameLoop;
protected idCounter: number;
protected renderingManager: RenderingManager;
private currentScene: Scene; constructor(viewport: Viewport, game: GameLoop, renderingManager: RenderingManager){
private viewport: Viewport;
private resourceManager: ResourceManager;
private game: GameLoop;
private idCounter: number;
constructor(viewport: Viewport, game: GameLoop){
this.resourceManager = ResourceManager.getInstance(); this.resourceManager = ResourceManager.getInstance();
this.viewport = viewport; this.viewport = viewport;
this.game = game; this.game = game;
this.renderingManager = renderingManager;
this.idCounter = 0; this.idCounter = 0;
} }
@ -23,7 +25,7 @@ export default class SceneManager {
* @param constr The constructor of the scene to add * @param constr The constructor of the scene to add
*/ */
public addScene<T extends Scene>(constr: new (...args: any) => T, options: Record<string, any>): void { public addScene<T extends Scene>(constr: new (...args: any) => T, options: Record<string, any>): void {
let scene = new constr(this.viewport, this, this.game, options); let scene = new constr(this.viewport, this, this.renderingManager, this.game, options);
this.currentScene = scene; this.currentScene = scene;
// Enqueue all scene asset loads // Enqueue all scene asset loads
@ -36,6 +38,8 @@ export default class SceneManager {
scene.startScene(); scene.startScene();
scene.setRunning(true); scene.setRunning(true);
}); });
this.renderingManager.setScene(scene);
} }
/** /**
@ -57,8 +61,8 @@ export default class SceneManager {
return this.idCounter++; return this.idCounter++;
} }
public render(ctx: CanvasRenderingContext2D){ public render(){
this.currentScene.render(ctx); this.currentScene.render();
} }
public update(deltaT: number){ public update(deltaT: number){

View File

@ -53,6 +53,13 @@ export default class Color {
return new Color(255, 100, 0, 1); return new Color(255, 100, 0, 1);
} }
set(r: number, g: number, b: number, a: number = 1): void {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
/** /**
* Returns a new color slightly lighter than the current color * Returns a new color slightly lighter than the current color
*/ */