mirror of
https://github.com/Aviortheking/games.git
synced 2025-07-04 07:09:18 +00:00
feat: move to Astro and mostly reworked the Pokémon Shuffle game
Signed-off-by: Avior <github@avior.me>
This commit is contained in:
61
src/GameEngine/Renderer/CircleRenderer.ts
Normal file
61
src/GameEngine/Renderer/CircleRenderer.ts
Normal file
@ -0,0 +1,61 @@
|
||||
/* eslint-disable max-depth */
|
||||
import Renderer from '.'
|
||||
import type GameEngine from '..'
|
||||
|
||||
interface Params {
|
||||
material?: string
|
||||
stroke?: string | {
|
||||
color: string
|
||||
width: number
|
||||
dotted?: number
|
||||
position?: 'inside' | 'center' | 'outside'
|
||||
}
|
||||
}
|
||||
|
||||
export default class CircleRenderer extends Renderer<Params> {
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
|
||||
await super.render(ge, ctx)
|
||||
const item = this.preRender(ctx, ge)
|
||||
|
||||
ctx.beginPath()
|
||||
const realX = item[0] + item[2] / 2
|
||||
const realY = item[1] + item[3] / 2
|
||||
ctx.arc(realX, realY, item[2] / 2, 0, 180 * Math.PI)
|
||||
|
||||
if (this.props.material) {
|
||||
ctx.fillStyle = this.props.material
|
||||
ctx.fill()
|
||||
}
|
||||
if (this.props.stroke) {
|
||||
if (typeof this.props.stroke === 'string') {
|
||||
ctx.strokeStyle = this.props.stroke
|
||||
ctx.stroke()
|
||||
} else {
|
||||
if (this.props.stroke.dotted) {
|
||||
ctx.setLineDash([this.props.stroke.dotted / 2, this.props.stroke.dotted])
|
||||
}
|
||||
|
||||
ctx.lineWidth = this.props.stroke.width * (ge.currentScene?.scale ?? 1)
|
||||
ctx.stroke()
|
||||
|
||||
if (this.props.stroke.dotted) {
|
||||
ctx.setLineDash([])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.debug) {
|
||||
if (typeof this.props.stroke === 'string') {
|
||||
ctx.strokeStyle = this.props.stroke
|
||||
} else {
|
||||
ctx.strokeStyle = 'red'
|
||||
ctx.lineWidth = 1 * (ge.currentScene?.scale ?? 1)
|
||||
}
|
||||
ctx.strokeRect(...item)
|
||||
}
|
||||
|
||||
this.postRender(ctx, ge)
|
||||
}
|
||||
}
|
80
src/GameEngine/Renderer/ImageRenderer.ts
Normal file
80
src/GameEngine/Renderer/ImageRenderer.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import Renderer from '.'
|
||||
import GameEngine from '..'
|
||||
import Asset, { AssetStatus } from '../Asset'
|
||||
|
||||
interface Params {
|
||||
asset?: Asset
|
||||
stream?: boolean
|
||||
imageRotation?: number
|
||||
debug?: boolean
|
||||
/**
|
||||
* padding for each sides
|
||||
*/
|
||||
padding?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Add origin support
|
||||
*/
|
||||
export default class ImageRenderer extends Renderer<Params> {
|
||||
|
||||
protected defaultProps: Partial<Params> = {
|
||||
stream: true
|
||||
}
|
||||
|
||||
private padding = 0
|
||||
|
||||
public onUpdate(): void {
|
||||
const ge = GameEngine.getGameEngine()
|
||||
this.padding = (this.props.padding ?? 0) * ge.caseSize.x * (ge.currentScene?.scale ?? 1)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
|
||||
await super.render(ge, ctx)
|
||||
|
||||
if (!this.props.asset) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.props.asset.status !== AssetStatus.LOADED) {
|
||||
if (this.props.stream) {
|
||||
// load asset but do not stop threads
|
||||
this.props.asset.load()
|
||||
return
|
||||
} else {
|
||||
await this.props.asset.load()
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.asset.status === AssetStatus.LOADING && this.props.stream) {
|
||||
return
|
||||
}
|
||||
|
||||
const padding = this.padding
|
||||
const size = this.props.asset.size()
|
||||
const final = this.preRender(ctx, ge, this.props.imageRotation)
|
||||
const x = final[0] + (padding ?? 0)
|
||||
const y = final[1] + (padding ?? 0)
|
||||
const width = Math.max(final[2] - (padding ?? 0) * 2, 0)
|
||||
const height = Math.max(final[3] - (padding ?? 0) * 2, 0)
|
||||
|
||||
if (this.debug || this.component.debug) {
|
||||
ctx.fillStyle = 'red'
|
||||
ctx.fillRect(...final)
|
||||
}
|
||||
ctx.drawImage(
|
||||
this.props.asset.get(),
|
||||
0,
|
||||
0,
|
||||
size.x,
|
||||
size.y,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
)
|
||||
|
||||
this.postRender(ctx, ge)
|
||||
}
|
||||
}
|
16
src/GameEngine/Renderer/MultiRenderer.ts
Normal file
16
src/GameEngine/Renderer/MultiRenderer.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import Renderer from '.'
|
||||
import type GameEngine from '..'
|
||||
|
||||
interface Params {
|
||||
renderers: Array<Renderer>
|
||||
}
|
||||
|
||||
export default class MultiRenderer extends Renderer<Params> {
|
||||
|
||||
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
|
||||
await super.render(ge, ctx)
|
||||
for await (const renderer of this.props.renderers) {
|
||||
await renderer.render(ge, ctx)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,57 +1,292 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import GameEngine from 'GameEngine'
|
||||
import Asset from 'GameEngine/Asset'
|
||||
import Component2D from 'GameEngine/Component2D'
|
||||
/* eslint-disable complexity */
|
||||
/* eslint-disable max-len */
|
||||
/* eslint-disable max-depth */
|
||||
import { objectKeys, objectLoop } from '@dzeio/object-util'
|
||||
import Renderer from '.'
|
||||
import GameEngine from '..'
|
||||
|
||||
interface Params {
|
||||
material?: string | Asset
|
||||
stroke?: string | {color: string, width: number}
|
||||
export interface StrokeOptions {
|
||||
color: string
|
||||
width: number
|
||||
dotted?: number
|
||||
position?: 'inside' | 'center' | 'outside'
|
||||
offset?: number
|
||||
}
|
||||
|
||||
export default class RectRenderer extends Renderer implements Params {
|
||||
// type Border = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'
|
||||
// type Stroke = 'top' | 'bottom' | 'left' | 'right'
|
||||
|
||||
public material?: string | Asset
|
||||
public stroke?: string | {color: string, width: number}
|
||||
interface Params {
|
||||
material?: string | null
|
||||
alpha?: number
|
||||
stroke?: string | StrokeOptions | {
|
||||
top?: StrokeOptions
|
||||
right?: StrokeOptions
|
||||
bottom?: StrokeOptions
|
||||
left?: StrokeOptions
|
||||
}
|
||||
borderRadius?: number | {
|
||||
topLeft?: number
|
||||
topRight?: number
|
||||
bottomLeft?: number
|
||||
bottomRight?: number
|
||||
}
|
||||
debug?: boolean
|
||||
}
|
||||
|
||||
public constructor(component: Component2D, params?: Params) {
|
||||
super(component)
|
||||
objectLoop(params ?? {}, (v, k) => {this[k as 'material'] = v})
|
||||
export default class RectRenderer extends Renderer<Params> {
|
||||
|
||||
private borderRadius: {
|
||||
topLeft: number
|
||||
topRight: number
|
||||
bottomLeft: number
|
||||
bottomRight: number
|
||||
} = {
|
||||
topLeft: 0,
|
||||
topRight: 0,
|
||||
bottomLeft: 0,
|
||||
bottomRight: 0
|
||||
}
|
||||
|
||||
private stroke?: {
|
||||
top?: StrokeOptions
|
||||
right?: StrokeOptions
|
||||
bottom?: StrokeOptions
|
||||
left?: StrokeOptions
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
|
||||
const position = this.getPosition()
|
||||
const item: [number, number, number, number] = [
|
||||
// source x
|
||||
// 0 - 1.5 - -1.5
|
||||
position.x * (ge.caseSize.x),
|
||||
// source y
|
||||
position.y * (ge.caseSize.y),
|
||||
// source end X
|
||||
this.component.scale.x * (ge.caseSize.x),
|
||||
// source end Y
|
||||
this.component.scale.y * (ge.caseSize.y)
|
||||
]
|
||||
await super.render(ge, ctx)
|
||||
const item = this.preRender(ctx, ge)
|
||||
const scaling = ge.currentScene?.scale ?? 1
|
||||
|
||||
if (this.material instanceof Asset) {
|
||||
ctx.drawImage(
|
||||
await this.material.get(),
|
||||
...item
|
||||
)
|
||||
return
|
||||
ctx.globalAlpha = this.props.alpha ?? 1
|
||||
|
||||
if (this.props.material) {
|
||||
ctx.fillStyle = this.props.material
|
||||
// ctx.fillRect(...item)
|
||||
this.roundRect(ctx, ...item, true, false)
|
||||
}
|
||||
if (this.material) {
|
||||
ctx.fillStyle = this.material
|
||||
ctx.fillRect(...item)
|
||||
if (this.stroke && objectKeys(this.stroke).length > 0) {
|
||||
|
||||
objectLoop(this.stroke, (options, strokePos) => {
|
||||
if (!options) {
|
||||
return
|
||||
}
|
||||
let offset = options.width * scaling / 2
|
||||
|
||||
if (options.position === 'inside') {
|
||||
offset = -offset
|
||||
} else if (options.position === 'center') {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
ctx.lineWidth = options.width * scaling
|
||||
const lineByTwo = ctx.lineWidth / 3
|
||||
|
||||
ctx.strokeStyle = options.color
|
||||
|
||||
const xStart = item[0] - offset
|
||||
const xEnd = item[0] + item[2] + offset
|
||||
const yStart = item[1] - offset
|
||||
const yEnd = item[1] + item[3] + offset
|
||||
|
||||
if (options.dotted) {
|
||||
ctx.setLineDash([options.dotted / 2 * scaling, options.dotted * scaling])
|
||||
}
|
||||
|
||||
ctx.beginPath()
|
||||
switch (strokePos) {
|
||||
case 'top':
|
||||
ctx.moveTo(xStart - lineByTwo, yStart)
|
||||
ctx.lineTo(xEnd + lineByTwo, yStart)
|
||||
break
|
||||
case 'bottom':
|
||||
ctx.moveTo(xStart - lineByTwo, yEnd)
|
||||
ctx.lineTo(xEnd + lineByTwo, yEnd)
|
||||
break
|
||||
case 'left':
|
||||
ctx.moveTo(xStart, yStart - lineByTwo)
|
||||
ctx.lineTo(xStart, yEnd + lineByTwo)
|
||||
break
|
||||
case 'right':
|
||||
ctx.moveTo(xEnd, yStart - lineByTwo)
|
||||
ctx.lineTo(xEnd, yEnd + lineByTwo)
|
||||
break
|
||||
}
|
||||
ctx.stroke()
|
||||
|
||||
ctx.setLineDash([])
|
||||
})
|
||||
|
||||
}
|
||||
if (this.stroke) {
|
||||
|
||||
if (this.debug) {
|
||||
if (typeof this.stroke === 'string') {
|
||||
ctx.strokeStyle = this.stroke
|
||||
} else {
|
||||
ctx.strokeStyle = this.stroke.color
|
||||
ctx.lineWidth = this.stroke.width
|
||||
ctx.strokeStyle = 'red'
|
||||
ctx.lineWidth = 1 * scaling
|
||||
}
|
||||
ctx.strokeRect(...item)
|
||||
this.roundRect(ctx, ...item, false, true)
|
||||
// ctx.strokeRect(...item)
|
||||
}
|
||||
|
||||
this.postRender(ctx, ge)
|
||||
}
|
||||
|
||||
public onUpdate(): void {
|
||||
const ge = GameEngine.getGameEngine()
|
||||
const scaling = ge.currentScene?.scale ?? 1
|
||||
const min = Math.min(this.component.scale.x / 2, this.component.scale.y / 2)
|
||||
|
||||
this.borderRadius = {
|
||||
topLeft: Math.min(typeof this.props.borderRadius === 'number' ? this.props.borderRadius : this.props.borderRadius?.topLeft ?? 0, min),
|
||||
topRight: Math.min(typeof this.props.borderRadius === 'number' ? this.props.borderRadius : this.props.borderRadius?.topRight ?? 0, min),
|
||||
bottomLeft: Math.min(typeof this.props.borderRadius === 'number' ? this.props.borderRadius : this.props.borderRadius?.bottomLeft ?? 0, min),
|
||||
bottomRight:Math.min(typeof this.props.borderRadius === 'number' ? this.props.borderRadius : this.props.borderRadius?.bottomRight ?? 0, min)
|
||||
}
|
||||
if (this.props.stroke) {
|
||||
if (typeof this.props.stroke === 'string') {
|
||||
const stroke = { color: this.props.stroke, width: 1 }
|
||||
this.stroke = {
|
||||
top: stroke,
|
||||
left: stroke,
|
||||
right: stroke,
|
||||
bottom: stroke
|
||||
}
|
||||
} else if ('color' in this.props.stroke) {
|
||||
this.stroke = {
|
||||
top: this.props.stroke,
|
||||
left: this.props.stroke,
|
||||
right: this.props.stroke,
|
||||
bottom: this.props.stroke
|
||||
}
|
||||
} else {
|
||||
this.stroke = this.props.stroke
|
||||
}
|
||||
} else {
|
||||
this.stroke = undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a rounded rectangle using the current state of the canvas.
|
||||
* If you omit the last three params, it will draw a rectangle
|
||||
* outline with a 5 pixel border radius
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Number} x The top left x coordinate
|
||||
* @param {Number} y The top left y coordinate
|
||||
* @param {Number} width The width of the rectangle
|
||||
* @param {Number} height The height of the rectangle
|
||||
* @param {Number} [radius.tl = 0] Top left
|
||||
* @param {Number} [radius.tr = 0] Top right
|
||||
* @param {Number} [radius.br = 0] Bottom right
|
||||
* @param {Number} [radius.bl = 0] Bottom left
|
||||
* @param {Boolean} [fill = false] Whether to fill the rectangle.
|
||||
* @param {Boolean} [stroke = true] Whether to stroke the rectangle.
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
private roundRect(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
fill = false,
|
||||
stroke = true
|
||||
) {
|
||||
ctx.beginPath()
|
||||
// topLeft point
|
||||
ctx.moveTo(x + this.borderRadius.topLeft, y)
|
||||
// line to top right
|
||||
ctx.lineTo(x + width - this.borderRadius.topRight, y)
|
||||
// curve for top right
|
||||
ctx.quadraticCurveTo(x + width, y, x + width, y + this.borderRadius.topRight)
|
||||
// line to bottom right
|
||||
ctx.lineTo(x + width, y + height - this.borderRadius.bottomRight)
|
||||
// curve for the bottom right
|
||||
ctx.quadraticCurveTo(x + width, y + height, x + width - this.borderRadius.bottomRight, y + height)
|
||||
// line to bottom left
|
||||
ctx.lineTo(x + this.borderRadius.bottomLeft, y + height)
|
||||
// curve for bottom left
|
||||
ctx.quadraticCurveTo(x, y + height, x, y + height - this.borderRadius.bottomLeft)
|
||||
// line to top left
|
||||
ctx.lineTo(x, y + this.borderRadius.topLeft)
|
||||
// curve for top left
|
||||
ctx.quadraticCurveTo(x, y, x + this.borderRadius.topLeft, y)
|
||||
// end path
|
||||
ctx.closePath()
|
||||
// fill rect
|
||||
if (fill) {
|
||||
ctx.fill()
|
||||
}
|
||||
// stroke rect
|
||||
if (stroke) {
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
// private drawStroke(
|
||||
// ctx: CanvasRenderingContext2D,
|
||||
// ge: GameEngine,
|
||||
// width: number,
|
||||
// height: number,
|
||||
// stroke: Stroke,
|
||||
// options: StrokeOptions
|
||||
// ) {
|
||||
// const borders = this.getBorders(stroke)
|
||||
// const firstBorderRadius = this.getRadius(width, height, borders[0])
|
||||
// const secondBorderRadius = this.getRadius(width, height, borders[1])
|
||||
|
||||
// ctx.lineWidth = ge.currentScene?.scale ?? 1
|
||||
|
||||
// ctx.stroke()
|
||||
// }
|
||||
|
||||
// private getStrokeSize(
|
||||
// width: number,
|
||||
// height: number,
|
||||
// stroke: Stroke
|
||||
// ): [[number, number], [number, number]] {
|
||||
// const borders = this.getBorders(stroke)
|
||||
// const firstBorderRadius = this.getRadius(width, height, borders[0])
|
||||
// const secondBorderRadius = this.getRadius(width, height, borders[1])
|
||||
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * get the borders for a specific stroke
|
||||
// *
|
||||
// * @param stroke the stroke to get border
|
||||
// * @returns the name of the borders of the stroke
|
||||
// */
|
||||
// private getBorders(stroke: Stroke): [Border, Border] {
|
||||
// switch (stroke) {
|
||||
// case 'top': return ['topLeft', 'topRight']
|
||||
// case 'bottom': return ['bottomLeft', 'bottomRight']
|
||||
// case 'left': return ['topLeft', 'bottomLeft']
|
||||
// case 'right': return ['topRight', 'bottomRight']
|
||||
// }
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * get the border radius for a specific border
|
||||
// *
|
||||
// * @param width the width of the rectangle
|
||||
// * @param height the height of the rectangle
|
||||
// * @param border the border to find he radius
|
||||
// * @returns the radius of the specified border
|
||||
// */
|
||||
// private getRadius(width: number, height: number, border: Border): number {
|
||||
// if (!this.borderRadius) {
|
||||
// return 0
|
||||
// }
|
||||
// const min = Math.min(width / 2, height / 2)
|
||||
// if (typeof this.borderRadius === 'number') {
|
||||
// return Math.min(this.borderRadius, min)
|
||||
// }
|
||||
// return Math.min(this.borderRadius[border] ?? 0)
|
||||
// }
|
||||
}
|
||||
|
@ -1,44 +1,64 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import GameEngine from 'GameEngine'
|
||||
import Component2D from 'GameEngine/Component2D'
|
||||
import Renderer from '.'
|
||||
import type GameEngine from '..'
|
||||
|
||||
interface Params {
|
||||
text?: string
|
||||
size?: number
|
||||
weight?: 'bold'
|
||||
stroke?: string | {color: string, width: number}
|
||||
color?: string
|
||||
align?: 'left' | 'center' | 'right'
|
||||
overrideSizeLimit?: boolean
|
||||
}
|
||||
|
||||
export default class TextRenderer extends Renderer {
|
||||
export default class TextRenderer extends Renderer<Params> {
|
||||
|
||||
public text?: string
|
||||
public size?: number
|
||||
public weight?: 'bold'
|
||||
public color?: string
|
||||
private width?: number
|
||||
|
||||
public constructor(component: Component2D, params?: Params) {
|
||||
super(component)
|
||||
objectLoop(params ?? {}, (v, k) => {this[k as 'text'] = v})
|
||||
public async getWidth() {
|
||||
return this.width ?? -1
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
|
||||
const position = this.getPosition()
|
||||
const item: [number, number] = [
|
||||
// source x
|
||||
// 0 - 1.5 - -1.5
|
||||
position.x * (ge.caseSize.x),
|
||||
// source y
|
||||
position.y * (ge.caseSize.y)
|
||||
]
|
||||
await super.render(ge, ctx)
|
||||
const globalScale = ge.currentScene?.scale ?? 1
|
||||
const item = this.preRender(ctx, ge)
|
||||
|
||||
const size = this.component.scale.y * ge.caseSize.y
|
||||
|
||||
// console.log
|
||||
if (this.text) {
|
||||
ctx.fillStyle = this.color ?? 'black'
|
||||
ctx.textBaseline = 'middle'
|
||||
ctx.textAlign = 'center'
|
||||
|
||||
ctx.font = `${this.weight ? `${this.weight} ` : ''}${size + (this.size ?? 0)}px sans-serif`
|
||||
ctx.fillText(this.text, ...item)
|
||||
if (typeof this.props.text !== 'string') {
|
||||
if (this.debug) {
|
||||
console.warn('no text, no display')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.textBaseline = 'top'
|
||||
ctx.textAlign = this.props.align ?? 'left'
|
||||
let posX = item[0]
|
||||
if (this.props.align === 'center') {
|
||||
posX += item[2] / 2
|
||||
} else if (this.props.align === 'right') {
|
||||
posX += item[2]
|
||||
}
|
||||
ctx.font = `${this.props.weight ? `${this.props.weight} ` : ''}${(this.props.size ?? size) / 16 * ge.caseSize.x * globalScale * 3}px sans-serif`
|
||||
if (this.props.color) {
|
||||
ctx.fillStyle = this.props.color ?? 'black'
|
||||
ctx.fillText(this.props.text, posX, item[1], this.props.overrideSizeLimit ? undefined : item[2])
|
||||
}
|
||||
if (this.props.stroke) {
|
||||
if (typeof this.props.stroke === 'string') {
|
||||
ctx.strokeStyle = this.props.stroke
|
||||
ctx.lineWidth = ge.currentScene?.scale ?? 1
|
||||
} else {
|
||||
ctx.strokeStyle = this.props.stroke.color
|
||||
ctx.lineWidth = this.props.stroke.width * (ge.currentScene?.scale ?? 1)
|
||||
}
|
||||
ctx.strokeText(this.props.text, item[0], item[1], this.props.overrideSizeLimit ? undefined : item[2])
|
||||
}
|
||||
this.width = ctx.measureText(this.props.text).width / ge.caseSize.x / globalScale
|
||||
|
||||
this.postRender(ctx, ge)
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import GameEngine from 'GameEngine'
|
||||
import Vector2D from 'GameEngine/2D/Vector2D'
|
||||
import Component2D from 'GameEngine/Component2D'
|
||||
import Tileset from 'GameEngine/Tileset'
|
||||
import Renderer from '.'
|
||||
import type GameEngine from '..'
|
||||
import type Tileset from '../Tileset'
|
||||
|
||||
interface Params {
|
||||
tileset?: Tileset
|
||||
@ -13,30 +10,25 @@ interface Params {
|
||||
/**
|
||||
* TODO: Add origin support
|
||||
*/
|
||||
export default class TileRenderer extends Renderer implements Params {
|
||||
export default class TileRenderer extends Renderer<Params> {
|
||||
|
||||
public tileset?: Tileset
|
||||
public id?: number
|
||||
|
||||
public constructor(component: Component2D, params?: Params) {
|
||||
super(component)
|
||||
objectLoop(params ?? {}, (v, k) => {this[k as 'id'] = v})
|
||||
}
|
||||
|
||||
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
|
||||
if (!this.tileset || typeof this.id !== 'number') {
|
||||
public override async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
|
||||
await super.render(ge, ctx)
|
||||
if (!this.props.tileset || typeof this.props.id !== 'number') {
|
||||
return
|
||||
}
|
||||
const {sx, sy} = this.tileset.getSourceData(this.id)
|
||||
const {sx, sy} = this.props.tileset.getSourceData(this.props.id)
|
||||
const position = this.getPosition()
|
||||
await this.props.tileset.asset.load()
|
||||
ctx.drawImage(
|
||||
await this.tileset.asset.get(),
|
||||
this.props.tileset.asset.get(),
|
||||
sx,
|
||||
sy,
|
||||
this.tileset.width(this.id),
|
||||
this.tileset.height(this.id),
|
||||
position.x * (ge.caseSize.x),
|
||||
position.y * (ge.caseSize.y),
|
||||
this.props.tileset.width(this.props.id),
|
||||
this.props.tileset.height(this.props.id),
|
||||
position.x * ge.caseSize.x,
|
||||
position.y * ge.caseSize.y,
|
||||
(this.component.scale.x ?? ge.caseSize.x) * ge.caseSize.x,
|
||||
(this.component.scale.y ?? ge.caseSize.y) * ge.caseSize.y
|
||||
)
|
||||
|
@ -1,25 +1,157 @@
|
||||
import GameEngine from 'GameEngine'
|
||||
import Vector2D from 'GameEngine/2D/Vector2D'
|
||||
import Component2D from 'GameEngine/Component2D'
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import GameEngine from '..'
|
||||
import Vector2D from '../2D/Vector2D'
|
||||
import Component2D from '../Component2D'
|
||||
import MathUtils from '../libs/MathUtils'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export default abstract class Renderer {
|
||||
public constructor(
|
||||
protected component: Component2D
|
||||
) {}
|
||||
export default abstract class Renderer<Props extends object = {}> {
|
||||
|
||||
protected getPosition(): Vector2D {
|
||||
const ge = GameEngine.getGameEngine()
|
||||
const realPosition = ge.currentScene?.camera.topLeft.sum(this.component.position)
|
||||
if (!realPosition) {
|
||||
console.error('no camera?!?')
|
||||
return this.component.position
|
||||
}
|
||||
return new Vector2D(
|
||||
realPosition.x - this.component.scale.x / 2 - this.component.origin.x,
|
||||
realPosition.y - this.component.scale.y / 2 - this.component.origin.y
|
||||
)
|
||||
/**
|
||||
* set the renderer in debug mode
|
||||
*/
|
||||
public debug = false
|
||||
|
||||
public readonly props: Props
|
||||
|
||||
protected readonly defaultProps: Partial<Props> = {}
|
||||
|
||||
private oldProps?: Props
|
||||
private needUpdate = true
|
||||
|
||||
public constructor(
|
||||
public readonly component: Component2D,
|
||||
props: Props = {} as Props
|
||||
) {
|
||||
this.props = {...this.defaultProps, ...props}
|
||||
}
|
||||
|
||||
public abstract render(ge: GameEngine, ctx: CanvasRenderingContext2D): Promise<void>
|
||||
public setProps(newProps: Partial<Props>) {
|
||||
objectLoop(newProps as any, (value, obj) => {
|
||||
if (this.props[obj as keyof Props] === value) {
|
||||
return
|
||||
}
|
||||
this.props[obj as keyof Props] = value
|
||||
this.needUpdate = true
|
||||
})
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public async render(_ge: GameEngine, _ctx: CanvasRenderingContext2D): Promise<void> {
|
||||
if (this.needUpdate) {
|
||||
this.onUpdate?.(this.oldProps ?? this.props)
|
||||
this.needUpdate = false
|
||||
this.oldProps = this.props
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
|
||||
public onUpdate?(oldProps: Props): void
|
||||
|
||||
/**
|
||||
* return the position of the component with the camera offset applied
|
||||
* @param component the component to get the real position
|
||||
* @returns the position with the camera offset applied
|
||||
*/
|
||||
protected getPosition(component: Component2D = this.component): Vector2D {
|
||||
const ge = GameEngine.getGameEngine()
|
||||
let originComponent = component
|
||||
while (originComponent.parent) {
|
||||
originComponent = originComponent.parent
|
||||
}
|
||||
const realPosition = component.getAbsolutePosition()
|
||||
.rotate(
|
||||
originComponent.getAbsolutePosition(),
|
||||
this.component.getAbsoluteRotation()
|
||||
)
|
||||
.sum(
|
||||
-(ge.currentScene?.position?.x ?? 0),
|
||||
-(ge.currentScene?.position?.y ?? 0)
|
||||
)
|
||||
|
||||
return realPosition
|
||||
}
|
||||
|
||||
/**
|
||||
* transform the position of the object to the real position on the canvas
|
||||
* @returns the position of the element and scale translated to the canvas positioning
|
||||
*/
|
||||
protected realPosition(): [number, number, number, number] {
|
||||
const position = this.getPosition()
|
||||
return [
|
||||
this.translateToCanvas('x', position.x),
|
||||
this.translateToCanvas('y', position.y),
|
||||
this.translateToCanvas('x', this.component.scale.x),
|
||||
this.translateToCanvas('y', this.component.scale.y)
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the component
|
||||
*
|
||||
* It needs to be closed by rotateFinish
|
||||
* @param ctx the context
|
||||
* @param rotation rotation in degrees
|
||||
*/
|
||||
protected rotateStart(ctx: CanvasRenderingContext2D, sizes: [number, number, number, number], rotation: number) {
|
||||
const radians = MathUtils.toRadians(rotation)
|
||||
ctx.setTransform(
|
||||
1, // Horizontal Scaling
|
||||
0, // Horizontal Skewing
|
||||
0, // Vertical Skewing
|
||||
1, // Vertical Scaling
|
||||
sizes[0], // Horizontal Moving
|
||||
sizes[1] // Vertical Moving
|
||||
)
|
||||
ctx.rotate(radians)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ctx the context
|
||||
* @param ge the gmeEngine
|
||||
* @param additionnalRotation additionnal rotation
|
||||
* @returns x, y, width, height
|
||||
*/
|
||||
protected preRender(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
ge: GameEngine,
|
||||
additionnalRotation = 0
|
||||
): [number, number, number, number] {
|
||||
let position = this.realPosition()
|
||||
|
||||
this.rotateStart(
|
||||
ctx,
|
||||
position,
|
||||
this.component.getAbsoluteRotation() + additionnalRotation
|
||||
)
|
||||
|
||||
position = [
|
||||
0,
|
||||
0,
|
||||
position[2],
|
||||
position[3]
|
||||
]
|
||||
|
||||
return position
|
||||
}
|
||||
|
||||
protected postRender(ctx: CanvasRenderingContext2D, ge: GameEngine) {
|
||||
|
||||
// handle rotation reset
|
||||
ctx.resetTransform()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ctx the context
|
||||
*/
|
||||
protected rotateFinish(ctx: CanvasRenderingContext2D) {
|
||||
ctx.resetTransform()
|
||||
}
|
||||
|
||||
protected translateToCanvas(axis: 'x' | 'y', point: number): number {
|
||||
const ge = GameEngine.getGameEngine()
|
||||
const globalScale = ge.currentScene?.scale ?? 1
|
||||
return point * ge.caseSize[axis] * globalScale
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user