ecs init
This commit is contained in:
parent
5148ee2063
commit
aa08a8943a
7
src/engine/components/Component.ts
Normal file
7
src/engine/components/Component.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export abstract class Component {
|
||||
public readonly name: string;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
3
src/engine/components/ComponentNames.ts
Normal file
3
src/engine/components/ComponentNames.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export namespace ComponentNames {
|
||||
export const Sprite = "Sprite";
|
||||
}
|
2
src/engine/components/index.ts
Normal file
2
src/engine/components/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./Component";
|
||||
export * from "./ComponentNames";
|
34
src/engine/entities/Entity.ts
Normal file
34
src/engine/entities/Entity.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { type Component } from "../components";
|
||||
|
||||
const randomId = () => (Math.random() * 1_000_000_000).toString();
|
||||
|
||||
export abstract class Entity {
|
||||
public id: string;
|
||||
public components: Map<string, Component>;
|
||||
public name: string;
|
||||
|
||||
constructor(name: string, id: string = randomId()) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.components = new Map();
|
||||
}
|
||||
|
||||
public addComponent(component: Component) {
|
||||
this.components.set(component.name, component);
|
||||
}
|
||||
|
||||
public getComponent<T extends Component>(name: string): T {
|
||||
if (!this.hasComponent(name)) {
|
||||
throw new Error("Entity does not have component " + name);
|
||||
}
|
||||
return this.components.get(name) as T;
|
||||
}
|
||||
|
||||
public getComponents(): Component[] {
|
||||
return Array.from(this.components.values());
|
||||
}
|
||||
|
||||
public hasComponent(name: string): boolean {
|
||||
return this.components.has(name);
|
||||
}
|
||||
}
|
5
src/engine/entities/EntityNames.ts
Normal file
5
src/engine/entities/EntityNames.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export namespace EntityNames {
|
||||
export const Player = "Player";
|
||||
export const Wall = "Wall";
|
||||
export const Ball = "Ball";
|
||||
}
|
2
src/engine/entities/index.ts
Normal file
2
src/engine/entities/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./Entity";
|
||||
export * from "./EntityNames";
|
90
src/engine/index.ts
Normal file
90
src/engine/index.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { Entity } from "./entities";
|
||||
import { System } from "./systems";
|
||||
|
||||
export class Game {
|
||||
private systemOrder: string[];
|
||||
|
||||
private running: boolean;
|
||||
private lastTimeStamp: number;
|
||||
|
||||
public entities: Map<string, Entity>;
|
||||
public systems: Map<string, System>;
|
||||
public componentEntities: Map<string, Set<string>>;
|
||||
|
||||
constructor() {
|
||||
this.lastTimeStamp = performance.now();
|
||||
this.running = false;
|
||||
this.systemOrder = [];
|
||||
this.systems = new Map();
|
||||
this.entities = new Map();
|
||||
this.componentEntities = new Map();
|
||||
}
|
||||
|
||||
public start() {
|
||||
this.lastTimeStamp = performance.now();
|
||||
this.running = true;
|
||||
}
|
||||
|
||||
public addEntity(entity: Entity) {
|
||||
this.entities.set(entity.id, entity);
|
||||
}
|
||||
|
||||
public getEntity(id: string): Entity | undefined {
|
||||
return this.entities.get(id);
|
||||
}
|
||||
|
||||
public removeEntity(id: string) {
|
||||
this.entities.delete(id);
|
||||
}
|
||||
|
||||
public forEachEntityWithComponent(
|
||||
componentName: string,
|
||||
callback: (entity: Entity) => void,
|
||||
) {
|
||||
this.componentEntities.get(componentName)?.forEach((entityId) => {
|
||||
const entity = this.getEntity(entityId);
|
||||
if (!entity) return;
|
||||
|
||||
callback(entity);
|
||||
});
|
||||
}
|
||||
|
||||
public addSystem(system: System) {
|
||||
if (!this.systemOrder.includes(system.name)) {
|
||||
this.systemOrder.push(system.name);
|
||||
}
|
||||
this.systems.set(system.name, system);
|
||||
}
|
||||
|
||||
public getSystem<T>(name: string): T {
|
||||
return this.systems.get(name) as unknown as T;
|
||||
}
|
||||
|
||||
public doGameLoop(timeStamp: number) {
|
||||
if (!this.running) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dt = timeStamp - this.lastTimeStamp;
|
||||
this.lastTimeStamp = timeStamp;
|
||||
|
||||
// rebuild the Component -> { Entity } map
|
||||
this.componentEntities.clear();
|
||||
this.entities.forEach((entity) =>
|
||||
entity.getComponents().forEach((component) => {
|
||||
if (!this.componentEntities.has(component.name)) {
|
||||
this.componentEntities.set(
|
||||
component.name,
|
||||
new Set<string>([entity.id]),
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.componentEntities.get(component.name)?.add(entity.id);
|
||||
}),
|
||||
);
|
||||
|
||||
this.systemOrder.forEach((systemName) => {
|
||||
this.systems.get(systemName)?.update(dt, this);
|
||||
});
|
||||
}
|
||||
}
|
11
src/engine/systems/System.ts
Normal file
11
src/engine/systems/System.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Game } from "..";
|
||||
|
||||
export abstract class System {
|
||||
public readonly name: string;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
abstract update(dt: number, game: Game): void;
|
||||
}
|
7
src/engine/systems/SystemNames.ts
Normal file
7
src/engine/systems/SystemNames.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export namespace SystemNames {
|
||||
export const Render = "Render";
|
||||
export const Physics = "Physics";
|
||||
export const Input = "Input";
|
||||
export const Collision = "Collision";
|
||||
export const WallBounds = "WallBounds";
|
||||
}
|
2
src/engine/systems/index.ts
Normal file
2
src/engine/systems/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./SystemNames";
|
||||
export * from "./System";
|
Loading…
Reference in New Issue
Block a user