]> git.r.bdr.sh - rbdr/serpentity/blob - dist/serpentity.js
8871985bc710ea7debe9f4b1c1c1093b71d13a5d
[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 let Serpentity = class Serpentity {
97
98 constructor (config) {
99 this.systems = [];
100 this.entities = [];
101 this._nodeCollections = [];
102 this._nodeCollectionKeys = [];
103
104 Object.assign(this, config || {});
105 }
106
107 /*
108 * Adds a system to the engine, so its update method will be called
109 * with the others. Triggers added hook.
110 *
111 * returns true if added succesfully, false if already added
112 */
113 addSystem (system, priority) {
114 let lastIndex, found;
115
116 if (this.systems.indexOf(system) >= 0) {
117 return false;
118 }
119
120 system.priority = priority;
121
122 found = false;
123 lastIndex = 0;
124
125 this.systems.some(function findPriority(existingSystem, i) {
126 lastIndex = i;
127 if (existingSystem.priority >= system.priority) {
128 found = true;
129 return true;
130 }
131 });
132
133 if (!found) {
134 lastIndex += 1;
135 }
136
137 this.systems.splice(lastIndex, 0, system);
138 system.added(this);
139 return true;
140 }
141
142 /*
143 * Removes a system from the engine, so its update method will no
144 * longer will be called. Triggers the removed hook.
145 *
146 * returns true if removed succesfully, false if already added
147 */
148 removeSystem (system) {
149 let position;
150
151 position = this.systems.indexOf(system);
152 if (position >= 0) {
153 this.systems[position].removed(this);
154 this.systems.splice(position, 1);
155 return true;
156 }
157
158 return false;
159 }
160
161 /*
162 * Adds an entity to the engine, adds to existing node collections
163 *
164 * returns true if added, false if already there
165 */
166 addEntity (entity) {
167 if (this.entities.indexOf(entity) >= 0) {
168 return false;
169 }
170 this.entities.push(entity);
171
172 this._nodeCollections.forEach(function (collection) {
173 collection.add(entity);
174 });
175
176 return true;
177 }
178
179 /*
180 * Removes entity from system, removing from all node collections
181 *
182 * returns true if removed, false if not present
183 */
184 removeEntity (entity) {
185 let position;
186
187 position = this.entities.indexOf(entity);
188 if (position >= 0) {
189 this._nodeCollections.forEach(function (collection) {
190 collection.remove(entity);
191 });
192
193 this.entities.splice(position, 1);
194 return true;
195 }
196
197 return false;
198 }
199
200 /*
201 * Given a Node Class, retrieves a list of all the nodes for each
202 * applicable entity.
203 */
204 getNodes (nodeType) {
205 let position, nodeCollection;
206
207 position = this._nodeCollectionKeys.indexOf(nodeType);
208
209 if (position >= 0) {
210 return this._nodeCollections[position].nodes;
211 }
212
213 nodeCollection = new Serpentity.NodeCollection({
214 type : nodeType
215 });
216
217 this._nodeCollectionKeys.push(nodeType);
218 this._nodeCollections.push(nodeCollection);
219
220 this.entities.forEach(function (entity) {
221 nodeCollection.add(entity);
222 });
223
224 return nodeCollection.nodes;
225 }
226
227 /*
228 * Calls update for every loaded system.
229 */
230 update (dt) {
231 this.systems.forEach(function (system) {
232 system.update(dt);
233 });
234 }
235 };
236
237 // Add namespaced objects.
238 if (typeof module !== 'undefined' && this.module !== module) {
239 Serpentity.Component = require('./serpentity/component.js');
240 Serpentity.Entity = require('./serpentity/entity.js');
241 Serpentity.Node = require('./serpentity/node.js');
242 Serpentity.NodeCollection = require('./serpentity/node_collection.js');
243 Serpentity.System = require('./serpentity/system.js');
244
245 module.exports = Serpentity;
246 } else {
247 window.Serpentity = Serpentity;
248 }
249
250 'use strict';
251
252 /* global Serpentity */
253
254 /*
255 * The entity gives the entity framework its name. It exists only
256 * to hold components.
257 */
258
259 let Entity = class Entity {
260 constructor (config) {
261 this._componentKeys = [];
262 this._components = [];
263
264 Object.assign(this, config || {});
265 }
266
267 /*
268 * Adds a component to the entity.
269 *
270 * returns true if added, false if already present
271 */
272 addComponent (component) {
273 if (this._componentKeys.indexOf(component.constructor) >= 0) {
274 return false;
275 }
276 this._componentKeys.push(component.constructor);
277 this._components.push(component);
278 return true;
279 }
280
281 /*
282 * returns true if component is included, false otherwise
283 */
284 hasComponent (componentClass) {
285 if (this._componentKeys.indexOf(componentClass) >= 0) {
286 return true;
287 }
288 return false;
289 }
290
291 /*
292 * returns the component associated with that key
293 */
294 getComponent (componentClass) {
295 let position;
296 position = this._componentKeys.indexOf(componentClass);
297 if (position >= 0) {
298 return this._components[position];
299 }
300 }
301 };
302
303 if (typeof module !== 'undefined' && this.module !== module) {
304 module.exports = Entity;
305 } else {
306 Serpentity.Entity = Entity;
307 }
308
309 'use strict';
310
311 /* global Serpentity */
312
313 /*
314 * A node describes a set of components in order to describe entities
315 * that include them.
316 */
317 let Node = class Node {
318
319 /*
320 * Returns true if the given entity matches the defined protocol,
321 * false otherwise
322 */
323 static matches (entity) {
324 let property, types;
325
326 types = this.types;
327
328 for (property in types) {
329 if (types.hasOwnProperty(property)) {
330 let matched, type;
331
332 matched = false;
333 type = types[property];
334 if (entity.hasComponent(type)) {
335 matched = true;
336 }
337 if (!matched) {
338 return false;
339 }
340 }
341 }
342
343 return true;
344 }
345
346 constructor (config) {
347 this.types = {};
348
349 Object.assign(this, config || {});
350 }
351 };
352
353 if (typeof module !== 'undefined' && this.module !== module) {
354 module.exports = Node;
355 } else {
356 Serpentity.Node = Node;
357 }
358
359 'use strict';
360
361 /* global Serpentity */
362
363 /*
364 * Node Collections contain nodes, in order to keep the lists of nodes
365 * that belong to each type.
366 *
367 * It has a type which is the class name of the node, and an array of
368 * instances of that class.
369 */
370
371 let NodeCollection = class NodeCollection {
372
373 constructor (config) {
374 this.nodes = [];
375 this.type = null;
376
377 Object.assign(this, config || {});
378 }
379
380 /*
381 * Creates a node for an entity if it matches, and adds it to the
382 * node list.
383 *
384 * Returns true if added, false otherwise.
385 */
386 add (entity) {
387
388 if (this.type.matches(entity) && !this._entityExists(entity)) {
389 let node, types, property;
390
391 node = new this.type({});
392 node.entity = entity;
393 types = this.type.types;
394
395 for (property in types) {
396 if (types.hasOwnProperty(property)) {
397 node[property] = entity.getComponent(types[property]);
398 }
399 }
400
401 this.nodes.push(node);
402
403 return true;
404 }
405
406 return false;
407 }
408
409 /*
410 * Removes an entity by removing its related node from the list of nodes
411 *
412 * returns true if it was removed, false otherwise.
413 */
414 remove (entity) {
415 let found;
416
417 found = -1;
418 this.nodes.forEach(function (node, i) {
419 if (node.entity === entity) {
420 found = i;
421 }
422 });
423
424 if (found >= 0) {
425 this.nodes.splice(found, 1);
426 return true;
427 }
428
429 return false;
430 }
431
432 /*
433 * Checks whether we already have nodes for this entity.
434 */
435 _entityExists (entity) {
436 let found, node;
437
438 found = false;
439 for (node of this.nodes) {
440 if (node.entity === entity) {
441 found = true;
442 }
443 }
444
445 return found;
446 }
447 };
448
449 if (typeof module !== 'undefined' && this.module !== module) {
450 module.exports = NodeCollection;
451 } else {
452 Serpentity.NodeCollection = NodeCollection;
453 }
454
455 'use strict';
456
457 /* global Serpentity */
458
459 /*
460 * Components store data. Nothing to say here really, just
461 * inherit and add a prototype, or don't even inherit, see?
462 * It's just an empty class, so what I'm trying to say is your
463 * components can be any class whatsoever.
464 */
465
466 let Component = class Component {
467 constructor (config) {
468 Object.assign(this, config || {});
469 }
470 };
471
472 if (typeof module !== 'undefined' && this.module !== module) {
473 module.exports = Component;
474 } else {
475 Serpentity.Component = Component;
476 }
477
478 'use strict';
479
480 /* global Serpentity */
481
482 /*
483 * Systems contain most of the logic, and work with nodes in order to
484 * act and change their values.
485 *
486 * You usually want to inherit from this class and override the
487 * three methods. They are shown here to document the interface.
488 */
489
490 let System = class System {
491
492 /*
493 * This will be run when the system is added to the engine
494 */
495 added () {
496 // Override with added(engine)
497 // Receives an instance of the serpentity engine
498 }
499
500 /*
501 * This will be run when the system is removed from the engine
502 */
503 removed () {
504 // Override with removed(engine)
505 // Receives an instance of the serpentity engine
506 }
507
508 /*
509 * This will run every time the engine's update method is called
510 */
511 update () {
512 // Override with update(dt)
513 // Receives a delta of the time
514 }
515 };
516
517 if (typeof module !== 'undefined' && this.module !== module) {
518 module.exports = System;
519 } else {
520 Serpentity.System = System;
521 }