7a62067af1
Vite + React + PixiJS + TypeScript. Features: - 4-level campaign (King's Road → Obsidian Keep) - Isometric 2.5D grid with ley-line mechanics - ECS architecture (entities, components, systems) - 4 tower types, hero spellcaster, 10+ enemy types - Lich King boss with 3-phase AI - Meta-progression: essence, rune unlocks - Full UI redesign with fantasy design system Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
115 lines
2.8 KiB
TypeScript
115 lines
2.8 KiB
TypeScript
import { Application, Text, TextStyle } from 'pixi.js'
|
|
import { Time } from './Time'
|
|
import { EntityManager } from './EntityManager'
|
|
import { eventBus } from './EventBus'
|
|
import { getApp, layers } from '@/game/rendering/PixiRoot'
|
|
import { LevelScene } from '@/game/LevelScene'
|
|
import type { LevelDef } from '@/data/levels'
|
|
|
|
export class GameEngine {
|
|
private readonly _app: Application
|
|
readonly entities = new EntityManager()
|
|
private _running = false
|
|
private _lastTime = 0
|
|
private _frameCount = 0
|
|
private _fpsTime = 0
|
|
private _fpsText: Text | null = null
|
|
private _showFps = false
|
|
private _scene: LevelScene | null = null
|
|
private _canvas: HTMLElement | null = null
|
|
|
|
constructor(app: Application) {
|
|
this._app = app
|
|
}
|
|
|
|
loadLevel(def: LevelDef, canvas: HTMLElement): void {
|
|
this._scene?.destroy()
|
|
this._canvas = canvas
|
|
this._scene = new LevelScene(def)
|
|
this._scene.attachInput(canvas)
|
|
}
|
|
|
|
getScene(): LevelScene | null {
|
|
return this._scene
|
|
}
|
|
|
|
start(): void {
|
|
this._running = true
|
|
this._lastTime = performance.now()
|
|
this._fpsTime = performance.now()
|
|
this._app.ticker.add(this._tick, this)
|
|
this._setupFpsCounter()
|
|
}
|
|
|
|
stop(): void {
|
|
this._running = false
|
|
this._app.ticker.remove(this._tick, this)
|
|
}
|
|
|
|
setShowFps(show: boolean): void {
|
|
this._showFps = show
|
|
if (this._fpsText) this._fpsText.visible = show
|
|
}
|
|
|
|
private _tick(): void {
|
|
if (!this._running) return
|
|
|
|
const now = performance.now()
|
|
Time.delta = Math.min((now - this._lastTime) / 1000, 0.05)
|
|
Time.elapsed += Time.delta
|
|
this._lastTime = now
|
|
|
|
this._scene?.update(Time.delta)
|
|
this._updateFps(now)
|
|
}
|
|
|
|
private _setupFpsCounter(): void {
|
|
const style = new TextStyle({
|
|
fill: 'rgba(201, 161, 74, 0.7)',
|
|
fontSize: 12,
|
|
fontFamily: 'monospace',
|
|
})
|
|
this._fpsText = new Text({ text: 'FPS: --', style })
|
|
this._fpsText.visible = this._showFps
|
|
this._fpsText.x = 8
|
|
this._fpsText.y = 8
|
|
this._fpsText.zIndex = 9999
|
|
layers?.ui.addChild(this._fpsText)
|
|
}
|
|
|
|
private _updateFps(now: number): void {
|
|
this._frameCount++
|
|
if (now - this._fpsTime >= 500) {
|
|
const fps = Math.round((this._frameCount * 1000) / (now - this._fpsTime))
|
|
if (this._fpsText) this._fpsText.text = `FPS: ${fps}`
|
|
this._frameCount = 0
|
|
this._fpsTime = now
|
|
}
|
|
}
|
|
|
|
destroy(): void {
|
|
if (this._canvas && this._scene) {
|
|
this._scene.detachInput(this._canvas)
|
|
}
|
|
this._scene?.destroy()
|
|
this._scene = null
|
|
this.stop()
|
|
this.entities.clear()
|
|
eventBus.clear()
|
|
this._fpsText?.destroy()
|
|
this._fpsText = null
|
|
}
|
|
}
|
|
|
|
let _engine: GameEngine | null = null
|
|
|
|
export function createEngine(): GameEngine {
|
|
_engine?.destroy()
|
|
_engine = new GameEngine(getApp())
|
|
return _engine
|
|
}
|
|
|
|
export function getEngine(): GameEngine | null {
|
|
return _engine
|
|
}
|