đźš§ Ecstra is a work-in-progress and might be unstable, use it at your own risks đźš§
Fast & Flexible EntityComponentSystem (ECS) for JavaScript and Typescript, available in browser and Node.js.
Get started with:
- The Documentation
- The JavaScript Examples
- The TypeScript Examples
🔍 I am currently looking for people to help me to identify their needs in order to drive the development of this library further.
Created as 'Flecs', it's been renamed to 'Ecstra' to avoid duplicate
Ecstra (pronounced as "extra") is heavily based on Ecsy, but mixes concepts from other great ECS. It also share some concepts with Hecs.
My goals for the library is to keep it:
- đź’» Framework Agnostic đź’»
- 🪶 Lightweight 🪶
- ⚡ Fast ⚡
- 🏋️ Robust 🏋️
The library will prioritize stability improvements over feature development.
- Easy To Use Query Language
- System Grouping
- System Topological Sorting
- Automatic Component Registration
- Component Properties Merging
- System Queries Merging
- TypeScript Decorators
- For component properties
- For system ordering and configuration
- No Dependency
Using npm:
npm install ecstraUsing yarn
yarn add ecstraThe library is distributed as an ES6 module, but also comes with two UMD builds:
fecs/umd/fecs.js→ Development build with debug assertionsfecs/umd/fecs.min.js→ Minified production build, without debug assertions
import {
ComponentData,
TagComponent,
System,
World,
number,
queries,
ref
} from 'ecstra';
/**
* Components definition.
*/
class Position2D extends ComponentData {
@number()
x!: number;
@number()
y!: number;
}
class FollowTarget extends ComponentData {
@ref()
target!: number;
@number(1.0)
speed!: number;
}
class PlayerTag extends TagComponent {}
class ZombieTag extends TagComponent {}
/**
* Systems definition.
*/
@queries({
// Select entities with all three components `ZombieTag`, `FollowTarget`, and
// `Position2D`.
zombies: [ZombieTag, FollowTarget, Position2D]
})
class ZombieFollowSystem extends System {
execute(delta: number): void {
this.queries.zombies.execute((entity) => {
const { speed, target } = entity.read(FollowTarget);
const position = entity.write(Position2D);
const deltaX = target.x - position.x;
const deltaY = target.y - position.y;
const len = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (len >= 0.00001) {
position.x += speed * delta * (deltaX / len);
position.y += speed * delta * (deltaY / len);
}
});
}
}
const world = new World().register(ZombieFollowSystem);
// Creates a player entity.
const playerEntity = world.create().add(PlayerTag).add(Position2D);
const playerPosition = playerEntity.read();
// Creates 100 zombies at random positions with a `FollowTarget` component that
// will make them follow our player.
for (let i = 0; i < 100; ++i) {
world.create()
.add(ZombieTag)
.add(Position2D, {
x: Math.floor(Math.random() * 50.0) - 100.0,
y: Math.floor(Math.random() * 50.0) - 100.0
})
.add(FollowTarget, { target: playerPosition })
}
// Runs the animation loop and execute all systems every frame.
let lastTime = 0.0;
function loop() {
const currTime = performance.now();
const deltaTime = currTime - lastTime;
lastTime = currTime;
world.execute(deltaTime);
requestAnimationFrame(loop);
}
lastTime = performance.now();
loop();import {
ComponentData,
TagComponent,
NumberProp,
RefProp,
System,
World
} from 'ecstra';
/**
* Components definition.
*/
class Position2D extends ComponentData {}
Position2D.Properties = {
x: NumberProp(),
y: NumberProp()
};
class FollowTarget extends ComponentData {}
FollowTarget.Properties = {
target: RefProp(),
speed: NumberProp(1.0)
};
class PlayerTag extends TagComponent {}
class ZombieTag extends TagComponent {}
/**
* Systems definition.
*/
class ZombieFollowSystem extends System {
execute(delta) {
this.queries.zombies.execute((entity) => {
const { speed, target } = entity.read(FollowTarget);
const position = entity.write(Position2D);
const deltaX = target.x - position.x;
const deltaY = target.y - position.y;
const len = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (len >= 0.00001) {
position.x += speed * delta * (deltaX / len);
position.y += speed * delta * (deltaY / len);
}
});
}
}
ZombieFollowSystem.Queries = {
// Select entities with all three components `ZombieTag`, `FollowTarget`, and
// `Position2D`.
zombies: [ZombieTag, FollowTarget, Position2D]
}
const world = new World().register(ZombieFollowSystem);
// Creates a player entity.
const playerEntity = world.create().add(PlayerTag).add(Position2D);
const playerPosition = playerEntity.read();
// Creates 100 zombies at random positions with a `FollowTarget` component that
// will make them follow our player.
for (let i = 0; i < 100; ++i) {
world.create()
.add(ZombieTag)
.add(Position2D, {
x: Math.floor(Math.random() * 50.0) - 100.0,
y: Math.floor(Math.random() * 50.0) - 100.0
})
.add(FollowTarget, { target: playerPosition })
}
// Runs the animation loop and execute all systems every frame.
let lastTime = 0.0;
function loop() {
const currTime = performance.now();
const deltaTime = currTime - lastTime;
lastTime = currTime;
world.execute(deltaTime);
requestAnimationFrame(loop);
}
lastTime = performance.now();
loop();In order to try the examples, you need to build the library using:
yarn build # Alternatively, `yarn start` to watch the filesYou can then start the examples web server using:
yarn exampleTypeScript versions of the examples are available here. If you only want to see the example running, you can run the JS ones as they are identicial.
If you want to run the TypeScript examples themselves, please build the examples first:
yarn example:build # Alternatively, `yarn example:start` to watch the filesAnd then run the examples web server:
yarn exampleThe library is brand new and it's the perfect time for me to taylor it to match as much as possible most of the developer needs.
I want to open discussion about the following topics:
- Deferred creation and removal of components
- Deferred creation and removal of entities
- Command buffers
- Query system improvement
- New selector (
Modified?Removed?)
- New selector (
- Is a
StateComponentcomponent needed?
Please feel free to reach out directly in the Github Issues or contact me on Twitter to discuss those topics.
Coming soon.
For detailed information about how to contribute, please have a look at the CONTRIBUTING.md guide.
