Awesome Entity Component System
An entity component system made with typescript.
Install
npm i --save aecs
Getting Started
To use the entity component system, you first must create an instance of Engine.
import { Engine } from "aecs";
const engine = new Engine();
Define your Components:
import { Component } from "aecs";
// Components can have custom constructors, but they must be able to be initialized
// with no arguments, because the engine creates the instances for you.
// Try not to save complex data types in your components
// due to the handling of references inside javascript
export class PositionComponent extends Component {
public x: number = 0;
public y: number = 0;
}
Create an Entity type:
import { Entity } from "aecs";
//can have a constructor but is not needed
//the type is basically only used for queries from your systems
export class PlayerEntity extends Entity {}
For the creation of your entities it makes sense to define a Prefab.
import { Prefab } from "aecs";
export class PlayerPrefab extends Prefab<PlayerEntity> { // <-- entityType needed
private spawnX: number = 0;
private spawnY: number = 0;
protected init(): void {
//code here runs only once for the creation of the prefab instance
}
public someSetterDefinedByYou(x: number, y: number): void {
this.spawnX = x;
this.spawnY = y;
}
public instantiate(): PlayerEntity {
const positionComponent = new PositionComponent();
positionComponent.x = this.spawnX;
positionComponent.y = this.spawnY;
return new PlayerEntity(<<<SOME_ID>>>, [positionComponent]);
}
}
Finally a System is created to work with the entities and components
import { System } from "aecs";
//in this system we will instantiate a player and listen for keyboard inputs to move the player around
export class PlayerSystem extends System {
private playerPrefab: PlayerPrefab;
init(): void {
this.playerPrefab = this.prefabManager.getPrefab<PlayerPrefab>(PlayerPrefab);
this.playerPrefab.someSetterDefinedByYou(10, 10);
this.ecm.instantiatePrefab(this.playerPrefab);
}
// we have multiple ways to query for components/entities
run(delta: number): void {
//query by id
let playerEntityItem = this.ecm.getEntity<PlayerEntity>(<<<SOME_ID>>>)
//query by entity type -> returns array
let playerEntities = this.ecm.getEntites<PlayerEntity>(PlayerEntity);
//query by a specific component + id
let positionComponent = this.ecm.getComponent<PositionComponent>(PositionComponent, <<<SOME_ID>>>);
//query all by component type -> returns array
let positionComponents = this.ecm.getComponents<PositionComponent>(PositionComponent);
if(some key pressed) {
//do things with entities & components here
}
}
}
AECS is defined by multiple Worlds. Think of a world like a container which hold multiple prefabs & systems. So you could have for example a GameWorld and a MenuWorld etc. To get the engine running we need to define a world for our systems and prefabs to run.
import { World } from "aecs";
export interface WorldProperties {
someValue: string;
}
export class SomeWorld extends World<WorldProperties> {
init(): void {
this.initPrefabs([
new PlayerPrefab(PlayerEntity) // needs the entity type
]);
this.initSystems([
new PlayerSystem(true) // bool if active or not
]);
}
update(delta: number): void {
this.callSystems(delta); //in our loop we call all systems
}
}
In the last step we add our created GameWorld to the engine and start it all up
engine.addWorlds([
new SomeWorld({someValue: "someValue"})
]);
engine.setActiveWorld(SomeWorld);
engine.start(60); // number of ticks we want to have per second
//alternative you can manually call the update loop with
//engine.update(deltaTime?: number);
Important!
When you build your project with webpack it is needed to the following option to your webpack.config.js, otherwise aecs will throw all sorts of errors during runtime!
optimization: {
minimizer: [
new ESBuildMinifyPlugin({
keepNames: true
})
]
},