abstracted rendering to renderingManager
This commit is contained in:
parent
3d275ba7f9
commit
b6a0aa569a
|
@ -9,6 +9,8 @@ import SceneManager from "../Scene/SceneManager";
|
|||
import AudioManager from "../Sound/AudioManager";
|
||||
import Stats from "../Debug/Stats";
|
||||
import ArrayUtils from "../Utils/ArrayUtils";
|
||||
import RenderingManager from "../Rendering/RenderingManager";
|
||||
import CanvasRenderer from "../Rendering/CanvasRenderer";
|
||||
|
||||
export default class GameLoop {
|
||||
gameOptions: GameOptions;
|
||||
|
@ -70,6 +72,7 @@ export default class GameLoop {
|
|||
private resourceManager: ResourceManager;
|
||||
private sceneManager: SceneManager;
|
||||
private audioManager: AudioManager;
|
||||
private renderingManager: RenderingManager;
|
||||
|
||||
constructor(options?: Record<string, any>){
|
||||
// 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
|
||||
this.WIDTH = this.gameOptions.viewportSize.x;
|
||||
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
|
||||
this.viewport = new Viewport();
|
||||
|
@ -114,24 +120,12 @@ export default class GameLoop {
|
|||
this.inputReceiver.setViewport(this.viewport);
|
||||
this.recorder = new Recorder();
|
||||
this.resourceManager = ResourceManager.getInstance();
|
||||
this.sceneManager = new SceneManager(this.viewport, this);
|
||||
this.sceneManager = new SceneManager(this.viewport, this, this.renderingManager);
|
||||
this.audioManager = AudioManager.getInstance();
|
||||
|
||||
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
|
||||
* @param initMax
|
||||
|
@ -278,7 +272,7 @@ export default class GameLoop {
|
|||
*/
|
||||
render(): void {
|
||||
this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT);
|
||||
this.sceneManager.render(this.ctx);
|
||||
this.sceneManager.render();
|
||||
Debug.render(this.ctx);
|
||||
Stats.render();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
export default abstract class CanvasNode extends GameNode implements Region, Renderable {
|
||||
export default abstract class CanvasNode extends GameNode implements Region {
|
||||
private _size: Vec2;
|
||||
private _scale: Vec2;
|
||||
private _boundary: AABB;
|
||||
|
@ -75,6 +75,4 @@ export default abstract class CanvasNode extends GameNode implements Region, Ren
|
|||
contains(x: number, y: number): boolean {
|
||||
return this._boundary.containsPoint(new Vec2(x, y));
|
||||
}
|
||||
|
||||
abstract render(ctx: CanvasRenderingContext2D): void;
|
||||
}
|
|
@ -6,7 +6,7 @@ import Color from "../Utils/Color";
|
|||
*/
|
||||
export default abstract class Graphic extends CanvasNode {
|
||||
|
||||
protected color: Color;
|
||||
color: Color;
|
||||
|
||||
constructor(){
|
||||
super();
|
||||
|
|
|
@ -10,14 +10,4 @@ export default class Point extends Graphic {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -23,6 +23,10 @@ export default class Rect extends Graphic {
|
|||
this.borderColor = color;
|
||||
}
|
||||
|
||||
getBorderColor(): Color {
|
||||
return this.borderColor;
|
||||
}
|
||||
|
||||
/**Sets the border width of this rectangle
|
||||
*
|
||||
* @param width The width of the rectangle in pixels
|
||||
|
@ -31,20 +35,9 @@ export default class Rect extends Graphic {
|
|||
this.borderWidth = width;
|
||||
}
|
||||
|
||||
getBorderWidth(): number {
|
||||
return this.borderWidth;
|
||||
}
|
||||
|
||||
update(deltaT: number): void {}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -6,8 +6,8 @@ import Vec2 from "../../DataTypes/Vec2";
|
|||
* The representation of a sprite - an in-game image
|
||||
*/
|
||||
export default class Sprite extends CanvasNode {
|
||||
private imageId: string;
|
||||
private imageOffset: Vec2;
|
||||
imageId: string;
|
||||
imageOffset: Vec2;
|
||||
|
||||
constructor(imageId: string){
|
||||
super();
|
||||
|
@ -26,20 +26,4 @@ export default class Sprite extends CanvasNode {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -84,6 +84,4 @@ export default abstract class Tilemap extends CanvasNode {
|
|||
*/
|
||||
// TODO: This shouldn't use tiled data specifically - it should be more general
|
||||
protected abstract parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void;
|
||||
|
||||
abstract render(ctx: CanvasRenderingContext2D): void;
|
||||
}
|
|
@ -128,27 +128,4 @@ export default class OrthogonalTilemap extends Tilemap {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -7,11 +7,11 @@ import Vec2 from "../DataTypes/Vec2";
|
|||
*/
|
||||
export default abstract class UIElement extends CanvasNode {
|
||||
// Style attributes
|
||||
protected backgroundColor: Color;
|
||||
protected borderColor: Color;
|
||||
protected borderRadius: number;
|
||||
protected borderWidth: number;
|
||||
protected padding: Vec2;
|
||||
backgroundColor: Color;
|
||||
borderColor: Color;
|
||||
borderRadius: number;
|
||||
borderWidth: number;
|
||||
padding: Vec2;
|
||||
|
||||
// EventAttributes
|
||||
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
|
||||
*/
|
||||
protected calculateBackgroundColor(): string {
|
||||
calculateBackgroundColor(): string {
|
||||
return this.backgroundColor.toStringRGBA();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ export default class Button extends Label {
|
|||
this.textColor = new Color(255, 255, 255);
|
||||
}
|
||||
|
||||
protected calculateBackgroundColor(): string {
|
||||
calculateBackgroundColor(): string {
|
||||
// Change the background color if clicked or hovered
|
||||
if(this.isEntered && !this.isClicked){
|
||||
return this.backgroundColor.lighten().toStringRGBA();
|
||||
|
|
|
@ -3,8 +3,8 @@ import Color from "../../Utils/Color";
|
|||
import UIElement from "../UIElement";
|
||||
|
||||
export default class Label extends UIElement{
|
||||
protected textColor: Color;
|
||||
protected text: string;
|
||||
textColor: Color;
|
||||
text: string;
|
||||
protected font: string;
|
||||
protected fontSize: number;
|
||||
protected hAlign: string;
|
||||
|
@ -33,10 +33,14 @@ export default class Label extends UIElement{
|
|||
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
|
||||
*/
|
||||
protected calculateTextColor(): string {
|
||||
calculateTextColor(): string {
|
||||
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
|
||||
*
|
||||
*/
|
||||
protected calculateTextOffset(ctx: CanvasRenderingContext2D): Vec2 {
|
||||
calculateTextOffset(ctx: CanvasRenderingContext2D): Vec2 {
|
||||
let textWidth = this.calculateTextWidth(ctx);
|
||||
|
||||
let offset = new Vec2(0, 0);
|
||||
|
@ -80,49 +83,22 @@ export default class Label extends UIElement{
|
|||
this.sizeAssigned = true;
|
||||
}
|
||||
|
||||
protected autoSize(ctx: CanvasRenderingContext2D){
|
||||
protected autoSize(ctx: CanvasRenderingContext2D): void {
|
||||
let width = this.calculateTextWidth(ctx);
|
||||
let height = this.fontSize;
|
||||
this.size.set(width + this.padding.x*2, height + this.padding.y*2);
|
||||
this.sizeAssigned = true;
|
||||
}
|
||||
|
||||
/** On the next render, size this element to it's current text using its current font size */
|
||||
sizeToText(): void {
|
||||
this.sizeAssigned = false;
|
||||
}
|
||||
|
||||
render(ctx: CanvasRenderingContext2D): void {
|
||||
// If the size is unassigned (by the user or automatically) assign it
|
||||
handleInitialSizing(ctx: CanvasRenderingContext2D): void {
|
||||
if(!this.sizeAssigned){
|
||||
this.autoSize(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the global alpha so we can adjust it for this render
|
||||
let previousAlpha = ctx.globalAlpha;
|
||||
|
||||
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;
|
||||
/** On the next render, size this element to it's current text using its current font size */
|
||||
sizeToText(): void {
|
||||
this.sizeAssigned = false;
|
||||
}
|
||||
|
||||
debug_render = (ctx: CanvasRenderingContext2D): void => {
|
||||
|
|
|
@ -6,8 +6,8 @@ import UIElement from "../UIElement";
|
|||
export default class Slider extends UIElement {
|
||||
/** The value of the slider from [0, 1] */
|
||||
protected value: number;
|
||||
protected nibColor: Color;
|
||||
protected sliderColor: Color;
|
||||
public nibColor: Color;
|
||||
public sliderColor: Color;
|
||||
public onValueChange: (value: number) => void;
|
||||
public onValueChangeEventId: string;
|
||||
|
||||
|
@ -24,6 +24,10 @@ export default class Slider extends UIElement {
|
|||
this.size.set(200, 20);
|
||||
}
|
||||
|
||||
getValue(): number {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
protected valueChanged(): void {
|
||||
if(this.onValueChange){
|
||||
this.onValueChange(this.value);
|
||||
|
@ -43,34 +47,4 @@ export default class Slider extends UIElement {
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
152
src/Rendering/CanvasRenderer.ts
Normal file
152
src/Rendering/CanvasRenderer.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
44
src/Rendering/CanvasRendering/GraphicRenderer.ts
Normal file
44
src/Rendering/CanvasRendering/GraphicRenderer.ts
Normal 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);
|
||||
}
|
||||
}
|
79
src/Rendering/CanvasRendering/TilemapRenderer.ts
Normal file
79
src/Rendering/CanvasRendering/TilemapRenderer.ts
Normal 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));
|
||||
}
|
||||
}
|
110
src/Rendering/CanvasRendering/UIElementRenderer.ts
Normal file
110
src/Rendering/CanvasRendering/UIElementRenderer.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
39
src/Rendering/RenderingManager.ts
Normal file
39
src/Rendering/RenderingManager.ts
Normal 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;
|
||||
}
|
|
@ -21,6 +21,7 @@ import UILayer from "./Layers/UILayer";
|
|||
import CanvasNode from "../Nodes/CanvasNode";
|
||||
import GameNode from "../Nodes/GameNode";
|
||||
import ArrayUtils from "../Utils/ArrayUtils";
|
||||
import RenderingManager from "../Rendering/RenderingManager";
|
||||
|
||||
export default class Scene implements Updateable, Renderable {
|
||||
/** The size of the game world. */
|
||||
|
@ -68,6 +69,9 @@ export default class Scene implements Updateable, Renderable {
|
|||
/** The AI manager of the Scene */
|
||||
protected aiManager: AIManager;
|
||||
|
||||
/** The renderingManager of the scene */
|
||||
protected renderingManager: RenderingManager;
|
||||
|
||||
/** An interface that allows the adding of different nodes to the scene */
|
||||
public add: FactoryManager;
|
||||
|
||||
|
@ -77,7 +81,7 @@ export default class Scene implements Updateable, Renderable {
|
|||
/** The configuration options for this scene */
|
||||
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.worldSize = new Vec2(500, 500);
|
||||
|
@ -99,6 +103,7 @@ export default class Scene implements Updateable, Renderable {
|
|||
this.physicsManager = new BasicPhysicsManager(this.sceneOptions.physics);
|
||||
this.navManager = new NavigationManager();
|
||||
this.aiManager = new AIManager();
|
||||
this.renderingManager = renderingManager;
|
||||
|
||||
this.add = new FactoryManager(this, this.tilemaps);
|
||||
|
||||
|
@ -143,9 +148,8 @@ export default class Scene implements Updateable, Renderable {
|
|||
this.viewport.update(deltaT);
|
||||
}
|
||||
|
||||
render(ctx: CanvasRenderingContext2D): void {
|
||||
// For webGL, pass a visible set to the renderer
|
||||
// We need to keep track of the order of things.
|
||||
render(): void {
|
||||
// Get the visible set of nodes
|
||||
let visibleSet = this.sceneGraph.getVisibleSet();
|
||||
|
||||
// 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
|
||||
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 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)));
|
||||
// Send the visible set, tilemaps, and uiLayers to the renderer
|
||||
this.renderingManager.render(visibleSet, this.tilemaps, this.uiLayers);
|
||||
}
|
||||
|
||||
setRunning(running: boolean): void {
|
||||
|
|
|
@ -2,19 +2,21 @@ import Scene from "./Scene";
|
|||
import ResourceManager from "../ResourceManager/ResourceManager";
|
||||
import Viewport from "../SceneGraph/Viewport";
|
||||
import GameLoop from "../Loop/GameLoop";
|
||||
import RenderingManager from "../Rendering/RenderingManager";
|
||||
|
||||
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;
|
||||
private viewport: Viewport;
|
||||
private resourceManager: ResourceManager;
|
||||
private game: GameLoop;
|
||||
private idCounter: number;
|
||||
|
||||
constructor(viewport: Viewport, game: GameLoop){
|
||||
constructor(viewport: Viewport, game: GameLoop, renderingManager: RenderingManager){
|
||||
this.resourceManager = ResourceManager.getInstance();
|
||||
this.viewport = viewport;
|
||||
this.game = game;
|
||||
this.renderingManager = renderingManager;
|
||||
this.idCounter = 0;
|
||||
}
|
||||
|
||||
|
@ -23,7 +25,7 @@ export default class SceneManager {
|
|||
* @param constr The constructor of the scene to add
|
||||
*/
|
||||
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;
|
||||
|
||||
// Enqueue all scene asset loads
|
||||
|
@ -36,6 +38,8 @@ export default class SceneManager {
|
|||
scene.startScene();
|
||||
scene.setRunning(true);
|
||||
});
|
||||
|
||||
this.renderingManager.setScene(scene);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,8 +61,8 @@ export default class SceneManager {
|
|||
return this.idCounter++;
|
||||
}
|
||||
|
||||
public render(ctx: CanvasRenderingContext2D){
|
||||
this.currentScene.render(ctx);
|
||||
public render(){
|
||||
this.currentScene.render();
|
||||
}
|
||||
|
||||
public update(deltaT: number){
|
||||
|
|
|
@ -53,6 +53,13 @@ export default class Color {
|
|||
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
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue
Block a user