/ape-ecs

Entity-Component-System library for JavaScript.

Primary LanguageTypeScriptMIT LicenseMIT

Ape-ECS

Ape-ECS Hero

npm Build Status Coveralls github PRs Welcome

chat on discord @fritzy twitter

A performant, featureful, and flexible Entity-Component-System library for JavaScript, written in ECMAScript ES2018, intended for use in games and simulations.

Documentation

Install

npm install ape-ecs 

Differentiating Features

  • Advanced Queries for entities.
  • Persisted Queries (indexes) are updated as Entity composition changes.
  • Component reference properties to Entities (EntityRef, EntitySet, EntityObject)
    • When a referenced entity is destroyed, the property is updated to null.
    • Subscribe-able events for adding and removing references.
    • Reverse query from entity to entity-components that reference it.
  • Not all systems need to run every frame.
  • Export/import support for saving/restoring state with component-level serialization configuration.
  • 100% Test Coverage.

Example

const ApeECS = require('ape-ecs');

class Gravity extends ApeECS.System {
  init() {
    this.mainQuery = this.createQuery().fromAll('Position', 'Physics');
  }

  update(tick) {
    const entities = this.mainQuery.execute();
    const frameInfo = this.world.getEntity('frame');
    for (const entity of entities) {
      const point = entity.getOne('Position');
      if (!entity.has('Vector')) {
        entity.addComponent({
          type: 'Vector',
          mx: 0,
          my: 0
        })
      }
      const vector = entity.getOne('Vector');
      vector.my += 9.807 * frame.time.deltaTime * .01;
      vector.update();
    }
  }
}

class Position extends ApeECS.Component {}
Position.properties = {
  x: 0,
  y: 0
};

class Vector extends ApeECS.Component {

  get speed() {
    return Math.sqrt(this.mx**2 + this.my**2);
  }
}
Vector.properties = {
  mx: 0,
  my: 0,
  speed: 0
};

class FrameInfo extends ApeECS.Component {}
FrameInfo.properties = {
  deltaTime: 0,
  deltaFrame: 0,
  time: 0
};

const world = new ApeECS.World();
world.registerComponent(Position);
world.registerComponent(Vectory);
world.registerComponent(FrameInfo);
world.registerTags('Physics');
world.registerSystem('frame', Gravity);

const frame = world.createEntity({
  id: 'frame',
  c: {
    time: {
      type: 'FrameInfo',
    }
  }
})

// see world.creatEntity and world.createEntities
// in docs/World.md for more details
world.registerSystem('frame', require('./move.js'));
world.createEntities(require('./saveGame.json'));

let lastTime = 0;

function update(time) {
  const delta = time - lastTime;
  time = lastTime;
  frame.time.update({
    time: time,
    deltaTime: delta,
    deltaFrame: delta / 16.667
  });
  world.runSystems('frame');
  // run update again the next browser render call
  // every 16ms or so
  window.requestAnimationFrame(update);
}
update(0);

More About ECS

The Entity-Component-System paradigm is great for managing dynamic objects in games and simulations. Instead of binding functionality to data through methods, entities are dynamically composed of any combination of types. Separate systems are then able to query for entities with a given set of types.

ECS's dynamic data composition and freely interacting systems leads to:

  • More complex and dynamic composition than OOP
  • Improved performance due to lack of API methods
  • Emergent Gameplay with logical behavior extended beyond the programmer's vision.

This library has been inspired in part by:

Example Game

An in-progress arcade game Missile Orders.

This game is not in a complete state yet, nor does it show off all of the potential of ECS yet.

Running the Tests

The goal is to keep test coverage at 100%.

git clone git@github.com/fritzy/ape-ecs.git
cd ape-ecs
npm install
npm test

Contributors

  • Ben Morse -- Ben is an early adopter that provided a lot of insight and you have him to thank for the TypeScript definitions! Ben has a game, Super Game of Life that uses Ape ECS.

Special Thanks