slime/src/core/Engine.js

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);
}
}