This repository has been archived on 2026-05-07. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
instructions-clear/src/game.js
T
2025-01-18 10:49:38 +09:00

132 lines
3.5 KiB
JavaScript

import { EventEmitter } from 'node:events'
import Ability from './ability.js'
import Buff from './buff.js'
import Entity from './entity.js'
import Projectile from './projectile.js'
import Terrain from './terrain.js'
export default class Game {
abilities = Object.values({...Ability})
buffs = Object.values({...Buff})
averageTick = 0
currentTick = 0
entities = []
height = 2000
projectiles = []
secondToSlowestTick = 0
terrains = []
tickRate = 30
width = 2000
#currentTiming = 0
#eventEmitter = new EventEmitter()
#logic = null
#nextTickAt = 0
#tickBudget = 1000 / this.tickRate
#timings = new Float32Array(this.tickRate)
get logic() { return this.#logic }
get eventEmitter() { return this.#eventEmitter }
get tickBudget() { return this.#tickBudget }
set logic(value) { this.#logic = value }
get unadjustedWaypoints() {
return this.terrains.map((t) => t.unadjustedWaypoints).concat(this.entities.map((e) => e.unadjustedWaypoints)).flat()
}
addTerrain(terrain) {
this.terrains.push(terrain)
}
despawn(object) {
if (object instanceof Entity) { this.despawnEntity(object) }
else if (object instanceof Terrain) { this.removeTerrain(object) }
else if (object instanceof Projectile) { this.despawnProjectile(object) }
else { console.error({ error: { reason: 'Can\'t despawn object', object } }) }
}
despawnEntity(entity) {
this.entities = this.entities.filter((e) => e.id != entity.id)
entity.game = null
}
despawnProjectile(projectile) {
this.projectiles = this.projectiles.filter((p) => p.id != projectile.id)
projectile.game = null
}
removeTerrain(terrain) {
this.terrains = this.terrains.filter((t) => t.id != terrain.id)
}
secToTick(sec) {
return Math.floor(this.tickRate * sec)
}
spawn(object) {
if (object instanceof Entity) { this.spawnEntity(object) }
else if (object instanceof Terrain) { this.addTerrain(object) }
else if (object instanceof Projectile) { this.spawnProjectile(object) }
else { console.error({ error: { reason: 'Can\'t spawn object', object } }) }
}
spawnEntity(entity) {
this.entities.push(entity)
entity.game = this
}
spawnProjectile(projectile) {
this.projectiles.push(projectile)
projectile.game = this
}
start() {
setInterval(() => this.#gameLoop(), 1)
}
update() {
this.entities.forEach((e) => e.update())
this.projectiles.forEach((p) => p.update())
if (this.#logic != null) {
this.#logic()
}
this.#calculateTickMetrics()
this.eventEmitter.emit('tick')
this.currentTick++
}
#calculateTickMetrics() {
this.averageTick = Math.floor(10 * this.#timings.reduce((sum, t) => sum += t, 0) / this.#timings.length) / 10
this.secondToSlowestTick = Math.floor(10 * this.#timings.toSorted().at(-2)) / 10
}
#gameLoop() {
const tickBudget = this.#tickBudget
if (this.#nextTickAt != null) {
const nextTickAt = this.#nextTickAt
this.#nextTickAt = null
let start = 0
while (start < nextTickAt) { start = performance.now() }
const before = performance.now()
this.update()
this.#nextTickAt = start + tickBudget
const after = performance.now()
const taken = (after - before)
this.#timings[this.#currentTiming] = taken
if (this.#currentTiming++ > this.#timings.length) {
this.#currentTiming = 0
}
if (after - before > tickBudget) {
console.warn(`Can't keep up! A tick took ${taken.toFixed(1)} ms (Budget: ${tickBudget.toFixed(1)} ms)`)
}
}
}
}