4 Serpentity is a simple entity framework inspired by Ash.
8 let Serpentity = require('serpentity');
10 ## Instantiating an engine
12 let engine = Serpentity();
14 Add entities or systems, systems are added with a priority (the smaller
15 the number, the earlier it will be called):
17 engine.addEntity(entityFactory());
18 engine.addSystem(new GameSystem(), priority);
24 Remove entities or systems:
26 engine.removeEntity(entityReference);
27 engine.removeSystem(systemReference);
31 Entities are the basic object of Serpentity, and they do nothing.
33 let entity = new Serpentity.Entity();
35 All the behavior is added through components
37 ## Creating Components
39 Components define data that we can add to an entity. This data will
40 eventually be consumed by "Systems"
42 let PositionComponent = class PositionComponent extends Serpentity.Component {
43 constructor (config) {
51 You can add components to entities by using the add method:
53 entity.addComponent(new PositionComponent());
56 Systems can refer to entities by requesting nodes.
60 Nodes are sets of components that you define, so your system can require
61 entities that always follow the API defined in the node.
63 let MovementNode = class MovementNode extends Serpentity.Node;
64 MovementNode.position = PositionComponent;
65 MovementNode.motion = MotionComponent;
67 You can then request an array of all the nodes representing entities
68 that comply with that API
70 engine.getNodes(MovementNode);
74 Systems are called on every update, and they use components through nodes.
76 let TestSystem = class TestSystem extends Serpentity.System {
78 this.nodeList = engine.getNodes(MovementNode);
81 this.nodeList = undefined;
85 for (node of this.nodeList) {
86 console.log(`Current position is: ${node.position.x},${node.position.y}`);
93 Just run `engine.update(dt)` in your game loop :D
97 var _createClass = function () { function defineProperties(target
, props
) { for (var i
= 0; i
< props
.length
; i
++) { var descriptor
= props
[i
]; descriptor
.enumerable
= descriptor
.enumerable
|| false; descriptor
.configurable
= true; if ("value" in descriptor
) descriptor
.writable
= true; Object
.defineProperty(target
, descriptor
.key
, descriptor
); } } return function (Constructor
, protoProps
, staticProps
) { if (protoProps
) defineProperties(Constructor
.prototype, protoProps
); if (staticProps
) defineProperties(Constructor
, staticProps
); return Constructor
; }; }();
99 function _classCallCheck(instance
, Constructor
) { if (!(instance
instanceof Constructor
)) { throw new TypeError("Cannot call a class as a function"); } }
101 var Serpentity = function () {
102 function Serpentity(config
) {
103 _classCallCheck(this, Serpentity
);
107 this._nodeCollections
= [];
108 this._nodeCollectionKeys
= [];
110 Object
.assign(this, config
|| {});
114 * Adds a system to the engine, so its update method will be called
115 * with the others. Triggers added hook.
117 * returns true if added succesfully, false if already added
121 _createClass(Serpentity
, [{
123 value: function addSystem(system
, priority
) {
124 var lastIndex
= void 0,
127 if (this.systems
.indexOf(system
) >= 0) {
131 system
.priority
= priority
;
136 this.systems
.some(function findPriority(existingSystem
, i
) {
138 if (existingSystem
.priority
>= system
.priority
) {
148 this.systems
.splice(lastIndex
, 0, system
);
154 * Removes a system from the engine, so its update method will no
155 * longer will be called. Triggers the removed hook.
157 * returns true if removed succesfully, false if already added
162 value: function removeSystem(system
) {
163 var position
= void 0;
165 position
= this.systems
.indexOf(system
);
167 this.systems
[position
].removed(this);
168 this.systems
.splice(position
, 1);
176 * Adds an entity to the engine, adds to existing node collections
178 * returns true if added, false if already there
183 value: function addEntity(entity
) {
184 if (this.entities
.indexOf(entity
) >= 0) {
187 this.entities
.push(entity
);
189 this._nodeCollections
.forEach(function (collection
) {
190 collection
.add(entity
);
197 * Removes entity from system, removing from all node collections
199 * returns true if removed, false if not present
204 value: function removeEntity(entity
) {
205 var position
= void 0;
207 position
= this.entities
.indexOf(entity
);
209 this._nodeCollections
.forEach(function (collection
) {
210 collection
.remove(entity
);
213 this.entities
.splice(position
, 1);
221 * Given a Node Class, retrieves a list of all the nodes for each
227 value: function getNodes(nodeType
) {
228 var position
= void 0,
229 nodeCollection
= void 0;
231 position
= this._nodeCollectionKeys
.indexOf(nodeType
);
234 return this._nodeCollections
[position
].nodes
;
237 nodeCollection
= new Serpentity
.NodeCollection({
241 this._nodeCollectionKeys
.push(nodeType
);
242 this._nodeCollections
.push(nodeCollection
);
244 this.entities
.forEach(function (entity
) {
245 nodeCollection
.add(entity
);
248 return nodeCollection
.nodes
;
252 * Calls update for every loaded system.
257 value: function update(dt
) {
258 this.systems
.forEach(function (system
) {
267 // Add namespaced objects.
268 if (typeof module
!== 'undefined' && undefined.module
!== module
) {
269 Serpentity
.Component
= require('./serpentity/component.js');
270 Serpentity
.Entity
= require('./serpentity/entity.js');
271 Serpentity
.Node
= require('./serpentity/node.js');
272 Serpentity
.NodeCollection
= require('./serpentity/node_collection.js');
273 Serpentity
.System
= require('./serpentity/system.js');
275 module
.exports
= Serpentity
;
279 /* global Serpentity */
282 * The entity gives the entity framework its name. It exists only
283 * to hold components.
286 var _createClass = function () { function defineProperties(target
, props
) { for (var i
= 0; i
< props
.length
; i
++) { var descriptor
= props
[i
]; descriptor
.enumerable
= descriptor
.enumerable
|| false; descriptor
.configurable
= true; if ("value" in descriptor
) descriptor
.writable
= true; Object
.defineProperty(target
, descriptor
.key
, descriptor
); } } return function (Constructor
, protoProps
, staticProps
) { if (protoProps
) defineProperties(Constructor
.prototype, protoProps
); if (staticProps
) defineProperties(Constructor
, staticProps
); return Constructor
; }; }();
288 function _classCallCheck(instance
, Constructor
) { if (!(instance
instanceof Constructor
)) { throw new TypeError("Cannot call a class as a function"); } }
290 var Entity = function () {
291 function Entity(config
) {
292 _classCallCheck(this, Entity
);
294 this._componentKeys
= [];
295 this._components
= [];
297 Object
.assign(this, config
|| {});
301 * Adds a component to the entity.
303 * returns true if added, false if already present
307 _createClass(Entity
, [{
309 value: function addComponent(component
) {
310 if (this._componentKeys
.indexOf(component
.constructor) >= 0) {
313 this._componentKeys
.push(component
.constructor);
314 this._components
.push(component
);
319 * returns true if component is included, false otherwise
324 value: function hasComponent(componentClass
) {
325 if (this._componentKeys
.indexOf(componentClass
) >= 0) {
332 * returns the component associated with that key
337 value: function getComponent(componentClass
) {
338 var position
= this._componentKeys
.indexOf(componentClass
);
340 return this._components
[position
];
348 if (typeof module
!== 'undefined' && undefined.module
!== module
) {
349 module
.exports
= Entity
;
351 Serpentity
.Entity
= Entity
;
355 /* global Serpentity */
358 * A node describes a set of components in order to describe entities
362 var _createClass = function () { function defineProperties(target
, props
) { for (var i
= 0; i
< props
.length
; i
++) { var descriptor
= props
[i
]; descriptor
.enumerable
= descriptor
.enumerable
|| false; descriptor
.configurable
= true; if ("value" in descriptor
) descriptor
.writable
= true; Object
.defineProperty(target
, descriptor
.key
, descriptor
); } } return function (Constructor
, protoProps
, staticProps
) { if (protoProps
) defineProperties(Constructor
.prototype, protoProps
); if (staticProps
) defineProperties(Constructor
, staticProps
); return Constructor
; }; }();
364 function _classCallCheck(instance
, Constructor
) { if (!(instance
instanceof Constructor
)) { throw new TypeError("Cannot call a class as a function"); } }
366 var Node = function () {
367 _createClass(Node
, null, [{
372 * Returns true if the given entity matches the defined protocol,
375 value: function matches(entity
) {
376 var types
= this.types
;
378 for (var typeName
in types
) {
379 if (types
.hasOwnProperty(typeName
)) {
382 var type
= types
[typeName
];
384 if (entity
.hasComponent(type
)) {
398 function Node(config
) {
399 _classCallCheck(this, Node
);
403 Object
.assign(this, config
|| {});
409 if (typeof module
!== 'undefined' && undefined.module
!== module
) {
410 module
.exports
= Node
;
412 Serpentity
.Node
= Node
;
416 /* global Serpentity */
419 * Node Collections contain nodes, in order to keep the lists of nodes
420 * that belong to each type.
422 * It has a type which is the class name of the node, and an array of
423 * instances of that class.
426 var _createClass = function () { function defineProperties(target
, props
) { for (var i
= 0; i
< props
.length
; i
++) { var descriptor
= props
[i
]; descriptor
.enumerable
= descriptor
.enumerable
|| false; descriptor
.configurable
= true; if ("value" in descriptor
) descriptor
.writable
= true; Object
.defineProperty(target
, descriptor
.key
, descriptor
); } } return function (Constructor
, protoProps
, staticProps
) { if (protoProps
) defineProperties(Constructor
.prototype, protoProps
); if (staticProps
) defineProperties(Constructor
, staticProps
); return Constructor
; }; }();
428 function _classCallCheck(instance
, Constructor
) { if (!(instance
instanceof Constructor
)) { throw new TypeError("Cannot call a class as a function"); } }
430 var NodeCollection = function () {
431 function NodeCollection(config
) {
432 _classCallCheck(this, NodeCollection
);
437 Object
.assign(this, config
|| {});
441 * Creates a node for an entity if it matches, and adds it to the
444 * Returns true if added, false otherwise.
448 _createClass(NodeCollection
, [{
450 value: function add(entity
) {
452 if (this.type
.matches(entity
) && !this._entityExists(entity
)) {
454 var node
= new this.type({});
455 var types
= this.type
.types
;
457 node
.entity
= entity
;
459 for (var typeName
in types
) {
460 if (types
.hasOwnProperty(typeName
)) {
461 node
[typeName
] = entity
.getComponent(types
[typeName
]);
465 this.nodes
.push(node
);
474 * Removes an entity by removing its related node from the list of nodes
476 * returns true if it was removed, false otherwise.
481 value: function remove(entity
) {
484 this.nodes
.forEach(function (node
, i
) {
485 if (node
.entity
=== entity
) {
491 this.nodes
.splice(found
, 1);
499 * Checks whether we already have nodes for this entity.
503 key: '_entityExists',
504 value: function _entityExists(entity
) {
507 var _iteratorNormalCompletion
= true;
508 var _didIteratorError
= false;
509 var _iteratorError
= undefined;
512 for (var _iterator
= this.nodes
[Symbol
.iterator
](), _step
; !(_iteratorNormalCompletion
= (_step
= _iterator
.next()).done
); _iteratorNormalCompletion
= true) {
513 var node
= _step
.value
;
515 if (node
.entity
=== entity
) {
520 _didIteratorError
= true;
521 _iteratorError
= err
;
524 if (!_iteratorNormalCompletion
&& _iterator
.return) {
528 if (_didIteratorError
) {
529 throw _iteratorError
;
538 return NodeCollection
;
541 if (typeof module
!== 'undefined' && undefined.module
!== module
) {
542 module
.exports
= NodeCollection
;
544 Serpentity
.NodeCollection
= NodeCollection
;
548 /* global Serpentity */
551 * Components store data. Nothing to say here really, just
552 * inherit and add a prototype, or don't even inherit, see?
553 * It's just an empty class, so what I'm trying to say is your
554 * components can be any class whatsoever.
557 function _classCallCheck(instance
, Constructor
) { if (!(instance
instanceof Constructor
)) { throw new TypeError("Cannot call a class as a function"); } }
559 var Component
= function Component(config
) {
560 _classCallCheck(this, Component
);
562 Object
.assign(this, config
|| {});
565 if (typeof module
!== 'undefined' && undefined.module
!== module
) {
566 module
.exports
= Component
;
568 Serpentity
.Component
= Component
;
572 /* global Serpentity */
575 * Systems contain most of the logic, and work with nodes in order to
576 * act and change their values.
578 * You usually want to inherit from this class and override the
579 * three methods. They are shown here to document the interface.
582 var _createClass = function () { function defineProperties(target
, props
) { for (var i
= 0; i
< props
.length
; i
++) { var descriptor
= props
[i
]; descriptor
.enumerable
= descriptor
.enumerable
|| false; descriptor
.configurable
= true; if ("value" in descriptor
) descriptor
.writable
= true; Object
.defineProperty(target
, descriptor
.key
, descriptor
); } } return function (Constructor
, protoProps
, staticProps
) { if (protoProps
) defineProperties(Constructor
.prototype, protoProps
); if (staticProps
) defineProperties(Constructor
, staticProps
); return Constructor
; }; }();
584 function _classCallCheck(instance
, Constructor
) { if (!(instance
instanceof Constructor
)) { throw new TypeError("Cannot call a class as a function"); } }
586 var System = function () {
588 _classCallCheck(this, System
);
591 _createClass(System
, [{
596 * This will be run when the system is added to the engine
598 value: function added() {}
599 // Override with added(engine)
600 // Receives an instance of the serpentity engine
604 * This will be run when the system is removed from the engine
609 value: function removed() {}
610 // Override with removed(engine)
611 // Receives an instance of the serpentity engine
615 * This will run every time the engine's update method is called
620 value: function update() {
621 // Override with update(dt)
622 // Receives a delta of the time
629 if (typeof module
!== 'undefined' && undefined.module
!== module
) {
630 module
.exports
= System
;
632 Serpentity
.System
= System
;