ecstra v0.0.2
Ecstra
ð§ 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:
ð I am currently looking for people to help me to identify their needs in order to drive the development of this library further.
Philosophy
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.
Features
- 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
Install
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
Usage Example
TypeScript
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();JavaScript
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();Running Examples
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 exampleTS Examples
TypeScript 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 exampleStable Version
The 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.
Benchmarks
Coming soon.
Contributing
For detailed information about how to contribute, please have a look at the CONTRIBUTING.md guide.