项目作者: andyhall

项目描述 :
A light, fast Entity Component System in JS
高级语言: JavaScript
项目地址: git://github.com/andyhall/ent-comp.git
创建时间: 2016-03-03T08:07:38Z
项目社区:https://github.com/andyhall/ent-comp

开源协议:

下载


ent-comp

A light, fast entity-component system in JS with no dependencies.

Overview

An Entity Component System
(ECS) is a programming construct that solves a very common
problem in gamedev. It lets you easily model dynamic systems
where the entities are difficult to describe with OO-style inheritance.

This library is the distilled result of my playing with a bunch of ECS libraries,
removing what wasn’t useful, and rejiggering what remained to perform well in the
most important cases. Specifically it’s tuned to be fast at accessing the state
of a given entity/component, and looping over all states for a given component.

To get started, check the usage examples below, or the API reference.

Installation:

To use as a dependency:

  1. npm install ent-comp

To hack on it:

  1. git clone https://github.com/fenomas/ent-comp.git
  2. cd ent-comp
  3. npm install
  4. npm test # run tests
  5. npm run bench # run benchmarks
  6. npm run doc # rebuild API docs

API reference:

See api.md.

Basic usage:

Create the ECS, entities, and components thusly:

  1. var EntComp = require('ent-comp')
  2. var ecs = new EntComp()
  3. // Entities are simply integer IDs:
  4. var playerID = ecs.createEntity() // 1
  5. var monsterID = ecs.createEntity() // 2
  6. // components are defined with a definition object:
  7. ecs.createComponent({
  8. name: 'isPlayer'
  9. })
  10. // component definitions can be accessed by name
  11. ecs.components['isPlayer'] // returns the definition object

Once you have some entities and components, you can add them, remove them, and check their existence:

  1. ecs.addComponent(playerID, 'isPlayer')
  2. ecs.hasComponent(playerID, 'isPlayer') // true
  3. ecs.removeComponent(playerID, 'isPlayer')
  4. ecs.hasComponent(playerID, 'isPlayer') // false
  5. // when creating an entity you can pass in an array of components to add
  6. var id = ecs.createEntity([ 'isPlayer', 'other-component' ])

The trivial example above implements a stateless component, which can be used like a boolean flag for entities. But most real components will also need to manage some data for each entity. This is done by giving the component a state object, and using the #getState method.

  1. // createComponent returns the component name, for convenience
  2. var locationComp = ecs.createComponent({
  3. name: 'location',
  4. state: { x:0, y:0, z:0 },
  5. })
  6. // give the player entity a location component
  7. ecs.addComponent(playerID, locationComp)
  8. // grab its state to update its data
  9. var loc = ecs.getState(playerID, locationComp)
  10. loc.y = 37
  11. // you can also pass in initial state values when adding a component:
  12. ecs.addComponent(monsterID, locationComp, { y: 42 })
  13. ecs.getState(monsterID, locationComp) // { x:0, y:42, z:0 }

When a component is added to an entity, its state object is automatically
populated with an __id property denoting the entity’s ID.

  1. loc.__id // same as playerID

Components can also have onAdd and onRemove properties, which get called
as any entity gains or loses the component.

  1. ecs.createComponent({
  2. name: 'orientation',
  3. state: { angle:0 },
  4. onAdd: (id, state) => {
  5. // initialize to a random direction
  6. state.angle = 360 * Math.random()
  7. },
  8. onRemove: (id, state) => {
  9. console.log('orientation removed from entity', id)
  10. }
  11. })

Finally, components can define system and/or renderSystem functions.
When your game ticks or renders, call the appropriate library methods,
and each component system function will get passed a list of state objects
for all the entities that have that component.

Components can also define an order property (default 99), to specify the order in which systems fire (lowest to highest).

  1. ecs.createComponent({
  2. name: 'hitPoints',
  3. state: { hp: 100 },
  4. order: 10,
  5. system: function(dt, states) {
  6. // states is an array of entity state objects
  7. states.forEach(state => {
  8. if (state.hp <= 0) console.log('Dead entity!', state.__id)
  9. })
  10. },
  11. renderSystem: function(dt, states) {
  12. states.forEach(state => {
  13. var id = state.__id
  14. var hp = state.hp
  15. drawTheEntityHitpoints(id, hp)
  16. })
  17. },
  18. })
  19. // calling tick/render triggers the systems
  20. ecs.tick( tick_time )
  21. ecs.render( render_time )

See the API reference for details on each method.

Multi-components

This library now supports multi components, where a component can be added to the same entity multiple times. Each addition creates a separate state object.

API may change someday, but for now all ECS methods that normally
return a state object instead return an array of state objects.
Calling removeComponent will remove all multi-component instances for
that entity, and there’s a new removeMultiComponent(id, name, index)
API to remove them individually (by their index in the array).

In practice it looks like this:

  1. ecs.createComponent({
  2. name: 'buff',
  3. multi: true, // this marks the component as multi
  4. state: { buffName: '', duration: 100 },
  5. system: function(dt, stateLists) {
  6. // stateLists is the array of all ent/comp pairs
  7. stateLists.forEach(statesArr => {
  8. // statesArr is an array of multi components for this entity
  9. statesArr.forEach((state, i) => {
  10. // update the state of this particular entity's buff
  11. state.duration -= dt
  12. if (state.duration < 0) {
  13. ecs.removeMultiComponent(state.__id, 'buff', i)
  14. }
  15. })
  16. })
  17. },
  18. })

Further usage:

If you need to query certain components many times each frame, you can create
bound accessor functions to get the existence or state of a given component.
These accessors are moderately faster than getState and hasComponent.

  1. var hasLocation = ecs.getComponentAccessor('location')
  2. hasLocation(entityID) // true or false
  3. var getLocation = ecs.getStateAccessor('location')
  4. getLocation(entityID) // returns a state object

There’s also an API for getting an array of all state objects for a given component.

  1. var states = ecs.getStatesList('hitPoints')
  2. // returns the same array that gets passed to `system` functions

Caveat about complex state objects:

When you add a component to an entity, a new state object is created for that ent/comp pair. This new state object is a shallow copy of the component’s default state, not a duplicate or deep clone. This means any non-primitive state properties will be copied by reference.

What this means to you is, state objects containing nested objects or arrays
probably won’t do what you intended. For example:

  1. ecs.createComponent({
  2. name: 'foo',
  3. state: {
  4. vector3: [0,0,0]
  5. }
  6. })

If you define a component that way, all its state objects will contain references to the same array. You probably want each to have its own, which you can do by initializing them in the onAdd handler:

  1. ecs.createComponent({
  2. name: 'foo',
  3. state: {
  4. vector3: null
  5. },
  6. onAdd: function(id, state) {
  7. if (!state.vector3) state.vector3 = [0,0,0]
  8. }
  9. })

Testing for the value before overwriting means that you can pass in an initial
value when adding the component, and it will still do what you expect:

  1. ecs.addComponent(id, 'foo', { vector3: [1,1,1] })

Things this library doesn’t do:

  1. Assemblages. I can’t for the life of me see how they add any value.
    If I’m missing something please file an issue.

  2. Provide any way of querying which entities have components A and B, but not C, and so on. If you need this, I think maintaining your own lists will be faster
    (and probably easier to use) than anything the library could do automatically.

Change list

  • 0.10.0
    • Changes internals such that removals and deletions are handled immediately. Removes the need for immediately arguments on such methods.
  • 0.9.0
    • Adds order property to component definitions
  • 0.7.0
    • Internals rebuilt and bugs fixed, should be no API changes
  • 0.6.0
    • removeComponent changed to be deferred by default
    • removeComponentLater removed
    • Adds multi-tagged components, and removeMultiComponent
    • Doubles performance of hasComponent and getState (for some reason..)

Author: https://github.com/fenomas

License: MIT