139 lines
3.1 KiB
JavaScript
139 lines
3.1 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
|