]> git.r.bdr.sh - rbdr/serpentity/blame - dist/serpentity.js
Merge branch 'release/1.0.0'
[rbdr/serpentity] / dist / serpentity.js
CommitLineData
1bd778d2 1'use strict';
a2949b6a
BB
2
3/*
4Serpentity is a simple entity framework inspired by Ash.
5
6Usage:
7
1bd778d2 8 let Serpentity = require('serpentity');
a2949b6a
BB
9
10## Instantiating an engine
11
1bd778d2 12 let engine = Serpentity();
a2949b6a
BB
13
14Add entities or systems, systems are added with a priority (the smaller
15the number, the earlier it will be called):
16
17 engine.addEntity(entityFactory());
18 engine.addSystem(new GameSystem(), priority);
19
20Update all systems:
21
22 engine.update(dt);
23
24Remove entities or systems:
25
26 engine.removeEntity(entityReference);
27 engine.removeSystem(systemReference);
28
29## Creating Entities
30
31Entities are the basic object of Serpentity, and they do nothing.
32
1bd778d2 33 let entity = new Serpentity.Entity();
a2949b6a
BB
34
35All the behavior is added through components
36
37## Creating Components
38
39Components define data that we can add to an entity. This data will
40eventually be consumed by "Systems"
41
1bd778d2
BB
42 let PositionComponent = class PositionComponent extends Serpentity.Component {
43 constructor (config) {
44 this.x = 0;
45 this.y = 0;
46
47 super(config);
48 }
49 };
a2949b6a
BB
50
51You can add components to entities by using the add method:
52
509e372f 53 entity.addComponent(new PositionComponent());
a2949b6a
BB
54
55
56Systems can refer to entities by requesting nodes.
57
58## Working with Nodes
59
60Nodes are sets of components that you define, so your system can require
61entities that always follow the API defined in the node.
62
1bd778d2
BB
63 let MovementNode = class MovementNode extends Serpentity.Node;
64 MovementNode.position = PositionComponent;
65 MovementNode.motion = MotionComponent;
a2949b6a
BB
66
67You can then request an array of all the nodes representing entities
68that comply with that API
69
70 engine.getNodes(MovementNode);
71
72## Creating Systems
73
74Systems are called on every update, and they use components through nodes.
75
1bd778d2
BB
76 let TestSystem = class TestSystem extends Serpentity.System {
77 added (engine){
78 this.nodeList = engine.getNodes(MovementNode);
79 },
80 removed (engine){
81 this.nodeList = undefined;
82 }
83 update (dt){
84 let node;
85 for (node of this.nodeList) {
86 console.log(`Current position is: ${node.position.x},${node.position.y}`);
a2949b6a 87 }
1bd778d2
BB
88 }
89 };
a2949b6a
BB
90
91## That's it
92
93Just run `engine.update(dt)` in your game loop :D
94
95*/
1bd778d2 96
8af8134e
BB
97var _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; }; }();
98
99function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
100
101var Serpentity = function () {
102 function Serpentity(config) {
103 _classCallCheck(this, Serpentity);
104
1bd778d2
BB
105 this.systems = [];
106 this.entities = [];
107 this._nodeCollections = [];
108 this._nodeCollectionKeys = [];
109
110 Object.assign(this, config || {});
111 }
112
113 /*
114 * Adds a system to the engine, so its update method will be called
115 * with the others. Triggers added hook.
116 *
117 * returns true if added succesfully, false if already added
118 */
1bd778d2 119
1bd778d2 120
8af8134e
BB
121 _createClass(Serpentity, [{
122 key: 'addSystem',
123 value: function addSystem(system, priority) {
124 var lastIndex = void 0,
125 found = void 0;
1bd778d2 126
8af8134e
BB
127 if (this.systems.indexOf(system) >= 0) {
128 return false;
1bd778d2 129 }
1bd778d2 130
8af8134e 131 system.priority = priority;
1bd778d2 132
8af8134e
BB
133 found = false;
134 lastIndex = 0;
1bd778d2 135
8af8134e
BB
136 this.systems.some(function findPriority(existingSystem, i) {
137 lastIndex = i;
138 if (existingSystem.priority >= system.priority) {
139 found = true;
140 return true;
141 }
142 });
143
144 if (!found) {
145 lastIndex += 1;
146 }
1bd778d2 147
8af8134e
BB
148 this.systems.splice(lastIndex, 0, system);
149 system.added(this);
1bd778d2
BB
150 return true;
151 }
152
8af8134e
BB
153 /*
154 * Removes a system from the engine, so its update method will no
155 * longer will be called. Triggers the removed hook.
156 *
157 * returns true if removed succesfully, false if already added
158 */
159
160 }, {
161 key: 'removeSystem',
162 value: function removeSystem(system) {
163 var position = void 0;
164
165 position = this.systems.indexOf(system);
166 if (position >= 0) {
167 this.systems[position].removed(this);
168 this.systems.splice(position, 1);
169 return true;
170 }
1bd778d2 171
1bd778d2
BB
172 return false;
173 }
1bd778d2 174
8af8134e
BB
175 /*
176 * Adds an entity to the engine, adds to existing node collections
177 *
178 * returns true if added, false if already there
179 */
180
181 }, {
182 key: 'addEntity',
183 value: function addEntity(entity) {
184 if (this.entities.indexOf(entity) >= 0) {
185 return false;
186 }
187 this.entities.push(entity);
1bd778d2 188
1bd778d2 189 this._nodeCollections.forEach(function (collection) {
8af8134e 190 collection.add(entity);
1bd778d2
BB
191 });
192
1bd778d2 193 return true;
a2949b6a 194 }
1bd778d2 195
8af8134e
BB
196 /*
197 * Removes entity from system, removing from all node collections
198 *
199 * returns true if removed, false if not present
200 */
1bd778d2 201
8af8134e
BB
202 }, {
203 key: 'removeEntity',
204 value: function removeEntity(entity) {
205 var position = void 0;
206
207 position = this.entities.indexOf(entity);
208 if (position >= 0) {
209 this._nodeCollections.forEach(function (collection) {
210 collection.remove(entity);
211 });
1bd778d2 212
8af8134e
BB
213 this.entities.splice(position, 1);
214 return true;
215 }
1bd778d2 216
8af8134e 217 return false;
1bd778d2
BB
218 }
219
8af8134e
BB
220 /*
221 * Given a Node Class, retrieves a list of all the nodes for each
222 * applicable entity.
223 */
1bd778d2 224
8af8134e
BB
225 }, {
226 key: 'getNodes',
227 value: function getNodes(nodeType) {
228 var position = void 0,
229 nodeCollection = void 0;
1bd778d2 230
8af8134e 231 position = this._nodeCollectionKeys.indexOf(nodeType);
1bd778d2 232
8af8134e
BB
233 if (position >= 0) {
234 return this._nodeCollections[position].nodes;
235 }
1bd778d2 236
8af8134e
BB
237 nodeCollection = new Serpentity.NodeCollection({
238 type: nodeType
239 });
240
241 this._nodeCollectionKeys.push(nodeType);
242 this._nodeCollections.push(nodeCollection);
243
244 this.entities.forEach(function (entity) {
245 nodeCollection.add(entity);
246 });
247
248 return nodeCollection.nodes;
249 }
250
251 /*
252 * Calls update for every loaded system.
253 */
254
255 }, {
256 key: 'update',
257 value: function update(dt) {
258 this.systems.forEach(function (system) {
259 system.update(dt);
260 });
261 }
262 }]);
263
264 return Serpentity;
265}();
1bd778d2
BB
266
267// Add namespaced objects.
8af8134e 268if (typeof module !== 'undefined' && undefined.module !== module) {
1bd778d2
BB
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');
274
275 module.exports = Serpentity;
a2949b6a 276}
1bd778d2
BB
277'use strict';
278
279/* global Serpentity */
280
a2949b6a
BB
281/*
282 * The entity gives the entity framework its name. It exists only
283 * to hold components.
284 */
1bd778d2 285
8af8134e
BB
286var _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; }; }();
287
288function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
289
290var Entity = function () {
291 function Entity(config) {
292 _classCallCheck(this, Entity);
293
1bd778d2
BB
294 this._componentKeys = [];
295 this._components = [];
296
297 Object.assign(this, config || {});
298 }
299
300 /*
301 * Adds a component to the entity.
302 *
303 * returns true if added, false if already present
304 */
1bd778d2 305
8af8134e
BB
306
307 _createClass(Entity, [{
308 key: 'addComponent',
309 value: function addComponent(component) {
310 if (this._componentKeys.indexOf(component.constructor) >= 0) {
311 return false;
312 }
313 this._componentKeys.push(component.constructor);
314 this._components.push(component);
1bd778d2
BB
315 return true;
316 }
1bd778d2 317
8af8134e
BB
318 /*
319 * returns true if component is included, false otherwise
320 */
321
322 }, {
323 key: 'hasComponent',
324 value: function hasComponent(componentClass) {
325 if (this._componentKeys.indexOf(componentClass) >= 0) {
326 return true;
327 }
328 return false;
1bd778d2 329 }
1bd778d2 330
8af8134e
BB
331 /*
332 * returns the component associated with that key
333 */
334
335 }, {
336 key: 'getComponent',
337 value: function getComponent(componentClass) {
338 var position = this._componentKeys.indexOf(componentClass);
339 if (position >= 0) {
340 return this._components[position];
341 }
342 }
343 }]);
344
345 return Entity;
346}();
347
348if (typeof module !== 'undefined' && undefined.module !== module) {
1bd778d2
BB
349 module.exports = Entity;
350} else {
351 Serpentity.Entity = Entity;
352}
1bd778d2
BB
353'use strict';
354
355/* global Serpentity */
a2949b6a
BB
356
357/*
358 * A node describes a set of components in order to describe entities
359 * that include them.
360 */
a2949b6a 361
8af8134e 362var _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; }; }();
a2949b6a 363
8af8134e 364function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
a2949b6a 365
8af8134e
BB
366var Node = function () {
367 _createClass(Node, null, [{
368 key: 'matches',
a2949b6a 369
8af8134e
BB
370
371 /*
372 * Returns true if the given entity matches the defined protocol,
373 * false otherwise
374 */
375 value: function matches(entity) {
376 var types = this.types;
377
378 for (var typeName in types) {
379 if (types.hasOwnProperty(typeName)) {
380
381 var matched = false;
382 var type = types[typeName];
383
384 if (entity.hasComponent(type)) {
385 matched = true;
386 }
387
388 if (!matched) {
389 return false;
390 }
1bd778d2
BB
391 }
392 }
8af8134e
BB
393
394 return true;
1bd778d2 395 }
8af8134e 396 }]);
a2949b6a 397
8af8134e
BB
398 function Node(config) {
399 _classCallCheck(this, Node);
a2949b6a 400
1bd778d2 401 this.types = {};
a2949b6a 402
1bd778d2
BB
403 Object.assign(this, config || {});
404 }
a2949b6a 405
8af8134e
BB
406 return Node;
407}();
408
409if (typeof module !== 'undefined' && undefined.module !== module) {
1bd778d2
BB
410 module.exports = Node;
411} else {
412 Serpentity.Node = Node;
413}
1bd778d2 414'use strict';
a2949b6a 415
1bd778d2 416/* global Serpentity */
a2949b6a
BB
417
418/*
419 * Node Collections contain nodes, in order to keep the lists of nodes
420 * that belong to each type.
421 *
422 * It has a type which is the class name of the node, and an array of
423 * instances of that class.
424 */
1bd778d2 425
8af8134e
BB
426var _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; }; }();
427
428function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
429
430var NodeCollection = function () {
431 function NodeCollection(config) {
432 _classCallCheck(this, NodeCollection);
1bd778d2 433
1bd778d2
BB
434 this.nodes = [];
435 this.type = null;
436
437 Object.assign(this, config || {});
438 }
439
440 /*
441 * Creates a node for an entity if it matches, and adds it to the
442 * node list.
443 *
444 * Returns true if added, false otherwise.
445 */
1bd778d2 446
1bd778d2 447
8af8134e
BB
448 _createClass(NodeCollection, [{
449 key: 'add',
450 value: function add(entity) {
1bd778d2 451
8af8134e
BB
452 if (this.type.matches(entity) && !this._entityExists(entity)) {
453
454 var node = new this.type({});
455 var types = this.type.types;
456
457 node.entity = entity;
458
459 for (var typeName in types) {
460 if (types.hasOwnProperty(typeName)) {
461 node[typeName] = entity.getComponent(types[typeName]);
462 }
a2949b6a 463 }
1bd778d2 464
8af8134e 465 this.nodes.push(node);
1bd778d2 466
8af8134e
BB
467 return true;
468 }
469
470 return false;
1bd778d2
BB
471 }
472
8af8134e
BB
473 /*
474 * Removes an entity by removing its related node from the list of nodes
475 *
476 * returns true if it was removed, false otherwise.
477 */
1bd778d2 478
8af8134e
BB
479 }, {
480 key: 'remove',
481 value: function remove(entity) {
482 var found = -1;
1bd778d2 483
8af8134e
BB
484 this.nodes.forEach(function (node, i) {
485 if (node.entity === entity) {
486 found = i;
487 }
488 });
489
490 if (found >= 0) {
491 this.nodes.splice(found, 1);
492 return true;
1bd778d2 493 }
1bd778d2 494
8af8134e 495 return false;
1bd778d2
BB
496 }
497
8af8134e
BB
498 /*
499 * Checks whether we already have nodes for this entity.
500 */
1bd778d2 501
8af8134e
BB
502 }, {
503 key: '_entityExists',
504 value: function _entityExists(entity) {
505 var found = false;
506
507 var _iteratorNormalCompletion = true;
508 var _didIteratorError = false;
509 var _iteratorError = undefined;
510
511 try {
512 for (var _iterator = this.nodes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
513 var node = _step.value;
1bd778d2 514
8af8134e
BB
515 if (node.entity === entity) {
516 found = true;
517 }
518 }
519 } catch (err) {
520 _didIteratorError = true;
521 _iteratorError = err;
522 } finally {
523 try {
524 if (!_iteratorNormalCompletion && _iterator.return) {
525 _iterator.return();
526 }
527 } finally {
528 if (_didIteratorError) {
529 throw _iteratorError;
530 }
531 }
1bd778d2 532 }
8af8134e
BB
533
534 return found;
a2949b6a 535 }
8af8134e 536 }]);
1bd778d2 537
8af8134e
BB
538 return NodeCollection;
539}();
1bd778d2 540
8af8134e 541if (typeof module !== 'undefined' && undefined.module !== module) {
1bd778d2
BB
542 module.exports = NodeCollection;
543} else {
544 Serpentity.NodeCollection = NodeCollection;
545}
1bd778d2
BB
546'use strict';
547
548/* global Serpentity */
a2949b6a
BB
549
550/*
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.
555 */
1bd778d2 556
8af8134e
BB
557function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
558
559var Component = function Component(config) {
560 _classCallCheck(this, Component);
561
562 Object.assign(this, config || {});
1bd778d2
BB
563};
564
8af8134e 565if (typeof module !== 'undefined' && undefined.module !== module) {
1bd778d2
BB
566 module.exports = Component;
567} else {
568 Serpentity.Component = Component;
569}
1bd778d2
BB
570'use strict';
571
572/* global Serpentity */
a2949b6a
BB
573
574/*
575 * Systems contain most of the logic, and work with nodes in order to
576 * act and change their values.
577 *
578 * You usually want to inherit from this class and override the
1bd778d2 579 * three methods. They are shown here to document the interface.
a2949b6a 580 */
1bd778d2 581
8af8134e 582var _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; }; }();
1bd778d2 583
8af8134e
BB
584function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
585
586var System = function () {
587 function System() {
588 _classCallCheck(this, System);
589 }
590
591 _createClass(System, [{
592 key: 'added',
593
594
595 /*
596 * This will be run when the system is added to the engine
597 */
598 value: function added() {}
1bd778d2
BB
599 // Override with added(engine)
600 // Receives an instance of the serpentity engine
1bd778d2 601
8af8134e
BB
602
603 /*
604 * This will be run when the system is removed from the engine
605 */
606
607 }, {
608 key: 'removed',
609 value: function removed() {}
1bd778d2
BB
610 // Override with removed(engine)
611 // Receives an instance of the serpentity engine
1bd778d2 612
1bd778d2 613
8af8134e
BB
614 /*
615 * This will run every time the engine's update method is called
616 */
617
618 }, {
619 key: 'update',
620 value: function update() {
621 // Override with update(dt)
622 // Receives a delta of the time
623 }
624 }]);
625
626 return System;
627}();
628
629if (typeof module !== 'undefined' && undefined.module !== module) {
1bd778d2
BB
630 module.exports = System;
631} else {
632 Serpentity.System = System;
8af8134e 633}