feat: Upgraded Engine

Signed-off-by: Avior <f.bouillon@aptatio.com>
This commit is contained in:
Florian Bouillon 2023-01-18 16:05:07 +01:00
parent bfd4beeaa4
commit 26df50c4a8
Signed by: Florian Bouillon
GPG Key ID: E05B3A94178D3A7C
33 changed files with 4886 additions and 16114 deletions

5
next-env.d.ts vendored
View File

@ -1,2 +1,5 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -19,5 +19,5 @@ module.exports = withPlugins([
}
}]
],
nextConfig
//nextConfig
)

15143
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,32 +10,33 @@
"test": "jest --config jext.config.js"
},
"dependencies": {
"@dzeio/components": "^0.10.1",
"@dzeio/object-util": "^1.2.0",
"critters": "^0.0.10",
"easy-sitemap": "^1.0.0",
"next": "^11.0.0",
"next-compose-plugins": "^2.2.0",
"next-plausible": "^1.6.1",
"next-pre-css": "^1.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-feather": "^2.0.9",
"stylus": "^0.54.7",
"stylus-loader": "^6.0.0",
"typescript": "^4.1.3",
"webpack": "^5.37.1"
"@dzeio/components": "^1.0.0-beta.10",
"@dzeio/listener": "^1.0.3",
"@dzeio/object-util": "^1",
"critters": "^0.0.16",
"easy-sitemap": "^1",
"lucide-react": "^0.102.0",
"next": "^12",
"next-compose-plugins": "^2",
"next-plausible": "^3",
"next-pre-css": "https://github.com/tcgdex/next-pre-css.git",
"react": "^18",
"react-dom": "^18",
"stylus": "^0.59.0",
"stylus-loader": "^7",
"typescript": "^4",
"webpack": "^5"
},
"devDependencies": {
"@babel/core": "^7.8.7",
"@types/favicons": "^6.2.0",
"@types/node": "^15.6.0",
"@types/react": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^4.14.1",
"@typescript-eslint/parser": "^4.14.1",
"eslint": "^7.1.0",
"eslint-config-next": "^10.2.2",
"eslint-plugin-react": "^7.18.3",
"ts-node": "^9.1.1"
"@babel/core": "^7",
"@types/favicons": "^6",
"@types/node": "^18",
"@types/react": "18.0.1",
"@typescript-eslint/eslint-plugin": "^5",
"@typescript-eslint/parser": "^5",
"eslint": "^8",
"eslint-config-next": "^12",
"eslint-plugin-react": "^7",
"ts-node": "^10"
}
}

View File

@ -0,0 +1,42 @@
import Collider from '.'
import Vector2D from '../Vector2D'
export default class BoxCollider2D extends Collider<{
center?: Vector2D
scale?: Vector2D
}> {
/**
*
* @returns [Vector2D, Vector2D, Vector2D, Vector2D] the four points of the box collider
*/
public pos(): [Vector2D, Vector2D, Vector2D, Vector2D] {
const middle = this.component.getAbsolutePosition(false).sum(this.component.scale.x / 2, this.component.scale.y / 2)
const topLeft = this.applyRotation(this.component.getAbsolutePosition(false), middle, this.component.rotation)
const topRight = this.applyRotation(topLeft.sum(this.component.scale.x, 0), middle, this.component.rotation)
const bottomLeft = this.applyRotation(topLeft.sum(0, this.component.scale.y), middle, this.component.rotation)
const bottomRight = this.applyRotation(topLeft.sum(this.component.scale), middle, this.component.rotation)
return [
topLeft,
topRight,
bottomLeft,
bottomRight
]
}
private applyRotation(point: Vector2D, middle: Vector2D, rotation: number) {
if (rotation === 0) {
return point.clone()
}
// FIXME: Fix rotation not returning the correct points for points other than topleft
return point.clone()
const rot = rotation * (Math.PI / 180)
const tmp = point.clone().sum(-middle.x, -middle.y)
return new Vector2D(
middle.x + (tmp.x * Math.cos(rot) - tmp.y * Math.sin(rot)),
middle.y + (tmp.x * Math.sin(rot) + tmp.y * Math.cos(rot))
)
}
}

View File

@ -0,0 +1,105 @@
import Collider from '.'
import Vector2D from '../Vector2D'
import BoxCollider2D from './BoxCollider2D'
import Circlecollider2D from './CircleCollider2D'
import PointCollider2D from './PointCollider2D'
export default class Checker {
public static boxCircleCollision(box: BoxCollider2D, circle: Circlecollider2D): boolean {
// clamp(value, min, max) - limits value to the range min..max
// Find the closest point to the circle within the rectangle
const [topLeft, bottomRight] = box.pos()
const center = circle.center()
const radius = circle.radius()
const closestX = this.clamp(center.x, topLeft.x, bottomRight.x)
const closestY = this.clamp(center.y, topLeft.y, bottomRight.y)
// Calculate the distance between the circle's center and this closest point
const distanceX = center.x - closestX
const distanceY = center.y - closestY
// If the distance is less than the circle's radius, an intersection occurs
const distanceSquared = distanceX * distanceX + distanceY * distanceY
return distanceSquared < radius * radius
}
public static circleCircleCollision(circle1: Circlecollider2D, circle2: Circlecollider2D) {
return false
}
public static pointBoxCollision(box1: PointCollider2D, box2: BoxCollider2D) {
const [topLeft, , , bottomRight] = box2.pos()
return box1.pos().isIn(topLeft, bottomRight)
}
/**
* dumb way to check currntly
*
* TODO: Handle rotation
*/
public static boxBoxCollision(box1: BoxCollider2D, box2: BoxCollider2D): boolean {
const selfPos = box1.pos()
const otherPos = box2.pos()
return selfPos[3].x >= otherPos[0].x && // self bottom higher than other top
selfPos[0].x <= otherPos[3].x &&
selfPos[3].y >= otherPos[0].y &&
selfPos[0].y <= otherPos[3].y
// const b1 = box1.pos()
// const b2 = box2.pos()
// for (const box1It of b1) {
// if (this.pointInRectangle(box1It, b2)) {
// return true
// }
// }
// return false
}
// eslint-disable-next-line complexity
public static detectCollision(collider1: Collider, collider2: Collider, reverse = false): boolean {
if (collider1 instanceof BoxCollider2D && collider2 instanceof Circlecollider2D) {
return this.boxCircleCollision(collider1, collider2)
} else if (collider1 instanceof Circlecollider2D && collider2 instanceof Circlecollider2D) {
return this.circleCircleCollision(collider2, collider1)
} else if (collider1 instanceof BoxCollider2D && collider2 instanceof BoxCollider2D) {
return this.boxBoxCollision(collider2, collider1)
} else if (collider1 instanceof BoxCollider2D && collider2 instanceof PointCollider2D) {
return this.pointBoxCollision(collider2, collider1)
}
if (!reverse) {
return this.detectCollision(collider2, collider1, true)
}
return false
}
private static clamp(value: number, min: number, max: number) {
return min > value ? min : max < value ? max : value
}
/**
* FIXME: does not work perfecly
*
* @param point the point to check
* @param rectangle the rectangle in which to check
* @returns if the point is in the defined rectangle or not
*/
private static pointInRectangle(point: Vector2D, rectangle: [Vector2D, Vector2D, Vector2D, Vector2D]) {
const ab = rectangle[1].sum(-rectangle[0].x, -rectangle[0].y)
const ad = rectangle[3].sum(-rectangle[0].x, -rectangle[0].y)
const am = point.sum(-rectangle[0].x, -rectangle[0].y)
const abam = ab.x * am.x + ab.y * am.y
const abab = ab.x * ab.x + ab.y * ab.y
const amad = am.x * ad.x + am.y * ad.y
const adad = ad.x * ad.x + ad.y * ad.y
return 0 <= abam && abam <= abab &&
0 <= amad && amad <= adad
}
}

View File

@ -0,0 +1,16 @@
import Collider from '.'
import Vector2D from '../Vector2D'
export default class Circlecollider2D extends Collider<{
radius?: number
offset?: Vector2D
}> {
public center(): Vector2D {
return new Vector2D(0)
}
public radius(): number {
return this.params.radius ?? 0
}
}

View File

@ -0,0 +1,16 @@
import Collider from '.'
import Vector2D from '../Vector2D'
export default class PointCollider2D extends Collider<{
center?: Vector2D
scale?: Vector2D
}> {
/**
*
* @returns Vector2D the position of the point
*/
public pos(): Vector2D {
return this.component.getAbsolutePosition()
}
}

View File

@ -0,0 +1,17 @@
import Component2D from 'GameEngine/Component2D'
export default abstract class Collider<
// eslint-disable-next-line @typescript-eslint/ban-types
T extends {} | void = {} | void
> {
public component!: Component2D
protected params: T = {} as T
public constructor(it: T | void) {
if (it) {
this.params = it
}
}
}

View File

@ -1,58 +0,0 @@
import GameEngine from 'GameEngine'
import Component2D from 'GameEngine/Component2D'
import Vector2D from '../Vector2D'
type BuiltinCollisionTypes = 'click' | 'pointerDown' | 'pointerUp'
export default class BoxCollider2D {
public constructor(
private component: Component2D,
public type: BuiltinCollisionTypes | string = 'collision',
private center = new Vector2D(0, 0),
private scale = new Vector2D(1, 1)
) {}
public pointColliding(point: Vector2D, type: BuiltinCollisionTypes | string = 'collision'): boolean {
if (this.type !== type) {
return false
}
return point.isIn(
...this.pos()
)
}
public pos(): [Vector2D, Vector2D] {
const scale = this.scale.multiply(this.component.scale)
const positionCenter = GameEngine.getGameEngine().currentScene?.position.sum(this.component.origin.sub(
new Vector2D(
this.component.position.x,
this.component.position.y
)
)) ?? this.component.origin.sub(
new Vector2D(
this.component.position.x,
this.component.position.y
)
)
const center = this.center.sum(positionCenter)
return [new Vector2D(
center.x,
center.y
),
new Vector2D(
center.x + scale.x,
center.y + scale.y
)]
}
public collideWith(collider: BoxCollider2D) {
const selfPos = this.pos()
const otherPos = collider.pos()
return selfPos[1].x >= otherPos[0].x && // self bottom higher than other top
selfPos[0].x <= otherPos[1].x &&
selfPos[1].y >= otherPos[0].y &&
selfPos[0].y <= otherPos[1].y
}
}

View File

@ -1,26 +1,29 @@
import Component2D, { ComponentState } from 'GameEngine/Component2D'
import RectRenderer from 'GameEngine/Renderer/RectRenderer'
import BoxCollider2D from '../Collision/BoxCollider2D'
import Vector2D from '../Vector2D'
export default class ColliderDebugger extends Component2D {
export default class ColliderDebugger extends Component2D<{collision?: Array<string>}> {
public readonly name = 'ColliderDebugger'
public constructor(component: Component2D, collider: BoxCollider2D) {
super()
this.collider = collider
const [topLeft, bottomRight] = collider.pos()
const size = topLeft.sub(bottomRight)
this.position = topLeft
this.scale = size
this.origin = new Vector2D(-(this.scale.x / 2), -(this.scale.y / 2))
this.renderer = new RectRenderer(this, {stroke: 'black'})
public renderer: RectRenderer = new RectRenderer(this, {stroke: 'transparent'})
public init() {
if (!this.parent) {
console.error('cant setup, no parent')
return
}
this.collider = this.parent.collider
this.position = new Vector2D(0)
this.scale = this.parent.scale
this.origin = this.parent.origin
}
public update(state: ComponentState) {
if (state.isColliding) {
(this.renderer as RectRenderer).material = 'rgba(0, 255, 0, .7)'
if (state.collideWith?.filter((it) => !this.params.collision ? true : this.params.collision.includes(it.name)).length ?? 0 > 1) {
this.renderer.material = 'rgba(0, 255, 0, .7)'
} else {
(this.renderer as RectRenderer).material = undefined
this.renderer.material = undefined
}
}
}

View File

@ -5,9 +5,14 @@ export default class Vector2D {
public y: number
public constructor(
x: number,
x: number | [number, number],
y?: number
) {
if (typeof x === 'object') {
this.x = x[0]
this.y = x[1]
return
}
this.x = x
if (typeof y === 'number') {
this.y = y
@ -16,6 +21,11 @@ export default class Vector2D {
}
}
/**
* return a new vector multiplied with the current one
* @param v vector
* @returns a new vector
*/
public multiply(v: Vector2D): Vector2D {
return new Vector2D(
v.x * this.x,
@ -23,6 +33,12 @@ export default class Vector2D {
)
}
/**
* return a new vector summed with the current one
* @param v vector or x to add
* @param y y to add
* @returns a new vector
*/
public sum(v: Vector2D | number, y?: number): Vector2D {
if (typeof v === 'number') {
return new Vector2D(this.x + v, this.y + (y ?? v))
@ -109,4 +125,8 @@ export default class Vector2D {
public equal(vector: Vector2D): boolean {
return vector.x === this.x && vector.y === this.y
}
public toArray(): [number, number] {
return [this.x, this.y]
}
}

View File

@ -40,6 +40,7 @@ export default class Asset {
this.image = new Image()
this.image.src = this.path
this.image.onload = () => {
console.log('resource loaded', this.path, this.image.width, this.image.height)
this.isLoaded = true
this.status = AssetStatus.LOADED
res()

View File

@ -1,4 +1,4 @@
import BoxCollider2D from './2D/Collision/BoxCollider2D'
import Collider from './2D/Collider'
import Vector2D from './2D/Vector2D'
import Renderer from './Renderer'
import Scene from './Scene'
@ -6,7 +6,7 @@ import Scene from './Scene'
export interface ComponentState {
mouseHovering?: boolean
/**
* is it is collinding return the type of collision
* is it is colliding return the type of Collider
*/
isColliding?: string
collideWith?: Array<Component2D>
@ -16,9 +16,10 @@ export interface ComponentState {
export type StaticComponent<
// eslint-disable-next-line @typescript-eslint/ban-types
T extends {} | void = {} | void
T extends {} | void = {} | void,
C extends Component2D<T> = Component2D<T>
> =
new (params: T | undefined) => Component2D<T>
new (params: T | undefined) => C
/**
* 2D Component
@ -71,10 +72,10 @@ T extends {} | void = {} | void
/**
* Component collider for events
*
* @type {BoxCollider2D}
* @type {Collider}
* @memberof Component2D
*/
public collider?: BoxCollider2D
public collider?: Collider
/**
* Parent component of self is any
@ -109,6 +110,11 @@ T extends {} | void = {} | void
*/
public debug?: boolean
/**
* Component rotation in Degrees
*/
public rotation = 0
protected params: T = {} as T
/**
@ -117,7 +123,7 @@ T extends {} | void = {} | void
*/
public abstract readonly name: string
public constructor(it: T) {
public constructor(it: T | void) {
if (it) {
this.params = it
}
@ -136,14 +142,33 @@ T extends {} | void = {} | void
public destroy?(): Promise<void> | void
public getAbsolutePosition(): Vector2D {
const realPosition = this.position.sum(
public getAbsolutePosition(calculateRotation = true): Vector2D {
let pos = this.position.sum(
this.scale.multiply(this.origin)
)
if (!this.parent) {
return realPosition
if (this.parent) {
pos = pos.sum(this.parent.getAbsolutePosition(calculateRotation))
}
return realPosition.sum(this.parent.getAbsolutePosition())
if (this.rotation && calculateRotation) {
const middle = pos.clone().sum(this.scale.x / 2, this.scale.y / 2)
const rot = this.rotation * (Math.PI / 180)
const tmp = pos.clone().sum(-middle.x, -middle.y)
return pos.set(
middle.x + (tmp.x * Math.cos(rot) - tmp.y * Math.sin(rot)),
middle.y + (tmp.x * Math.sin(rot) + tmp.y * Math.cos(rot))
)
}
return pos
}
public getAbsoluteRotation(): number {
if (this.parent) {
return this.parent.getAbsoluteRotation() + this.rotation
}
return this.rotation
}
public setState(key: keyof ComponentState, value: any): void {
@ -153,4 +178,8 @@ T extends {} | void = {} | void
public updateParam(key: keyof T, value: any): void {
this.params[key] = value
}
public getParam(key: keyof T): any {
return this.params[key]
}
}

View File

@ -6,15 +6,27 @@ import Cursor from './Cursor'
/**
* Currently not working Camera implementation
*/
export default class Camera extends Component2D {
export default class Camera extends Component2D<{
position?: Vector2D
zoom?: number
}> {
public name = 'Camera'
public position: Vector2D = new Vector2D(0)
public zoom = 1
public init(): void | Promise<void> {
if (this.params.position) {
this.position = this.params.position
}
if (this.params.zoom) {
this.setZoom(this.params.zoom)
}
}
public update() {
let needCursorUpdate = false
const scene = GameEngine.getGameEngine().currentScene
const scene = GameEngine.getGameEngine()?.currentScene
if (!scene) {
return
}
@ -42,4 +54,14 @@ export default class Camera extends Component2D {
public setZoom(value: number) {
this.zoom = value
}
public addToZoom(value: number, min?: number, max?: number) {
this.zoom += value
if (min && min > this.zoom) {
this.zoom = min
}
if (max && max < this.zoom) {
this.zoom = max
}
}
}

View File

@ -0,0 +1,36 @@
import Vector2D from 'GameEngine/2D/Vector2D'
import Component2D from 'GameEngine/Component2D'
import Renderer from 'GameEngine/Renderer'
export default class ComponentRenderer extends Component2D<{
renderer?: Renderer
position?: Vector2D
scale?: Vector2D
name?: string
}> {
public name = 'ComponentRenderer'
public update(): void | Promise<void> {
if (this.params.renderer) {
this.renderer = this.params.renderer
}
if (this.params.name) {
this.name = this.params.name
}
if (this.params.position) {
this.position = this.params.position
}
if (this.params.scale) {
this.scale = this.params.scale
}
}
public updateRenderer(key: string, value: any) {
(this.renderer as any)[key as any] = value
}
}

View File

@ -1,8 +1,9 @@
import GameEngine from 'GameEngine'
import BoxCollider2D from 'GameEngine/2D/Collision/BoxCollider2D'
import PointCollider2D from 'GameEngine/2D/Collider/PointCollider2D'
import Vector2D from 'GameEngine/2D/Vector2D'
import Component2D from 'GameEngine/Component2D'
import RectRenderer from 'GameEngine/Renderer/RectRenderer'
import Camera from './Camera'
export default class Cursor extends Component2D<{
debug?: boolean
@ -28,7 +29,9 @@ export default class Cursor extends Component2D<{
public scale: Vector2D = new Vector2D(1)
public collider: BoxCollider2D = new BoxCollider2D(this)
public collider: PointCollider2D = new PointCollider2D()
private touchZoom = 0
/**
* event handled down event
@ -91,14 +94,35 @@ export default class Cursor extends Component2D<{
this.onUp(ev)
}
private onTouchMove = (ev: TouchEvent) => {
// console.log('onTouchMove')
this.onMove(ev.touches.item(0) ?? undefined)
if (ev.touches.length >= 2) {
const cam = GameEngine.getGameEngine().currentScene?.getComponents().find((it) => it.name === 'Camera') as Camera | undefined
if (!cam) {
return
}
const nv = Math.hypot(
ev.touches[0].pageX - ev.touches[1].pageX,
ev.touches[0].pageY - ev.touches[1].pageY
)
cam.addToZoom(-((this.touchZoom - nv) / 100), 1)
this.touchZoom = nv
}
}
private onTouchStart = (ev: TouchEvent) => {
// console.log('onTouchStart')
this.onDown(ev.touches.item(0) ?? undefined)
if (ev.touches.length >= 2) {
this.touchZoom = Math.hypot(
ev.touches[0].pageX - ev.touches[1].pageX,
ev.touches[0].pageY - ev.touches[1].pageY
)
}
}
private onTouchEnd = (ev: TouchEvent) => {
@ -122,7 +146,7 @@ export default class Cursor extends Component2D<{
* Catch the onDown events
*/
private onDown(ev?: MouseEvent | Touch) {
console.log('cursor down')
// console.log('cursor down')
if (ev) {
this.updatePosition(
ev.clientX ?? 0,
@ -136,7 +160,7 @@ export default class Cursor extends Component2D<{
* catch the onUp events
*/
private onUp(ev?: MouseEvent | Touch) {
console.log('cursor up')
// console.log('cursor up')
if (ev) {
this.updatePosition(
ev.clientX ?? 0,
@ -146,8 +170,12 @@ export default class Cursor extends Component2D<{
this.eventDown = false
}
// eslint-disable-next-line complexity
private updatePosition(clientX: number, clientY: number) {
const ge = GameEngine.getGameEngine()
if (!ge) {
return
}
this.oldPosition = [clientX, clientY]
this.position.set(
((clientX ?? 0) + window.scrollX - ge.canvas.offsetLeft) /

View File

@ -7,6 +7,7 @@ import Renderer from '.'
interface Params {
asset?: Asset
stream?: boolean
imageRotation?: number
debug?: boolean
}
@ -17,13 +18,15 @@ export default class ImageRenderer extends Renderer implements Params {
public asset?: Asset
public stream = true
public imageRotation = 0
public debug = false
public constructor(component: Component2D, params?: Params) {
super(component)
objectLoop(params ?? {}, (value, key) => {this[key as keyof Params] = value})
objectLoop(params ?? {}, (value, key) => {this[key as 'stream'] = value as any})
}
// eslint-disable-next-line complexity
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
if (!this.asset) {
@ -44,15 +47,9 @@ export default class ImageRenderer extends Renderer implements Params {
return
}
const globalScale = ge.currentScene?.scale ?? 1
const size = this.asset.size()
const position = this.getPosition()
const final: [number, number, number, number] = [
position.x * ge.caseSize.x * globalScale,
position.y * ge.caseSize.y * globalScale,
(this.component.scale.x ?? ge.caseSize.x) * ge.caseSize.x * globalScale,
(this.component.scale.y ?? ge.caseSize.y) * ge.caseSize.y * globalScale
]
const final = this.preRender(ctx, ge, this.imageRotation)
if (this.debug || this.component.debug) {
ctx.fillStyle = 'red'
ctx.fillRect(...final)
@ -65,5 +62,7 @@ export default class ImageRenderer extends Renderer implements Params {
size.y,
...final
)
this.postRender(ctx, ge)
}
}

View File

@ -17,22 +17,11 @@ export default class RectRenderer extends Renderer implements Params {
public constructor(component: Component2D, params?: Params) {
super(component)
objectLoop(params ?? {}, (value, key) => {this[key as 'material'] = value})
objectLoop(params ?? {}, (value, key) => {this[key] = value as any})
}
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
const position = this.getPosition()
const globalScale = ge.currentScene?.scale ?? 1
const item: [number, number, number, number] = [
// source x
position.x * ge.caseSize.x * globalScale,
// source y
position.y * ge.caseSize.y * globalScale,
// size X
this.component.scale.x * ge.caseSize.x * globalScale,
// size Y
this.component.scale.y * ge.caseSize.y * globalScale
]
const item = this.preRender(ctx, ge)
if (this.material) {
ctx.fillStyle = this.material
@ -57,5 +46,7 @@ export default class RectRenderer extends Renderer implements Params {
}
ctx.strokeRect(...item)
}
this.postRender(ctx, ge)
}
}

View File

@ -23,44 +23,42 @@ export default class TextRenderer extends Renderer implements Params {
public constructor(component: Component2D, params?: Params) {
super(component)
objectLoop(params ?? {}, (v, k) => {this[k as 'text'] = v})
objectLoop(params ?? {}, (value, key) => {this[key] = value as any})
}
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
const position = this.getPosition()
const globalScale = ge.currentScene?.scale ?? 1
const item: [number, number] = [
// source x
// 0 - 1.5 - -1.5
position.x * ge.caseSize.x * globalScale,
// source y
position.y * ge.caseSize.y * globalScale
]
const item = this.preRender(ctx, ge)
const size = this.component.scale.y * ge.caseSize.y
// console.log
if (this.text) {
ctx.textBaseline = 'top'
ctx.textAlign = 'left'
if (!this.text) {
if (this.debug) {
console.warn('no text, no display')
ctx.font = `${this.weight ? `${this.weight} ` : ''}${(this.size ?? size) / 16 * ge.caseSize.x * globalScale * 3}px sans-serif`
if (this.color) {
ctx.fillStyle = this.color ?? 'black'
ctx.fillText(this.text, ...item)
}
if (this.stroke) {
if (typeof this.stroke === 'string') {
ctx.strokeStyle = this.stroke
ctx.lineWidth = ge.currentScene?.scale ?? 1
} else {
ctx.strokeStyle = this.stroke.color
ctx.lineWidth = this.stroke.width * (ge.currentScene?.scale ?? 1)
}
ctx.strokeText(this.text, ...item)
}
} else if (this.debug) {
console.warn('no text, no display')
return
}
ctx.textBaseline = 'top'
ctx.textAlign = 'left'
ctx.font = `${this.weight ? `${this.weight} ` : ''}${(this.size ?? size) / 16 * ge.caseSize.x * globalScale * 3}px sans-serif`
if (this.color) {
ctx.fillStyle = this.color ?? 'black'
ctx.fillText(this.text, item[0], item[1])
}
if (this.stroke) {
if (typeof this.stroke === 'string') {
ctx.strokeStyle = this.stroke
ctx.lineWidth = ge.currentScene?.scale ?? 1
} else {
ctx.strokeStyle = this.stroke.color
ctx.lineWidth = this.stroke.width * (ge.currentScene?.scale ?? 1)
}
ctx.strokeText(this.text, item[0], item[1])
}
this.postRender(ctx, ge)
}
}

View File

@ -19,7 +19,7 @@ export default class TileRenderer extends Renderer implements Params {
public constructor(component: Component2D, params?: Params) {
super(component)
objectLoop(params ?? {}, (value, key) => {this[key as 'id'] = value})
objectLoop(params ?? {}, (value, key) => {this[key] = value as any})
}
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {

View File

@ -8,9 +8,10 @@ export default abstract class Renderer {
protected component: Component2D
) {}
protected getPosition(): Vector2D {
protected getPosition(component?: Component2D): Vector2D {
const ge = GameEngine.getGameEngine()
const realPosition = this.component.getAbsolutePosition().sum(
const realPosition = (component ?? this.component).getAbsolutePosition().sum(
-(ge.currentScene?.position?.x ?? 0),
-(ge.currentScene?.position?.y ?? 0)
)
@ -18,5 +19,96 @@ export default abstract class Renderer {
return realPosition
}
protected realPosition(ge: GameEngine): [number, number, number, number] {
const position = this.getPosition()
const globalScale = ge.currentScene?.scale ?? 1
return [
position.x * ge.caseSize.x * globalScale,
position.y * ge.caseSize.y * globalScale,
this.component.scale.x * ge.caseSize.x * globalScale,
this.component.scale.y * ge.caseSize.y * globalScale
]
}
protected selfPosition(ge: GameEngine): [number, number, number, number] {
const position = this.component.position
const globalScale = ge.currentScene?.scale ?? 1
return [
position.x * ge.caseSize.x * globalScale,
position.y * ge.caseSize.y * globalScale,
(this.component.scale.x ?? ge.caseSize.x) * ge.caseSize.x * globalScale,
(this.component.scale.y ?? ge.caseSize.y) * ge.caseSize.y * globalScale
]
}
protected parentRealPosition(ge: GameEngine): [number, number, number, number] {
const parent = this.component.parent
if (!parent) {
return [
0,0,0,0
]
}
const position = this.getPosition(parent)
const globalScale = ge.currentScene?.scale ?? 1
return [
position.x * ge.caseSize.x * globalScale,
position.y * ge.caseSize.y * globalScale,
(parent.scale.x ?? ge.caseSize.x) * ge.caseSize.x * globalScale,
(this.component.scale.y ?? ge.caseSize.y) * ge.caseSize.y * globalScale
]
}
/**
* 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 = rotation * Math.PI / 180
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)
}
protected preRender(
ctx: CanvasRenderingContext2D,
ge: GameEngine,
additionnalRotation = 0
): [number, number, number, number] {
let position = this.realPosition(ge)
if (this.component.getAbsoluteRotation() !== 0 || additionnalRotation) {
this.rotateStart(
ctx,
this.component.parent ? this.parentRealPosition(ge) : position,
this.component.getAbsoluteRotation() + additionnalRotation
)
position = this.component.parent ? this.selfPosition(ge) : [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()
}
public abstract render(ge: GameEngine, ctx: CanvasRenderingContext2D): Promise<void>
}

View File

@ -1,8 +1,8 @@
import GameEngine from 'GameEngine'
import Component2D, { ComponentState } from './Component2D'
import BoxCollider2D from './2D/Collision/BoxCollider2D'
import Collider from './2D/Collider'
import Checker from './2D/Collider/Checker'
import Vector2D from './2D/Vector2D'
import { ComponentType } from 'react'
import Component2D, { ComponentState } from './Component2D'
export default class Scene {
public static scenes: Record<string, Scene> = {}
@ -12,8 +12,8 @@ export default class Scene {
public position: Vector2D = new Vector2D(0)
public scale = 1
public components: Array<Component2D> = []
private components: Array<Component2D> = []
private componentsInitialized: Array<boolean> = []
private ge!: GameEngine
private hasClickedComponent: number | undefined
@ -86,34 +86,39 @@ export default class Scene {
}
}
// eslint-disable-next-line complexity
public checkColisions(component: Component2D, exclusion?: Array<Component2D>): Array<Component2D> {
const list: Array<Component2D> = []
if (component.collider instanceof BoxCollider2D) {
const [topLeft, bottomRight] = component.collider.pos()
for (const otherComponent of this.components) {
if (
otherComponent === undefined ||
otherComponent.id === component.id ||
!(otherComponent.collider instanceof BoxCollider2D)
) {
continue
}
if (!component.collider) {
return list
}
for (const otherComponent of this.components) {
if (
!otherComponent ||
otherComponent.id === component.id ||
!otherComponent.collider
) {
continue
}
// Exclude components from being checked
if (exclusion?.find((it) => it.id === otherComponent.id)) {
continue
}
// Exclude components from being checked
if (exclusion?.find((it) => it.id === otherComponent.id)) {
continue
}
// Check for collision
const otherCollider = otherComponent.collider.pos()
if (
bottomRight.x > otherCollider[0].x && // self bottom higher than other top
topLeft.x < otherCollider[1].x &&
bottomRight.y > otherCollider[0].y &&
topLeft.y < otherCollider[1].y
) {
list.push(otherComponent)
}
if (component.collider) {
component.collider.component = component
}
if (otherComponent.collider) {
otherComponent.collider.component = otherComponent
}
// Check for collision
if (
Checker.detectCollision(component.collider, otherComponent.collider)
) {
list.push(otherComponent)
}
}
return list
@ -130,6 +135,7 @@ export default class Scene {
if (component.childs) {
for await (const child of component.childs) {
child.parent = component
await this.initComponent(child)
}
}
@ -153,7 +159,6 @@ export default class Scene {
const toExclude: Array<Component2D> = []
if (component.childs && component.childs.length > 0) {
for await (const child of component.childs) {
child.parent = component
toExclude.push(...await this.updateComponent(child))
}
}

View File

@ -47,7 +47,7 @@ export default class GameEngine {
/**
* Maximum framerate you want to achieve
*
* note: -1 mean infinite
* note: -1/undefined mean infinite
*/
goalFramerate?: number
}

View File

@ -7,6 +7,7 @@ import Tileset from 'GameEngine/Tileset'
export class Explosion extends Component2D {
public name = 'Explosion'
public size = {
width: .9,
height: .9

View File

@ -1,5 +1,5 @@
import GameEngine from 'GameEngine'
import BoxCollider2D from 'GameEngine/2D/Collision/BoxCollider2D'
import BoxCollider2D from 'GameEngine/2D/Collider/BoxCollider2D'
import ColliderDebugger from 'GameEngine/2D/Debug/ColliderDebugger'
import PointDebugger from 'GameEngine/2D/Debug/PointDebugger'
import Vector2D from 'GameEngine/2D/Vector2D'
@ -16,7 +16,9 @@ export default class Item extends Component2D {
public static explosion = new Explosion()
public collider: BoxCollider2D = new BoxCollider2D(this, 'click')
public name = 'Item'
public collider: BoxCollider2D = new BoxCollider2D()
private x: number
private y: number
@ -43,7 +45,7 @@ export default class Item extends Component2D {
// console.log(this.tileset.getSourceData(0), this.tileset.width(0), this.tileset.height(0))
// console.log(this.tileset.getSourceData(1))
this.childs = [
new ColliderDebugger(this, this.collider),
new ColliderDebugger(),
new PointDebugger(this.collider.pos()[0]),
new PointDebugger(this.collider.pos()[1])
]

View File

@ -5,6 +5,7 @@ import RectRenderer from 'GameEngine/Renderer/RectRenderer'
export default class Line extends Component2D {
// public debug = true
public name = 'Line'
public constructor(direction: number, index: number) {
super()

View File

@ -1,5 +1,5 @@
import GameEngine from 'GameEngine'
import BoxCollider2D from 'GameEngine/2D/Collision/BoxCollider2D'
import BoxCollider2D from 'GameEngine/2D/Collider/BoxCollider2D'
import ColliderDebugger from 'GameEngine/2D/Debug/ColliderDebugger'
import Vector2D from 'GameEngine/2D/Vector2D'
import Component2D, { ComponentState } from 'GameEngine/Component2D'
@ -7,14 +7,15 @@ import RectRenderer from 'GameEngine/Renderer/RectRenderer'
import { globalState } from '..'
export default class Start extends Component2D {
public name = 'Start'
public renderer: RectRenderer = new RectRenderer(this, {material: 'yellow'})
public position: Vector2D = new Vector2D(1, 1)
public scale: Vector2D = new Vector2D(2, 1)
public collider: BoxCollider2D = new BoxCollider2D(this, 'click')
public childs: Array<Component2D> = [new ColliderDebugger(this, this.collider)]
public collider: BoxCollider2D = new BoxCollider2D()
public childs: Array<Component2D> = [new ColliderDebugger()]
public update(state: ComponentState) {
if (state.isColliding === 'click') {
if (state.collideWith?.find((it) => it.name === 'Cursor')) {
console.log('Start Game !')
GameEngine.getGameEngine().setScene('TicTacToe')
globalState.isPlaying = true

View File

@ -1,8 +1,8 @@
import React from 'react'
import App from 'next/app'
import React from 'react'
import PlausibleProvider from 'next-plausible'
import '@dzeio/components/style.css'
import PlausibleProvider from 'next-plausible'
export default class CApp extends App {
@ -10,14 +10,14 @@ export default class CApp extends App {
const { Component, pageProps } = this.props
return (
<PlausibleProvider
enabled
customDomain="https://proxy.dzeio.com"
domain="games.avior.me"
integrity="sha256-R6vN8jmBq9SIpnfJRnw9eNUfLbC2yO3GPQAKR5ZS7zQ="
>
<Component {...pageProps} />
</PlausibleProvider>
// <PlausibleProvider
// enabled
// customDomain="https://proxy.dzeio.com"
// domain="games.avior.me"
// integrity="sha256-R6vN8jmBq9SIpnfJRnw9eNUfLbC2yO3GPQAKR5ZS7zQ="
// >
<Component {...pageProps} />
// </PlausibleProvider>
)
}
}

View File

@ -1,4 +1,4 @@
import { Button, Text, Util, NotificationManager, Col, Row, Input } from '@dzeio/components'
import { Button, Col, Input, NotificationManager, Row, Text, Util } from '@dzeio/components'
import { GetServerSideProps } from 'next'
import React, { MouseEvent as ReactMouseEvent } from 'react'
import css from './pokemon-shuffle.module.styl'
@ -187,8 +187,9 @@ export default class PokemonShuffle extends React.Component<Props, States> {
return NotificationManager.addNotification('Cant move nothing')
}
document.addEventListener('mousemove', this.mouveMove)
this.setState({movingItem: {x,y,cell}})
this.state.items[y][x] = undefined
const items = this.state.items
items[y][x] = undefined
this.setState({movingItem: {x,y,cell}, items})
this.mouveMove(ev.nativeEvent)
return
} else {

View File

@ -6,9 +6,12 @@ import Scene from 'GameEngine/Scene'
import Item from 'games/tictactoe/Item'
import Line from 'games/tictactoe/Line'
import Start from 'games/tictactoe/Menu/Start'
import Cursor from 'GameEngine/Components/Cursor'
export default class Snake extends React.PureComponent {
private cursor = new Cursor()
public async componentDidMount() {
const ge = new GameEngine('#test', {
caseCount: 3,
@ -17,19 +20,21 @@ export default class Snake extends React.PureComponent {
})
const menuScene = new Scene('Menu')
menuScene.addComponent(
new Start()
new Start(),
this.cursor
)
const scene = new Scene('TicTacToe')
scene.addComponent(
...Array.from(new Array(2)).map((_, index) => new Line(0, index)),
...Array.from(new Array(2)).map((_, index) => new Line(1, index)),
...Array.from(new Array(9)).map((_, index) => new Item(index)),
Item.explosion
Item.explosion,
this.cursor
// new TilingDebugger()
)
ge.start()
ge.setScene(menuScene)
ge.setScene(menuScene).then(() => ge.start())
// ge.start()
}
public render = () => (

View File

@ -1,38 +1,45 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": "./src",
"paths": {
"@styl/*": ["client/styl/*"],
"@cp/*": ["client/components/*"],
"@smd/*": ["client/styl/modules/*"],
}
},
"exclude": [
"node_modules",
"out",
"__tests__"
],
"include": [
"next-env.d.ts",
"src/client/styl/stylus.d.ts",
"**/*.ts",
"**/*.tsx"
]
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": "./src",
"paths": {
"@styl/*": [
"client/styl/*"
],
"@cp/*": [
"client/components/*"
],
"@smd/*": [
"client/styl/modules/*"
]
},
"incremental": true
},
"exclude": [
"node_modules",
"out",
"__tests__"
],
"include": [
"next-env.d.ts",
"src/client/styl/stylus.d.ts",
"**/*.ts",
"**/*.tsx"
]
}

4949
yarn.lock

File diff suppressed because it is too large Load Diff