]> git.r.bdr.sh - rbdr/serpentity/blame - dist/serpentity.js
Uses ES6 version in README
[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
BB
96let 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;
a2949b6a 195 }
1bd778d2
BB
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.
238if (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;
a2949b6a
BB
248}
249
1bd778d2
BB
250'use strict';
251
252/* global Serpentity */
253
a2949b6a
BB
254/*
255 * The entity gives the entity framework its name. It exists only
256 * to hold components.
257 */
1bd778d2
BB
258
259let 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;
a2949b6a 275 }
1bd778d2
BB
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
303if (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 */
a2949b6a
BB
312
313/*
314 * A node describes a set of components in order to describe entities
315 * that include them.
316 */
1bd778d2 317let Node = class Node {
a2949b6a 318
1bd778d2
BB
319 /*
320 * Returns true if the given entity matches the defined protocol,
321 * false otherwise
322 */
323 static matches (entity) {
324 let property, types;
a2949b6a 325
1bd778d2 326 types = this.types;
a2949b6a 327
1bd778d2
BB
328 for (property in types) {
329 if (types.hasOwnProperty(property)) {
330 let matched, type;
a2949b6a 331
1bd778d2
BB
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 }
a2949b6a 342
1bd778d2
BB
343 return true;
344 }
a2949b6a 345
1bd778d2
BB
346 constructor (config) {
347 this.types = {};
a2949b6a 348
1bd778d2
BB
349 Object.assign(this, config || {});
350 }
351};
a2949b6a 352
1bd778d2
BB
353if (typeof module !== 'undefined' && this.module !== module) {
354 module.exports = Node;
355} else {
356 Serpentity.Node = Node;
357}
a2949b6a 358
1bd778d2 359'use strict';
a2949b6a 360
1bd778d2 361/* global Serpentity */
a2949b6a
BB
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 */
1bd778d2
BB
370
371let 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]);
a2949b6a 398 }
1bd778d2
BB
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 }
a2949b6a 443 }
1bd778d2
BB
444
445 return found;
446 }
447};
448
449if (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 */
a2949b6a
BB
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 */
1bd778d2
BB
465
466let Component = class Component {
467 constructor (config) {
468 Object.assign(this, config || {});
469 }
470};
471
472if (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 */
a2949b6a
BB
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
1bd778d2 487 * three methods. They are shown here to document the interface.
a2949b6a 488 */
1bd778d2
BB
489
490let 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
517if (typeof module !== 'undefined' && this.module !== module) {
518 module.exports = System;
519} else {
520 Serpentity.System = System;
521}