An implementation of the Entity Component System (ECS) pattern used commonly in video games.
ECS is a way of organizing a system using composition instead of inheritance. It allows you to turn behaviors on and off by adding and removing components to entities.
This module manages the running of a list of "systems" over a collection of entities.
- An "entity" is a logical object in a game.
- A "component" is a chunk of data attached to an entity.
- A "system" is a function that runs on all entities with specific components.
The only way to make changes to an entity is to create/edit/delete components attached to it.
This is an example game loop:
var EntityComponentSystem = require("entity-component-system").EntityComponentSystem;
var EntityPool = require("entity-component-system").EntityPool;
var ecs = new EntityComponentSystem();
function drawBackground(entities, elapsed) { /* ... */ }
ecs.add(drawBackground);
function drawEntity(entity, elapsed) { /* ... */ }
ecs.addEach(drawEntity, "sprite"); // only run on entities with a "sprite" component
var entities = new EntityPool();
function spriteFactory() { return { "image": null }; }
entities.registerComponent("sprite", spriteFactory);
entities.load(/* some JSON */);
var lastTime = -1;
var render = function(time) {
if (this.lastTime === -1) {
this.lastTime = time;
}
var elapsed = time - this.lastTime;
this.lastTime = time;
ecs.run(entities, elapsed);
window.requestAnimationFrame(render);
};
window.requestAnimationFrame(render);An EntityComponentSystem holds the systems (code) and allows you to run
them on the entities inside an EntityPool.
Adds a "system" function to the ECS so it will be called once every time run
is called.
-
systemis a function that operates on all entities.systemhas the format:function mySystem(entityPool, elapsedTime) { /* ... */ }
entityPoolis theEntityPoolof entities to operate on.elapsedTimeis the elapsed time since the last call torun.
Adds a "system" function to the ECS so it will be called once for each entity
returned from EntityPool.find(search) in the EntityPool passed to run.
-
systemis a function that operates on a single entity matchingsearch.systemhas the format:function mySystem(entityId, elapsedTime) { /* ... */ }
entityIdis the id of an entity to operate on.elapsedTimeis the elapsed time since the last call torun.
-
searchis the name of a search that was previously registered withregisterSearch.systemis invoked once for every entity in the results ofsearch.
Invokes all systems in the order they were added to the EntityComponentSystem.
entityPoolis the collection of entities to operate on.elapsedTimeis the time passed since you last calledrun.
Returns the number of times run was called.
Returns an array of each system's name and time it ran in milliseconds. The
system names are gathered from the names of functions passed to add and
addEach. An example return value:
{
"drawBackground": 0.02,
"drawEntity": 5.00
}Resets the timing information and number of runs back to zero.
An EntityPool holds the entities and components for an
EntityComponentSystem. EntityPool provides ways to add, remove, modify, and
search for entities. EntityPool also has hooks where you can provide callbacks
to be notified of changes.
EntityPool also implements the Object Pool
pattern to reduce
stuttering caused by garbage collection.
Creates a new entity, and returns the entity's id.
var player = entities.create(); // => 1Removes all the components for an entity, and deletes the entity. The
onRemoveComponent callbacks are fired for each component that is removed.
idis the id of the entity to destroy.
entities.destroy(player);Registers a component type.
-
componentis the name of the component to register. -
factoryis a factory function which returns a newly allocated instance of the component. For example:function createPosition() { return { x: 0, y: 0 }; }
-
resetis an optional function which alters a previously used component instance to a clean state so it can be reused on a new entity. For example:function resetPosition(position) { position.x = 0; position.y = 0; }
-
sizeis an optional number of instances to allocate initially.
Adds a new component to an entity, and returns it. If the component is newly added,
the onAddComponent callbacks are fired. If the component already existed, it is reset.
idis the id of the entity to add the component to.componentis the name of the component to add.
var sprite = entities.addComponent(player, "sprite");
sprite.image = "something.png";Returns the component value for an entity.
idis the id of the entity to get the component from.componentis the name of the component to get.
var sprite = entities.getComponent(player, "sprite");
sprite.image = "something.png";Sets a primitive value for a component. To change a component that holds an
object, use getComponent instead.
idis the id of the entity to set the component on.componentis the name of the component to set.valueis the primitive value to set.
entities.setComponent(player, "health", 100);Removes a component from an entity. The onRemoveComponent callbacks are fired
for the removed component.
idis the id of the entity to remove the component from.componentis the name of the component to remove.
entities.removeComponent(player, "health");Registers a callback to be called when component is added to any entity.
callback looks like:
function myAddCallback(id, component, value) { /* ... */ }Registers a callback to be called when component is removed from any entity.
callback looks like:
function myRemoveCallback(id, component, removedValue) { /* ... */ }Registers a named search for entities that have all components listed in the
components array.
searchis the name of the search to register.componentsis an array of component names that an entity must possess to be included in the results.
entities.registerSearch("collectables", ["size", "collisions"]);Returns a list of entity ids for all entities that match the search. See
registerSearch.
searchis the name of the search previously registered withregisterSearch.
var collectables = entities.find("collectables"); // => [1, 2, 3, ...]Load entities into an entity pool from an array of objects. load should only
be used to fill an empty EntityPool.
-
entitiesis some JSON-compatible object returned bysave. The format looks like:[ { "id": 1, "componentName": "componentValue" }, { "id": 2, "componentName": "componentValue" } ]
Returns an object suitable for saving all entities in the EntityPool to a JSON
file. See load().
With npm do:
npm install --save entity-component-system
MIT