mirror of
https://github.com/Aviortheking/games.git
synced 2025-07-03 14:49:18 +00:00
Updated Infrastructure
Signed-off-by: Avior <florian.bouillon@delta-wings.net>
This commit is contained in:
38
src/GameEngine/Asset.ts
Normal file
38
src/GameEngine/Asset.ts
Normal file
@ -0,0 +1,38 @@
|
||||
export default class Asset {
|
||||
|
||||
public static assets: Record<string, Asset> = {}
|
||||
|
||||
public isLoaded = false
|
||||
|
||||
private image!: HTMLImageElement
|
||||
|
||||
private constructor(
|
||||
private path: string
|
||||
) {}
|
||||
|
||||
public static init(path: string) {
|
||||
if (!this.assets[path]) {
|
||||
this.assets[path] = new Asset(path)
|
||||
}
|
||||
return this.assets[path]
|
||||
}
|
||||
|
||||
public async load() {
|
||||
return new Promise<void>((res, rej) => {
|
||||
this.image = new Image()
|
||||
this.image.src = this.path
|
||||
this.image.onload = () => {
|
||||
this.isLoaded = true
|
||||
res()
|
||||
}
|
||||
this.image.onerror = rej
|
||||
})
|
||||
}
|
||||
|
||||
public async get() {
|
||||
if (!this.isLoaded) {
|
||||
await this.load()
|
||||
}
|
||||
return this.image
|
||||
}
|
||||
}
|
34
src/GameEngine/Component2D.ts
Normal file
34
src/GameEngine/Component2D.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import Renderer from './Renderer'
|
||||
|
||||
export interface ComponentState {
|
||||
mouseHovering: boolean
|
||||
mouseClicking: boolean
|
||||
mouseClicked: boolean
|
||||
}
|
||||
|
||||
export default abstract class Component2D {
|
||||
public id?: number
|
||||
public renderer?: Renderer
|
||||
public pos?: {x: number, y: number, z?: number, rotation?: number}
|
||||
|
||||
|
||||
protected size?: number | {width: number, height: number}
|
||||
|
||||
public init?(): Promise<void> | void
|
||||
|
||||
public update?(state: ComponentState): Promise<void> | void
|
||||
|
||||
public width() {
|
||||
if (!this.size) {
|
||||
return undefined
|
||||
}
|
||||
return typeof this.size === 'number' ? this.size : this.size?.width
|
||||
}
|
||||
|
||||
public height() {
|
||||
if (!this.size) {
|
||||
return undefined
|
||||
}
|
||||
return typeof this.size === 'number' ? this.size : this.size?.height
|
||||
}
|
||||
}
|
24
src/GameEngine/Renderer/ColorRenderer.ts
Normal file
24
src/GameEngine/Renderer/ColorRenderer.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import GameEngine from 'GameEngine'
|
||||
import Component2D from 'GameEngine/Component2D'
|
||||
import Renderer from '.'
|
||||
|
||||
export default class ColorRenderer implements Renderer {
|
||||
|
||||
public constructor(
|
||||
private component: Component2D,
|
||||
private color: string
|
||||
) {}
|
||||
|
||||
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
|
||||
if (!this.component.pos) {
|
||||
return
|
||||
}
|
||||
ctx.fillStyle = this.color
|
||||
ctx.fillRect(
|
||||
this.component.pos.x * (ge.caseSize[0]),
|
||||
this.component.pos.y * (ge.caseSize[1]),
|
||||
(this.component.width() ?? ge.caseSize[0]) * ge.caseSize[0],
|
||||
(this.component.height() ?? ge.caseSize[1]) * ge.caseSize[1]
|
||||
)
|
||||
}
|
||||
}
|
25
src/GameEngine/Renderer/ImageRenderer.ts
Normal file
25
src/GameEngine/Renderer/ImageRenderer.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import GameEngine from 'GameEngine'
|
||||
import Asset from 'GameEngine/Asset'
|
||||
import Component2D from 'GameEngine/Component2D'
|
||||
import Renderer from '.'
|
||||
|
||||
export default class ImageRenderer implements Renderer {
|
||||
|
||||
public constructor(
|
||||
private component: Component2D,
|
||||
private image: Asset
|
||||
) {}
|
||||
|
||||
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
|
||||
if (!this.component.pos) {
|
||||
return
|
||||
}
|
||||
ctx.drawImage(
|
||||
await this.image.get(),
|
||||
this.component.pos.x * (ge.caseSize[0]),
|
||||
this.component.pos.y * (ge.caseSize[1]),
|
||||
(this.component.width() ?? ge.caseSize[0]) * ge.caseSize[0],
|
||||
(this.component.height() ?? ge.caseSize[1]) * ge.caseSize[1]
|
||||
)
|
||||
}
|
||||
}
|
31
src/GameEngine/Renderer/TileRenderer.ts
Normal file
31
src/GameEngine/Renderer/TileRenderer.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import GameEngine from 'GameEngine'
|
||||
import Component2D from 'GameEngine/Component2D'
|
||||
import Tileset from 'GameEngine/Tileset'
|
||||
import Renderer from '.'
|
||||
|
||||
export default class TileRenderer implements Renderer {
|
||||
|
||||
public constructor(
|
||||
private component: Component2D,
|
||||
private tileset: Tileset,
|
||||
private id: number
|
||||
) {}
|
||||
|
||||
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
|
||||
if (!this.component.pos) {
|
||||
return
|
||||
}
|
||||
const {sx, sy} = this.tileset.getSourceData(this.id)
|
||||
ctx.drawImage(
|
||||
await this.tileset.asset.get(),
|
||||
sx,
|
||||
sy,
|
||||
this.tileset.width(),
|
||||
this.tileset.height(),
|
||||
this.component.pos.x * (ge.caseSize[0]),
|
||||
this.component.pos.y * (ge.caseSize[1]),
|
||||
(this.component.width() ?? ge.caseSize[0]) * ge.caseSize[0],
|
||||
(this.component.height() ?? ge.caseSize[1]) * ge.caseSize[1]
|
||||
)
|
||||
}
|
||||
}
|
5
src/GameEngine/Renderer/index.ts
Normal file
5
src/GameEngine/Renderer/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import GameEngine from 'GameEngine'
|
||||
|
||||
export default interface Renderer {
|
||||
render(ge: GameEngine, ctx: CanvasRenderingContext2D): Promise<void>
|
||||
}
|
56
src/GameEngine/Scene.ts
Normal file
56
src/GameEngine/Scene.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import GameEngine from 'GameEngine'
|
||||
import { ComponentState } from 'react'
|
||||
import AssetsManager from './Asset'
|
||||
import Component2D from './Component2D'
|
||||
|
||||
export default class Scene {
|
||||
public static scenes: Record<string, Scene> = {}
|
||||
|
||||
public background?: string
|
||||
|
||||
private components: Array<Component2D> = []
|
||||
private ge!: GameEngine
|
||||
|
||||
public constructor(sceneId: string) {
|
||||
Scene.scenes[sceneId] = this
|
||||
}
|
||||
|
||||
public addComponent(...cp: Array<Component2D>) {
|
||||
return this.components.push(...cp)
|
||||
}
|
||||
|
||||
public setGameEngine(ge: GameEngine) {
|
||||
this.ge = ge
|
||||
}
|
||||
|
||||
public async init() {
|
||||
this.components.forEach((v) => {
|
||||
if (v.init) {
|
||||
v.init()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public async update() {
|
||||
this.components.forEach(async (v) => {
|
||||
const state: Partial<ComponentState> = {}
|
||||
const width = (v.size?.width ?? 1) * this.ge.caseSize[0]
|
||||
const height = (v.size?.height ?? 1) * this.ge.caseSize[1]
|
||||
if (v.pos) {
|
||||
const ax = v.pos.x * this.ge.caseSize[0]
|
||||
const ay = v.pos.y * this.ge.caseSize[1]
|
||||
state.mouseHovering =
|
||||
this.ge.cursor.x >= ax && this.ge.cursor.x < (ax + width) &&
|
||||
this.ge.cursor.y >= ay && this.ge.cursor.y < (ay + height)
|
||||
state.mouseClicking = state.mouseHovering && this.ge.cursor.isDown
|
||||
state.mouseClicked = state.mouseClicking && !this.ge.cursor.wasDown
|
||||
}
|
||||
if (v.renderer) {
|
||||
await v.renderer.render(this.ge, this.ge.ctx)
|
||||
}
|
||||
if (v.update) {
|
||||
v.update(state as ComponentState)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
16
src/GameEngine/SoundManager.ts
Normal file
16
src/GameEngine/SoundManager.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export default class SoundManager {
|
||||
private audio: HTMLAudioElement
|
||||
public constructor(path: string) {
|
||||
this.audio = new Audio(path)
|
||||
this.audio.load()
|
||||
this.audio.volume = .8
|
||||
}
|
||||
|
||||
public play() {
|
||||
this.audio.play()
|
||||
}
|
||||
|
||||
public end() {
|
||||
this.audio.pause()
|
||||
}
|
||||
}
|
41
src/GameEngine/Tileset.ts
Normal file
41
src/GameEngine/Tileset.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import Asset from './Asset'
|
||||
|
||||
export interface TilesetDeclaration {
|
||||
// id: string
|
||||
// padding?: number
|
||||
fileSize: {width: number, height: number}
|
||||
tileSize: number | {width: number, height: number}
|
||||
spacing?: number
|
||||
}
|
||||
|
||||
export default class Tileset {
|
||||
|
||||
public constructor(
|
||||
public asset: Asset,
|
||||
private declaration: TilesetDeclaration
|
||||
) {}
|
||||
|
||||
public getPosFromId(id: number): {x: number, y: number} {
|
||||
const cols = Math.trunc(this.declaration.fileSize.width / this.width())
|
||||
const x = id % cols
|
||||
const y = Math.trunc(id / cols)
|
||||
return {x, y}
|
||||
}
|
||||
|
||||
public getSourceData(id: number): {sx: number ,sy: number} {
|
||||
const {x, y} = this.getPosFromId(id)
|
||||
const sx = x * this.width() + x * (this.declaration.spacing ?? 0)
|
||||
const sy = y * this.height() + y * (this.declaration.spacing ?? 0)
|
||||
return {sx, sy}
|
||||
}
|
||||
|
||||
public width() {
|
||||
const item = this.declaration.tileSize
|
||||
return typeof item === 'number' ? item : item.width
|
||||
}
|
||||
|
||||
public height() {
|
||||
const item = this.declaration.tileSize
|
||||
return typeof item === 'number' ? item : item.height
|
||||
}
|
||||
}
|
105
src/GameEngine/index.ts
Normal file
105
src/GameEngine/index.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import Scene from './Scene'
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* Animation Engine
|
||||
* Camera fined control
|
||||
* Collision
|
||||
*/
|
||||
export default class GameEngine {
|
||||
public ctx: CanvasRenderingContext2D
|
||||
public canvas: HTMLCanvasElement
|
||||
public caseSize: [number, number] = [1, 1]
|
||||
public cursor: {
|
||||
x: number
|
||||
y: number
|
||||
isDown: boolean
|
||||
wasDown: boolean
|
||||
} = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
isDown: false,
|
||||
wasDown: false
|
||||
}
|
||||
private currentScene?: Scene
|
||||
private isRunning = false
|
||||
|
||||
public constructor(
|
||||
private id: string,
|
||||
private options?: {
|
||||
caseCount?: number | [number, number]
|
||||
background?: string
|
||||
}
|
||||
) {
|
||||
const canvas = document.querySelector<HTMLCanvasElement>(id)
|
||||
if (!canvas) {
|
||||
throw new Error('Error, canvas not found!')
|
||||
}
|
||||
this.canvas = canvas
|
||||
if (this.options?.caseCount) {
|
||||
this.caseSize = [
|
||||
// @ts-expect-error idc
|
||||
this.canvas.width / ((typeof this.options.caseCount) !== 'number' ? this.options.caseCount[0] : this.options.caseCount ),
|
||||
// @ts-expect-error idc2 lol
|
||||
this.canvas.height / ((typeof this.options.caseCount) !== 'number' ? this.options.caseCount[1] : this.options.caseCount)
|
||||
]
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) {
|
||||
throw new Error('Error, Context could not get found!')
|
||||
}
|
||||
ctx.imageSmoothingEnabled = false
|
||||
this.ctx = ctx
|
||||
}
|
||||
|
||||
public start() {
|
||||
if (this.isRunning) {
|
||||
console.warn('Game is already running')
|
||||
return
|
||||
}
|
||||
this.isRunning = true
|
||||
requestAnimationFrame(() => {
|
||||
this.update()
|
||||
})
|
||||
document.addEventListener('mousemove', (ev) => {
|
||||
this.cursor.x = ev.clientX
|
||||
this.cursor.y = ev.clientY
|
||||
if (this.cursor.isDown) {
|
||||
this.cursor.wasDown = true
|
||||
}
|
||||
})
|
||||
document.addEventListener('mousedown', () => {
|
||||
this.cursor.isDown = true
|
||||
})
|
||||
document.addEventListener('mouseup', () => {
|
||||
this.cursor.isDown = false
|
||||
this.cursor.wasDown = false
|
||||
})
|
||||
}
|
||||
|
||||
public pause() {
|
||||
this.isRunning = false
|
||||
}
|
||||
|
||||
public setScene(scene: Scene | string) {
|
||||
this.currentScene = typeof scene === 'string' ? Scene.scenes[scene] : scene
|
||||
this.currentScene.setGameEngine(this)
|
||||
}
|
||||
|
||||
|
||||
private update() {
|
||||
if (!this.isRunning) {
|
||||
return
|
||||
}
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
if (this.options?.background) {
|
||||
this.ctx.fillStyle = this.options.background
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
}
|
||||
this.currentScene?.update()
|
||||
setTimeout(() => {
|
||||
this.update()
|
||||
}, 0)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user