@recs/core
TypeScript icon, indicating that this package has built-in type declarations

0.0.12 • Public • Published

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

Version

The core library!

> yarn add @recs/core

@recs/react

Version

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);
}

Readme

Keywords

none

Package Sidebar

Install

npm i @recs/core

Weekly Downloads

0

Version

0.0.12

License

MIT

Unpacked Size

92.9 kB

Total Files

28

Last publish

Collaborators

  • isaacmason