The world is the main class for Ape ECS . From there you can work with Components
, Entities
, Queries
, and Systems
. Your application can have more than one world, but all of your Entities
, Components
, Systems
, and Queries
will be specific to a given World
(although there are ways to clone things between worlds).
An instance of World
is essentially a registry of your game or simulation data, systems, and types. You've got to start with a world before you can do anything else. Typically you create your world, register your tags, components, and systems, and then start creating entities and running systems.
- World constructor
- currentTick
- tick
- registerComponent
- registerTags
- createEntity
- getObject
- createEntities
- copyTypes
- getEntity
- removeEntity
- getEntities
- getComponent
- createQuery
- registerSystem
- runSystems
- updateIndexes
Constructs a new World
instance.
const myWorld = new ApeECS.World({
trackChanges: true,
entityPool: 10,
cleanupPools: true,
useApeDestroy: true
});
- config:
Object
, optional- trackChanges:
bool
, defaulttrue
- entityPool:
Number
, default10
- cleanupPools:
bool
, defaulttrue
- useApeDestroy:
bool
, defaultfalse
:
- trackChanges:
Turning off trackChanges
removes the events that a System
can subscribe to.
The initial entityPool
spins up a given number Entity
instances for later use. Pooling configuration for Component
instances is done by indivual types at registration.
cleanupPools
shrinks the pool back down to between the set pool size and double the the pool size. Without it, the pool can be as large as the max number of entities or a given component type that has ever existed.
useApeDestroy
adds the ApeDestroy
tag and ApeCleanup
system group, which then runs at each tick().
If you want to destroy an Entity, but not until the end of the tick, just add the tag ApeDestroy
.
It keeps the entity around until the end, but it won't show up in Query.execute() results unless you set createQuery({ includeApeDestroy: true })
.
useApeDestroy
setup up a set of builtin behaviors for a common pattern.
const ApeECS = require('ape-ecs');
const myworld = new ApeECS.World({
trackChanges: true, // default
entityPool: 10 // default
});
The currentTick
is a Number integer property incremented by the world.tick()
method. It can be used to determine how recently Entities
and Components
have been updated based on their Entity.updatedComponents
and Component._meta.updated
values.
const q1 = world.createQuery().fromAll('Position', 'Tile');
const tiles = q1.execute({updatedComponents: world.currentTick - 1 });
world.tick();
Increments the world.currentTick
and manages any housekeeping that Ape ECS needs to do on the world between system runs. Once you've run through all of your Systems
you should world.tick()
before you do again.
Register a new Component
type in your World
.
class Position extends ApeECS.Component {
init() { // optional
this.coord = `${this.x}x${this.y}`;
}
preDestroy() { //optional
console.log('Boom?');
}
get coord() {
return `${this.x}x${this.y}`;
}
}
Position.properties = {
x: 0,
y: 0,
coord: '0x0',
parent: ApeECS.EntityRef
};
Position.serialize = true; // optional, default
Position.serializeFields = ['x', 'y']; // optional
// default is null, when serializeFields not specified, all properties are serialized
world.registerComponent(Position, 100);
- definition:
<ApeECS.Component>
- poolSize:
Number
, integer, default number of thisComponent
to create for a memory pool
Registers a new Component type to be used with world.createEntity()
or entity.addComponent()
.
☝️ If your Component doesn't need any properties or special options, register a Tag instead with world.registerTag()
.
👀 See the Component docs in learn how to properly extend ApeECS.Component
.
world.registerTags('MarkForDelete', 'Invisible', 'IsSandwich');
Registers any tags that you'll be using for your Entities.
- ...tags:
String
Tags are used like Components
, but they're only a string. You can check if entity.has('TagName')
just like you would a Component
name, and use them similarly in Queryies
. They're using for knowing if an Entity
"is" something, but you don't need any further properties to describe that aspect.
Create a new Entity, including any initial components (and their initial property values) or tags. It returns a new Entity
instance.
const playerEntity = world.createEntity({
id: 'Player', // optional
tags: ['Character', 'Visible'], //optional
components: [ // optional
{
type: 'Slot',
name: 'Left Hand',
slotType: 'holdable'
},
{
type: 'Slot',
name: 'Right Hand',
slotType: 'holdable'
},
{
type: 'Slot',
name: 'Body'
slotType: 'body'
},
],
c: { // optional
Controls: {
boundTo: 'keyboard',
keysDown: []
},
Position: {
x: 15,
y: 23
},
Inventory: {
type: 'Container' // specified type different than the key
size: 16
},
footSlot: {
type: 'Slot'
name: 'Feet'
slotType: 'shoes'
}
}
});
- definition
Object
, required- id:
String
, optional, unique identifier (generated if not specified) - tags:
[]String
, optional, any registered tags to include - components:
[]Object
, optional, initial components- type:
String
, required, registered component type - id:
String
, optional, unique identifier (generated if not specified) - key:
String
, optional, key value of the component instance in theEntity
. If not specified, tye component instance has no key value, and is only included in theSet
ofentity.components['ComponentType']
values. - *properties: initial values for defined properties of the component
- type:
- c:
Object
: _optional, Components indexed by a key value. Equivalant to specifying thekey
andtype
in thecomponents
array property.key
is thekey
value of the component instance. Also the component type if one is not specified.value
is anObject
defining the initial values of aComponent
type
:String
, optional, If not specified, thekey
key needs to be a registeredComponent
type.id
:String
, optional, unique identifier (generated if not specified)- *properties: initial values for defined properties of the component
- id:
👀 For more information on how keys work, see the Entity Docs.
☝️ The createEntity definition schema is the same one used by entity.getObject
and world.getObject
. As such, you can save and restore objects by saving the results of these methods and calling world.createEntity
with the same Object
to restore it.
💭 Ape ECS uses a very fast unique id generator for Components
and Entities
if you don't specify a given id upon creation. Look at the code in src/util.js.
Retrieves a serializable object that includes all of the Entities and their Components in the World. Returns an array of Entity definitions. See world.createEntity;
const saveState = world.getObject();
const jsonState = JSON.stringify(saveState);
Just like world.createEntity except that it takes an array of createEntity
definitions.
world.createEntities([
{
tags: ['Tile', 'Visible', 'New'],
components: [
{
type: 'Sprite':
texture: 'sprites/tiles/ground1.png'
}
],
c: {
Position: {
x: 0,
y: 0
}
}
},
{
tags: ['Tile', 'Visible', 'New'],
components: [
{
type: 'Sprite':
texture: 'sprites/tiles/ground1.png'
}
],
c: {
Position: {
x: 1,
y: 0
}
}
},
{
tags: ['Character', 'Player'],
id: 'Player'
c: {
Position: {
x: 0,
y: 0
}
}
}
]);
Copy registered Component types from another world to this one.
world.copyTypes(world2, types);
- world2:
World instance
, required - types:
[]String
, required, RegisteredComponent
types that you'd like to copy from world2
Get an Entity
instance by it's id
or undefined
.
const entity1 = world.getEntity('aabbccdd-1');
- id:
String
, required, The id of an existingEntity
instance.
Removes an Entity
instance from the world
, initializing its destroy()
.
- id/entity:
String
orEntity instance
of an existingEntity
.
☝️ Equivalent to:
world.getEntity(id).destroy();
Retrieve a Set
of all the Entities
that include a given Component
type or Tag.
const tiles = world.getEntities('Tile');
☝️ You could also do this with a Query
.
const q1 = world.createQuery().fromAll('Tile');
const tiles = q1.execute();
Get an Component
instance by its id
.
const component = world.getComponent('alakjds-123');
Arguments:
- id:
String
, required, unique id of a component
Factory that returns a new Query
instance.
const query1 = world.createQuery({ /* config */}).fromAll('Tile', 'Position');
const tiles = query1.execute({ /* filter */ });
👀 See the Query Docs to learn more about creating them and using them. Queries are a big part of Ape ECS and are fairly advanced.
☝️ You can also createQuery
from a System
. Systems
can persist Queries
which then act as an index to results that automatically stays up to date as Entity
composition changes.
Registers a System
class or instance with the world for later execution.
class Gravity extends ApeECS.System {}
world.registerSystem('movement', Gravity);
- group:
String
name for the group of Systems - system:
System class
orSystem instance
- initParams: optional array of arguments for system init()
System
instance.
You can have one, many, or all of your Systems
in a single "group."
You might have a "startup" group of Systems
that initializes a level, a "turn" group that runs for every user turn, and a "frame" group that runs for every rendered frame of animation.
If you always run all of your Systems
every time, you could just register them all with the same group.
If you want to be able to run any System
at an time in any order, you could register every System
with a unique group.
👀 See the System Docs for more information about how Systems
work.
Runs the added Systems
that were registered with that specific group, in order.
world.runSystems(group);
👀 See the System Docs for more information about running Systems
.
Get the World stats (currently pooling status)
const stats = world.getStats();
console.log(stats);
{
entity: { active: 23, pooled: 77, target: 100 },
SomeComponent: { active: 30 pooled: 20, target: 50 },
SomeOtherComponent: { active: 30 pooled: 0, target: 10 }
}
👆 When you registerComponent, the second argument is your target pool size.
👆 You can go over your target pool size, and if the world configuration of cleanupPools
is true (the default), any unused pooled items over double the target will be slowly decreased until somewhere between double and the target.
world.logStats(60, console.log);
3, Entity 23 active 77/100 pooled
3, SomeComponent 30 active 20/50 pooled
3, SomeOtherComponent 30 active 0/10 pooled
Arguments:
- freq: Number, required: How frequently in ticks to call the log
- callback: function, optional: function to call with stats logs, default: console.log
Method that updates persisted queries after system runs and upon ticks.
world.updateIndexes();
You can update the results of persisted queries for changed entities within a system update by calling this method. It won't do anything for non-system, non-persisted queries.