117 lines
3.0 KiB
JavaScript
117 lines
3.0 KiB
JavaScript
import { EventEmitter } from 'node:events'
|
|
import Entity from './entity.js'
|
|
import Terrain from './terrain.js'
|
|
import Projectile from './projectile.js'
|
|
|
|
export default class Game {
|
|
tickRate = 30
|
|
currentTick = 0
|
|
width = 2000
|
|
height = 2000
|
|
nextTickAt = 0
|
|
|
|
#logic = null
|
|
#entities = []
|
|
#eventEmitter = new EventEmitter()
|
|
#projectiles = []
|
|
#terrains = []
|
|
#tickBudget = 1000 / this.tickRate
|
|
|
|
get logic() { return this.#logic }
|
|
get entities() { return this.#entities }
|
|
get eventEmitter() { return this.#eventEmitter }
|
|
get projectiles() { return this.#projectiles }
|
|
get terrains() { return this.#terrains }
|
|
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
|
|
}
|
|
|
|
state() {
|
|
return {
|
|
...this,
|
|
entities: this.#entities.map((e) => e.state()),
|
|
terrains: this.#terrains.map((t) => t.state()),
|
|
projectiles: this.#projectiles.map((p) => p.state()),
|
|
}
|
|
}
|
|
|
|
gameLoop() {
|
|
const tickBudget = this.#tickBudget
|
|
|
|
if (this.nextTickAt != null) {
|
|
const nextTickAt = this.nextTickAt
|
|
this.nextTickAt = null
|
|
|
|
let start = performance.now()
|
|
while (start < nextTickAt) { start = performance.now() }
|
|
|
|
this.update()
|
|
this.nextTickAt = start + tickBudget
|
|
}
|
|
}
|
|
|
|
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.currentTick++
|
|
this.eventEmitter.emit('tick')
|
|
}
|
|
}
|