]> git.r.bdr.sh - rbdr/serpentity/blob - dist/serpentity.js
Merge branch 'release/1.0.0'
[rbdr/serpentity] / dist / serpentity.js
1 'use strict';
2
3 /*
4 Serpentity is a simple entity framework inspired by Ash.
5
6 Usage:
7
8 let Serpentity = require('serpentity');
9
10 ## Instantiating an engine
11
12 let engine = Serpentity();
13
14 Add entities or systems, systems are added with a priority (the smaller
15 the number, the earlier it will be called):
16
17 engine.addEntity(entityFactory());
18 engine.addSystem(new GameSystem(), priority);
19
20 Update all systems:
21
22 engine.update(dt);
23
24 Remove entities or systems:
25
26 engine.removeEntity(entityReference);
27 engine.removeSystem(systemReference);
28
29 ## Creating Entities
30
31 Entities are the basic object of Serpentity, and they do nothing.
32
33 let entity = new Serpentity.Entity();
34
35 All the behavior is added through components
36
37 ## Creating Components
38
39 Components define data that we can add to an entity. This data will
40 eventually be consumed by "Systems"
41
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 };
50
51 You can add components to entities by using the add method:
52
53 entity.addComponent(new PositionComponent());
54
55
56 Systems can refer to entities by requesting nodes.
57
58 ## Working with Nodes
59
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.
62
63 let MovementNode = class MovementNode extends Serpentity.Node;
64 MovementNode.position = PositionComponent;
65 MovementNode.motion = MotionComponent;
66
67 You can then request an array of all the nodes representing entities
68 that comply with that API
69
70 engine.getNodes(MovementNode);
71
72 ## Creating Systems
73
74 Systems are called on every update, and they use components through nodes.
75
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}`);
87 }
88 }
89 };
90
91 ## That's it
92
93 Just run `engine.update(dt)` in your game loop :D
94
95 */
96
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; }; }();
98
99 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
100
101 var Serpentity = function () {
102 function Serpentity(config) {
103 _classCallCheck(this, Serpentity);
104
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 */
119
120
121 _createClass(Serpentity, [{
122 key: 'addSystem',
123 value: function addSystem(system, priority) {
124 var lastIndex = void 0,
125 found = void 0;
126
127 if (this.systems.indexOf(system) >= 0) {
128 return false;
129 }
130
131 system.priority = priority;
132
133 found = false;
134 lastIndex = 0;
135
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 }
147
148 this.systems.splice(lastIndex, 0, system);
149 system.added(this);
150 return true;
151 }
152
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 }
171
172 return false;
173 }
174
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);
188
189 this._nodeCollections.forEach(function (collection) {
190 collection.add(entity);
191 });
192
193 return true;
194 }
195
196 /*
197 * Removes entity from system, removing from all node collections
198 *
199 * returns true if removed, false if not present
200 */
201
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 });
212
213 this.entities.splice(position, 1);
214 return true;
215 }
216
217 return false;
218 }
219
220 /*
221 * Given a Node Class, retrieves a list of all the nodes for each
222 * applicable entity.
223 */
224
225 }, {
226 key: 'getNodes',
227 value: function getNodes(nodeType) {
228 var position = void 0,
229 nodeCollection = void 0;
230
231 position = this._nodeCollectionKeys.indexOf(nodeType);
232
233 if (position >= 0) {
234 return this._nodeCollections[position].nodes;
235 }
236
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 }();
266
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');
274
275 module.exports = Serpentity;
276 }
277 'use strict';
278
279 /* global Serpentity */
280
281 /*
282 * The entity gives the entity framework its name. It exists only
283 * to hold components.
284 */
285
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; }; }();
287
288 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
289
290 var Entity = function () {
291 function Entity(config) {
292 _classCallCheck(this, Entity);
293
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 */
305
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);
315 return true;
316 }
317
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;
329 }
330
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
348 if (typeof module !== 'undefined' && undefined.module !== module) {
349 module.exports = Entity;
350 } else {
351 Serpentity.Entity = Entity;
352 }
353 'use strict';
354
355 /* global Serpentity */
356
357 /*
358 * A node describes a set of components in order to describe entities
359 * that include them.
360 */
361
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; }; }();
363
364 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
365
366 var Node = function () {
367 _createClass(Node, null, [{
368 key: 'matches',
369
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 }
391 }
392 }
393
394 return true;
395 }
396 }]);
397
398 function Node(config) {
399 _classCallCheck(this, Node);
400
401 this.types = {};
402
403 Object.assign(this, config || {});
404 }
405
406 return Node;
407 }();
408
409 if (typeof module !== 'undefined' && undefined.module !== module) {
410 module.exports = Node;
411 } else {
412 Serpentity.Node = Node;
413 }
414 'use strict';
415
416 /* global Serpentity */
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 */
425
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; }; }();
427
428 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
429
430 var NodeCollection = function () {
431 function NodeCollection(config) {
432 _classCallCheck(this, NodeCollection);
433
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 */
446
447
448 _createClass(NodeCollection, [{
449 key: 'add',
450 value: function add(entity) {
451
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 }
463 }
464
465 this.nodes.push(node);
466
467 return true;
468 }
469
470 return false;
471 }
472
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 */
478
479 }, {
480 key: 'remove',
481 value: function remove(entity) {
482 var found = -1;
483
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;
493 }
494
495 return false;
496 }
497
498 /*
499 * Checks whether we already have nodes for this entity.
500 */
501
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;
514
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 }
532 }
533
534 return found;
535 }
536 }]);
537
538 return NodeCollection;
539 }();
540
541 if (typeof module !== 'undefined' && undefined.module !== module) {
542 module.exports = NodeCollection;
543 } else {
544 Serpentity.NodeCollection = NodeCollection;
545 }
546 'use strict';
547
548 /* global Serpentity */
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 */
556
557 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
558
559 var Component = function Component(config) {
560 _classCallCheck(this, Component);
561
562 Object.assign(this, config || {});
563 };
564
565 if (typeof module !== 'undefined' && undefined.module !== module) {
566 module.exports = Component;
567 } else {
568 Serpentity.Component = Component;
569 }
570 'use strict';
571
572 /* global Serpentity */
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
579 * three methods. They are shown here to document the interface.
580 */
581
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; }; }();
583
584 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
585
586 var 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() {}
599 // Override with added(engine)
600 // Receives an instance of the serpentity engine
601
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() {}
610 // Override with removed(engine)
611 // Receives an instance of the serpentity engine
612
613
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
629 if (typeof module !== 'undefined' && undefined.module !== module) {
630 module.exports = System;
631 } else {
632 Serpentity.System = System;
633 }