Files
Arcanum_TD/src/game/core/GameEngine.ts
T
Mareli 7a62067af1 Initial commit: Arcanum TD — medieval fantasy tower defense
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>
2026-04-19 12:31:49 +03:00

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
}