@recs/core
v0.0.12
Published
recs is _Reminiscent [of an] Entity Component System_
Downloads
4
Readme
recs 🦖
recs is Reminiscent [of an] Entity Component System
Under active alpha development! Minor version bumps will have breaking changes. Stable release coming soon :)
- 🚀 Aims to provide a simple but performant API
- 💪 Flexible and extensible
- 🗑️ Avoids garbage collection without making you think too hard
- 🪶 The core library has zero dependencies
- 🖇 Easy integration with React -
@recs/react
Packages
@recs/core
The core library!
> yarn add @recs/core
@recs/react
React glue for @recs/core
> yarn add @recs/react
Introduction
As mentioned above, recs
is an object based Entity Component System. You can use recs
to structure games and other demanding applications.
If you don't know what an Entity Component System is, have a wikipedia article: https://en.wikipedia.org/wiki/Entity_component_system
Getting Started
Let's use RECS to make a dirt simple random walk simulation!
1. Import everything we need
import { Component, Query, System, World } from "@recs/core";
2. Create a few simple components to store some data
First, let's create a few components. We'll create:
- a component that stores a position
- a component that stores a color
- a component that stores the html canvas context
class Position extends Component {
// * note the not null `!:` syntax! *
// It is recommended that components use this to indicate those properties
// will be be initialised late, but at time of construction will be defined.
x!: number;
y!: number;
// * `recs` pools component objects for you! *
// Think of the `construct` method as a `constructor`.
// This method will be called every time a new component is being created or re-used
construct(x: number, y: number) {
this.x = x;
this.y = y;
}
}
class Color extends Component {
color!: "red" | "blue";
construct(color: "red" | "blue") {
this.color = color;
}
}
class CanvasContext extends Component {
ctx!: CanvasRenderingContext2D;
width!: number;
height!: number;
}
3. Create a System
that looks for entities with the Position
and Color
components and draws them!
const BOX_SIZE = 10;
class DrawSystem extends System {
// A `System` can have many queries for entities, filtering by what components they have
// we want to get the canvas context
context = this.query([CanvasContext]);
// we want to find entities with a Position and Color
toDraw = this.query({
all: [Position, Color],
});
// On each update, let's draw
onUpdate() {
// get the first entity from our canvas context query
const context = this.context.first!.get(CanvasContext);
const ctx = context.ctx;
// clear the canvas
ctx.clearRect(0, 0, context.width, context.height);
const xOffset = context.width / 2;
const yOffset = context.height / 2;
// the results of the `toDraw` query are available under `this.toDraw.entities`
for (const entity of this.toDraw.entities) {
// let's get the position of the random walker
const { x, y } = entity.get(Position);
// let's also get the color for this random walker
const { color } = entity.get(Color);
// draw the box
ctx.fillStyle = color;
ctx.fillRect(
xOffset + (x - BOX_SIZE / 2),
yOffset + (y - BOX_SIZE / 2),
BOX_SIZE,
BOX_SIZE
);
}
}
}
4. Create a system that makes our random walkers walk
class WalkSystem extends System {
// query for walkers
walkers = this.query({
all: [Position],
});
// keep track of when our walkers should move again
movementCountdown = WalkSystem.timeBetweenMovements;
// our random walkers should move every 0.05s
static timeBetweenMovements = 0.05;
onUpdate(delta: number) {
// count down until walkers should move again
this.movementCountdown -= delta;
// if it's time for entities to move again
if (this.movementCountdown <= 0) {
// move all walkers in a random direction
for (const entity of this.walkers.entities) {
const position = entity.get(Position)
position.x = position.x + (Math.random() - 0.5) * 3;
position.y = position.y + (Math.random() - 0.5) * 3;
}
// reset the countdown
this.movementCountdown = WalkSystem.timeBetweenMovements;
}
}
}
5. Bringing it all together
First, create a new recs World
:
const world = new World();
Next, let's register the components and systems we created:
// register components
world.registerComponent(Position);
world.registerComponent(Color);
world.registerComponent(CanvasContext);
// register systems
world.registerSystem(WalkSystem);
world.registerSystem(DrawSystem);
world.registerSystem(FlipSystem);
Now let's create some random walkers. These will be entities with the Position
and Color
components.
// create 100 random walkers!
const n = 100;
for (let i = 0; i < n; i++) {
const entity = world.create.entity();
entity.add(Position, Math.random() * 10 - 5, Math.random() * 10 - 5);
entity.add(Color, i % 2 === 0 ? "red" : "blue");
}
Next we'll create an entity with the CanvasContext
component, which will contain the HTML canvas context. We'll also add a handler for window resizing.
// create an entity with the CanvasContext component
const context = world.create.entity();
const canvas = document.querySelector("#example-canvas") as HTMLCanvasElement;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const canvasComponent = context.add(CanvasContext);
canvasComponent.ctx = canvas.getContext("2d")!;
canvasComponent.width = canvas.width;
canvasComponent.height = canvas.height;
// handle resizing
window.addEventListener(
"resize",
() => {
canvasComponent.width = canvas.width = window.innerWidth;
canvasComponent.height = canvas.height = window.innerHeight;
},
false
);
6. The loop
Finally, let's create a loop to run our simulation!
world.init();
let lastTime = performance.now() / 1000;
function update() {
const time = performance.now() / 1000;
const delta = time - lastTime;
lastTime = time;
world.update(delta);
requestAnimationFrame(update);
}