/**
* Минимальный ECS: сущность — число, компоненты — объекты по имени, системы — функции.
* Хватает для 2D-игр: без метапрограммирования, всё видно в отладчике.
*/
export type Entity = number;
export interface System {
/** Вызывается каждый тик фиксированного шага. dt — секунды. */
update(world: World, dt: number): void;
}
export class World {
private nextId = 1;
private alive = new Set<Entity>();
/** entity -> (имя компонента -> компонент). */
private components = new Map<Entity, Map<string, object>>();
private systems: System[] = [];
createEntity(): Entity {
const e = this.nextId++;
this.alive.add(e);
this.components.set(e, new Map());
return e;
}
destroyEntity(e: Entity): void {
this.alive.delete(e);
this.components.delete(e);
}
isAlive(e: Entity): boolean {
return this.alive.has(e);
}
addComponent<T extends object>(e: Entity, name: string, component: T): T {
this.components.get(e)!.set(name, component);
return component;
}
getComponent<T extends object>(e: Entity, name: string): T | undefined {
return this.components.get(e)?.get(name) as T | undefined;
}
hasComponent(e: Entity, name: string): boolean {
return this.components.get(e)?.has(name) ?? false;
}
removeComponent(e: Entity, name: string): void {
this.components.get(e)?.delete(name);
}
/** Все живые сущности с указанным компонентом. */
query(...names: string[]): Entity[] {
const out: Entity[] = [];
for (const e of this.alive) {
const map = this.components.get(e)!;
let ok = true;
for (const n of names) {
if (!map.has(n)) {
ok = false;
break;
}
}
if (ok) out.push(e);
}
return out;
}
addSystem(system: System): void {
this.systems.push(system);
}
update(dt: number): void {
for (const s of this.systems) {
s.update(this, dt);
}
}
}