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" />
/// <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
) )

15075
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" "test": "jest --config jext.config.js"
}, },
"dependencies": { "dependencies": {
"@dzeio/components": "^0.10.1", "@dzeio/components": "^1.0.0-beta.10",
"@dzeio/object-util": "^1.2.0", "@dzeio/listener": "^1.0.3",
"critters": "^0.0.10", "@dzeio/object-util": "^1",
"easy-sitemap": "^1.0.0", "critters": "^0.0.16",
"next": "^11.0.0", "easy-sitemap": "^1",
"next-compose-plugins": "^2.2.0", "lucide-react": "^0.102.0",
"next-plausible": "^1.6.1", "next": "^12",
"next-pre-css": "^1.0.0", "next-compose-plugins": "^2",
"react": "^17.0.2", "next-plausible": "^3",
"react-dom": "^17.0.2", "next-pre-css": "https://github.com/tcgdex/next-pre-css.git",
"react-feather": "^2.0.9", "react": "^18",
"stylus": "^0.54.7", "react-dom": "^18",
"stylus-loader": "^6.0.0", "stylus": "^0.59.0",
"typescript": "^4.1.3", "stylus-loader": "^7",
"webpack": "^5.37.1" "typescript": "^4",
"webpack": "^5"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.8.7", "@babel/core": "^7",
"@types/favicons": "^6.2.0", "@types/favicons": "^6",
"@types/node": "^15.6.0", "@types/node": "^18",
"@types/react": "^17.0.0", "@types/react": "18.0.1",
"@typescript-eslint/eslint-plugin": "^4.14.1", "@typescript-eslint/eslint-plugin": "^5",
"@typescript-eslint/parser": "^4.14.1", "@typescript-eslint/parser": "^5",
"eslint": "^7.1.0", "eslint": "^8",
"eslint-config-next": "^10.2.2", "eslint-config-next": "^12",
"eslint-plugin-react": "^7.18.3", "eslint-plugin-react": "^7",
"ts-node": "^9.1.1" "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 Component2D, { ComponentState } from 'GameEngine/Component2D'
import RectRenderer from 'GameEngine/Renderer/RectRenderer' import RectRenderer from 'GameEngine/Renderer/RectRenderer'
import BoxCollider2D from '../Collision/BoxCollider2D'
import Vector2D from '../Vector2D' import Vector2D from '../Vector2D'
export default class ColliderDebugger extends Component2D { export default class ColliderDebugger extends Component2D<{collision?: Array<string>}> {
public readonly name = 'ColliderDebugger' public readonly name = 'ColliderDebugger'
public constructor(component: Component2D, collider: BoxCollider2D) {
super() public renderer: RectRenderer = new RectRenderer(this, {stroke: 'transparent'})
this.collider = collider
const [topLeft, bottomRight] = collider.pos() public init() {
const size = topLeft.sub(bottomRight) if (!this.parent) {
this.position = topLeft console.error('cant setup, no parent')
this.scale = size return
this.origin = new Vector2D(-(this.scale.x / 2), -(this.scale.y / 2)) }
this.renderer = new RectRenderer(this, {stroke: 'black'}) this.collider = this.parent.collider
this.position = new Vector2D(0)
this.scale = this.parent.scale
this.origin = this.parent.origin
} }
public update(state: ComponentState) { public update(state: ComponentState) {
if (state.isColliding) { if (state.collideWith?.filter((it) => !this.params.collision ? true : this.params.collision.includes(it.name)).length ?? 0 > 1) {
(this.renderer as RectRenderer).material = 'rgba(0, 255, 0, .7)' this.renderer.material = 'rgba(0, 255, 0, .7)'
} else { } 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 y: number
public constructor( public constructor(
x: number, x: number | [number, number],
y?: number y?: number
) { ) {
if (typeof x === 'object') {
this.x = x[0]
this.y = x[1]
return
}
this.x = x this.x = x
if (typeof y === 'number') { if (typeof y === 'number') {
this.y = y 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 { public multiply(v: Vector2D): Vector2D {
return new Vector2D( return new Vector2D(
v.x * this.x, 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 { public sum(v: Vector2D | number, y?: number): Vector2D {
if (typeof v === 'number') { if (typeof v === 'number') {
return new Vector2D(this.x + v, this.y + (y ?? v)) return new Vector2D(this.x + v, this.y + (y ?? v))
@ -109,4 +125,8 @@ export default class Vector2D {
public equal(vector: Vector2D): boolean { public equal(vector: Vector2D): boolean {
return vector.x === this.x && vector.y === this.y 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 = new Image()
this.image.src = this.path this.image.src = this.path
this.image.onload = () => { this.image.onload = () => {
console.log('resource loaded', this.path, this.image.width, this.image.height)
this.isLoaded = true this.isLoaded = true
this.status = AssetStatus.LOADED this.status = AssetStatus.LOADED
res() res()

View File

@ -1,4 +1,4 @@
import BoxCollider2D from './2D/Collision/BoxCollider2D' import Collider from './2D/Collider'
import Vector2D from './2D/Vector2D' import Vector2D from './2D/Vector2D'
import Renderer from './Renderer' import Renderer from './Renderer'
import Scene from './Scene' import Scene from './Scene'
@ -6,7 +6,7 @@ import Scene from './Scene'
export interface ComponentState { export interface ComponentState {
mouseHovering?: boolean mouseHovering?: boolean
/** /**
* is it is collinding return the type of collision * is it is colliding return the type of Collider
*/ */
isColliding?: string isColliding?: string
collideWith?: Array<Component2D> collideWith?: Array<Component2D>
@ -16,9 +16,10 @@ export interface ComponentState {
export type StaticComponent< export type StaticComponent<
// eslint-disable-next-line @typescript-eslint/ban-types // 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 * 2D Component
@ -71,10 +72,10 @@ T extends {} | void = {} | void
/** /**
* Component collider for events * Component collider for events
* *
* @type {BoxCollider2D} * @type {Collider}
* @memberof Component2D * @memberof Component2D
*/ */
public collider?: BoxCollider2D public collider?: Collider
/** /**
* Parent component of self is any * Parent component of self is any
@ -109,6 +110,11 @@ T extends {} | void = {} | void
*/ */
public debug?: boolean public debug?: boolean
/**
* Component rotation in Degrees
*/
public rotation = 0
protected params: T = {} as T protected params: T = {} as T
/** /**
@ -117,7 +123,7 @@ T extends {} | void = {} | void
*/ */
public abstract readonly name: string public abstract readonly name: string
public constructor(it: T) { public constructor(it: T | void) {
if (it) { if (it) {
this.params = it this.params = it
} }
@ -136,14 +142,33 @@ T extends {} | void = {} | void
public destroy?(): Promise<void> | void public destroy?(): Promise<void> | void
public getAbsolutePosition(): Vector2D { public getAbsolutePosition(calculateRotation = true): Vector2D {
const realPosition = this.position.sum( let pos = this.position.sum(
this.scale.multiply(this.origin) 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 { public setState(key: keyof ComponentState, value: any): void {
@ -153,4 +178,8 @@ T extends {} | void = {} | void
public updateParam(key: keyof T, value: any): void { public updateParam(key: keyof T, value: any): void {
this.params[key] = value 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 * 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 name = 'Camera'
public position: Vector2D = new Vector2D(0) public position: Vector2D = new Vector2D(0)
public zoom = 1 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() { public update() {
let needCursorUpdate = false let needCursorUpdate = false
const scene = GameEngine.getGameEngine().currentScene const scene = GameEngine.getGameEngine()?.currentScene
if (!scene) { if (!scene) {
return return
} }
@ -42,4 +54,14 @@ export default class Camera extends Component2D {
public setZoom(value: number) { public setZoom(value: number) {
this.zoom = value 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 GameEngine from 'GameEngine'
import BoxCollider2D from 'GameEngine/2D/Collision/BoxCollider2D' import PointCollider2D from 'GameEngine/2D/Collider/PointCollider2D'
import Vector2D from 'GameEngine/2D/Vector2D' import Vector2D from 'GameEngine/2D/Vector2D'
import Component2D from 'GameEngine/Component2D' import Component2D from 'GameEngine/Component2D'
import RectRenderer from 'GameEngine/Renderer/RectRenderer' import RectRenderer from 'GameEngine/Renderer/RectRenderer'
import Camera from './Camera'
export default class Cursor extends Component2D<{ export default class Cursor extends Component2D<{
debug?: boolean debug?: boolean
@ -28,7 +29,9 @@ export default class Cursor extends Component2D<{
public scale: Vector2D = new Vector2D(1) public scale: Vector2D = new Vector2D(1)
public collider: BoxCollider2D = new BoxCollider2D(this) public collider: PointCollider2D = new PointCollider2D()
private touchZoom = 0
/** /**
* event handled down event * event handled down event
@ -91,14 +94,35 @@ export default class Cursor extends Component2D<{
this.onUp(ev) this.onUp(ev)
} }
private onTouchMove = (ev: TouchEvent) => { private onTouchMove = (ev: TouchEvent) => {
// console.log('onTouchMove') // console.log('onTouchMove')
this.onMove(ev.touches.item(0) ?? undefined) 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) => { private onTouchStart = (ev: TouchEvent) => {
// console.log('onTouchStart') // console.log('onTouchStart')
this.onDown(ev.touches.item(0) ?? undefined) 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) => { private onTouchEnd = (ev: TouchEvent) => {
@ -122,7 +146,7 @@ export default class Cursor extends Component2D<{
* Catch the onDown events * Catch the onDown events
*/ */
private onDown(ev?: MouseEvent | Touch) { private onDown(ev?: MouseEvent | Touch) {
console.log('cursor down') // console.log('cursor down')
if (ev) { if (ev) {
this.updatePosition( this.updatePosition(
ev.clientX ?? 0, ev.clientX ?? 0,
@ -136,7 +160,7 @@ export default class Cursor extends Component2D<{
* catch the onUp events * catch the onUp events
*/ */
private onUp(ev?: MouseEvent | Touch) { private onUp(ev?: MouseEvent | Touch) {
console.log('cursor up') // console.log('cursor up')
if (ev) { if (ev) {
this.updatePosition( this.updatePosition(
ev.clientX ?? 0, ev.clientX ?? 0,
@ -146,8 +170,12 @@ export default class Cursor extends Component2D<{
this.eventDown = false this.eventDown = false
} }
// eslint-disable-next-line complexity
private updatePosition(clientX: number, clientY: number) { private updatePosition(clientX: number, clientY: number) {
const ge = GameEngine.getGameEngine() const ge = GameEngine.getGameEngine()
if (!ge) {
return
}
this.oldPosition = [clientX, clientY] this.oldPosition = [clientX, clientY]
this.position.set( this.position.set(
((clientX ?? 0) + window.scrollX - ge.canvas.offsetLeft) / ((clientX ?? 0) + window.scrollX - ge.canvas.offsetLeft) /

View File

@ -7,6 +7,7 @@ import Renderer from '.'
interface Params { interface Params {
asset?: Asset asset?: Asset
stream?: boolean stream?: boolean
imageRotation?: number
debug?: boolean debug?: boolean
} }
@ -17,13 +18,15 @@ export default class ImageRenderer extends Renderer implements Params {
public asset?: Asset public asset?: Asset
public stream = true public stream = true
public imageRotation = 0
public debug = false public debug = false
public constructor(component: Component2D, params?: Params) { public constructor(component: Component2D, params?: Params) {
super(component) 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) { public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
if (!this.asset) { if (!this.asset) {
@ -44,15 +47,9 @@ export default class ImageRenderer extends Renderer implements Params {
return return
} }
const globalScale = ge.currentScene?.scale ?? 1
const size = this.asset.size() const size = this.asset.size()
const position = this.getPosition() const final = this.preRender(ctx, ge, this.imageRotation)
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
]
if (this.debug || this.component.debug) { if (this.debug || this.component.debug) {
ctx.fillStyle = 'red' ctx.fillStyle = 'red'
ctx.fillRect(...final) ctx.fillRect(...final)
@ -65,5 +62,7 @@ export default class ImageRenderer extends Renderer implements Params {
size.y, size.y,
...final ...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) { public constructor(component: Component2D, params?: Params) {
super(component) 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) { public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
const position = this.getPosition() const item = this.preRender(ctx, ge)
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
]
if (this.material) { if (this.material) {
ctx.fillStyle = this.material ctx.fillStyle = this.material
@ -57,5 +46,7 @@ export default class RectRenderer extends Renderer implements Params {
} }
ctx.strokeRect(...item) ctx.strokeRect(...item)
} }
this.postRender(ctx, ge)
} }
} }

View File

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

View File

@ -19,7 +19,7 @@ export default class TileRenderer extends Renderer implements Params {
public constructor(component: Component2D, params?: Params) { public constructor(component: Component2D, params?: Params) {
super(component) 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) { public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {

View File

@ -8,9 +8,10 @@ export default abstract class Renderer {
protected component: Component2D protected component: Component2D
) {} ) {}
protected getPosition(): Vector2D { protected getPosition(component?: Component2D): Vector2D {
const ge = GameEngine.getGameEngine() 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?.x ?? 0),
-(ge.currentScene?.position?.y ?? 0) -(ge.currentScene?.position?.y ?? 0)
) )
@ -18,5 +19,96 @@ export default abstract class Renderer {
return realPosition 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> public abstract render(ge: GameEngine, ctx: CanvasRenderingContext2D): Promise<void>
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import React from 'react'
import App from 'next/app' import App from 'next/app'
import React from 'react'
import PlausibleProvider from 'next-plausible'
import '@dzeio/components/style.css' import '@dzeio/components/style.css'
import PlausibleProvider from 'next-plausible'
export default class CApp extends App { export default class CApp extends App {
@ -10,14 +10,14 @@ export default class CApp extends App {
const { Component, pageProps } = this.props const { Component, pageProps } = this.props
return ( return (
<PlausibleProvider // <PlausibleProvider
enabled // enabled
customDomain="https://proxy.dzeio.com" // customDomain="https://proxy.dzeio.com"
domain="games.avior.me" // domain="games.avior.me"
integrity="sha256-R6vN8jmBq9SIpnfJRnw9eNUfLbC2yO3GPQAKR5ZS7zQ=" // integrity="sha256-R6vN8jmBq9SIpnfJRnw9eNUfLbC2yO3GPQAKR5ZS7zQ="
> // >
<Component {...pageProps} /> <Component {...pageProps} />
</PlausibleProvider> // </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 { GetServerSideProps } from 'next'
import React, { MouseEvent as ReactMouseEvent } from 'react' import React, { MouseEvent as ReactMouseEvent } from 'react'
import css from './pokemon-shuffle.module.styl' 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') return NotificationManager.addNotification('Cant move nothing')
} }
document.addEventListener('mousemove', this.mouveMove) document.addEventListener('mousemove', this.mouveMove)
this.setState({movingItem: {x,y,cell}}) const items = this.state.items
this.state.items[y][x] = undefined items[y][x] = undefined
this.setState({movingItem: {x,y,cell}, items})
this.mouveMove(ev.nativeEvent) this.mouveMove(ev.nativeEvent)
return return
} else { } else {

View File

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

View File

@ -19,10 +19,17 @@
"jsx": "preserve", "jsx": "preserve",
"baseUrl": "./src", "baseUrl": "./src",
"paths": { "paths": {
"@styl/*": ["client/styl/*"], "@styl/*": [
"@cp/*": ["client/components/*"], "client/styl/*"
"@smd/*": ["client/styl/modules/*"], ],
} "@cp/*": [
"client/components/*"
],
"@smd/*": [
"client/styl/modules/*"
]
},
"incremental": true
}, },
"exclude": [ "exclude": [
"node_modules", "node_modules",

4949
yarn.lock

File diff suppressed because it is too large Load Diff