import { System } from './System.js'; import { Entity } from './Entity.js'; import { EventBus } from './EventBus.js'; /** * Main game engine - manages ECS, game loop, and systems */ export class Engine { constructor(canvas) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.entities = []; this.systems = []; this.events = new EventBus(); this.running = false; this.lastTime = 0; // Set canvas size this.canvas.width = 1024; this.canvas.height = 768; // Game state this.deltaTime = 0; } /** * Add a system to the engine */ addSystem(system) { if (system instanceof System) { system.init(this); this.systems.push(system); // Sort by priority (lower priority runs first) this.systems.sort((a, b) => a.priority - b.priority); } return this; } /** * Emit an event locally */ emit(event, data) { this.events.emit(event, data); } /** * Subscribe to an event */ on(event, callback) { return this.events.on(event, callback); } /** * Create and add an entity */ createEntity() { const entity = new Entity(); this.entities.push(entity); return entity; } /** * Remove an entity */ removeEntity(entity) { const index = this.entities.indexOf(entity); if (index > -1) { this.entities.splice(index, 1); } } /** * Get all entities */ getEntities() { return this.entities.filter(e => e.active); } /** * Main game loop */ start() { if (this.running) return; this.running = true; this.lastTime = performance.now(); this.gameLoop(); } /** * Stop the game loop */ stop() { this.running = false; } /** * Game loop using requestAnimationFrame */ gameLoop = (currentTime = 0) => { if (!this.running) return; // Calculate delta time this.deltaTime = (currentTime - this.lastTime) / 1000; // Convert to seconds this.lastTime = currentTime; // Clamp delta time to prevent large jumps this.deltaTime = Math.min(this.deltaTime, 0.1); // Update all systems const menuSystem = this.systems.find(s => s.name === 'MenuSystem'); const gameState = menuSystem ? menuSystem.getGameState() : 'playing'; const isPaused = gameState === 'paused' || gameState === 'start'; this.systems.forEach(system => { // Skip game systems if paused/start menu (but allow MenuSystem, UISystem, and RenderSystem) if (isPaused && system.name !== 'MenuSystem' && system.name !== 'UISystem' && system.name !== 'RenderSystem') { return; } system.update(this.deltaTime, this.entities); }); // Update input system's previous states at end of frame const inputSystem = this.systems.find(s => s.name === 'InputSystem'); if (inputSystem && inputSystem.updatePreviousStates) { inputSystem.updatePreviousStates(); } // Continue loop requestAnimationFrame(this.gameLoop); } /** * Clear the canvas */ clear() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } }