]> git.r.bdr.sh - rbdr/serpentity/blob - dist/serpentity.js
Normalizes API for entities
[rbdr/serpentity] / dist / serpentity.js
1 if (typeof require !== "undefined") {
2 require("neon");
3 }
4
5 /*
6 Serpentity is a simple entity framework inspired by Ash.
7
8 Usage:
9
10 require('serpentity');
11
12 ## Instantiating an engine
13
14 var engine = Serpentity();
15
16 Add entities or systems, systems are added with a priority (the smaller
17 the number, the earlier it will be called):
18
19 engine.addEntity(entityFactory());
20 engine.addSystem(new GameSystem(), priority);
21
22 Update all systems:
23
24 engine.update(dt);
25
26 Remove entities or systems:
27
28 engine.removeEntity(entityReference);
29 engine.removeSystem(systemReference);
30
31 ## Creating Entities
32
33 Entities are the basic object of Serpentity, and they do nothing.
34
35 var entity = new Serpentity.Entity();
36
37 All the behavior is added through components
38
39 ## Creating Components
40
41 Components define data that we can add to an entity. This data will
42 eventually be consumed by "Systems"
43
44 Class("PositionComponent").inherits(Serpentity.Component)({
45 prototype : {
46 x : 0,
47 y : 0
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 Class("MovementNode").inherits(Serpentity.Node)({
64 types : {
65 position : PositionComponent,
66 motion : MotionComponent
67 }
68 });
69
70 You can then request an array of all the nodes representing entities
71 that comply with that API
72
73 engine.getNodes(MovementNode);
74
75 ## Creating Systems
76
77 Systems are called on every update, and they use components through nodes.
78
79 Class("TestSystem").inherits(Serpentity.System)({
80 prototype : {
81 added : function added(engine){
82 this.nodeList = engine.getNodes(MovementNode);
83 },
84 removed : function removed(engine){
85 this.nodeList = undefined;
86 }
87 update : function update(dt){
88 this.nodeList.forEach(function (node) {
89 console.log("Current position is: " + node.position.x + "," + node.position.y);
90 });
91 }
92 }
93 });
94
95 ## That's it
96
97 Just run `engine.update(dt)` in your game loop :D
98
99 */
100 Class("Serpentity")({
101 prototype : {
102 systems : null,
103 entities : null,
104 _nodeCollections : null,
105 _nodeCollectionKeys : null,
106
107 init : function init(config) {
108 var property;
109
110 config = config || {};
111
112 this.systems = [];
113 this.entities = [];
114 this._nodeCollections = [];
115 this._nodeCollectionKeys = [];
116
117 for (property in config) {
118 if (config.hasOwnProperty(property)) {
119 this[property] = config[property];
120 }
121 }
122 },
123
124 /*
125 * Adds a system to the engine, so its update method will be called
126 * with the others. Triggers added hook.
127 *
128 * returns true if added succesfully, false if already added
129 */
130 addSystem : function addSystem(system, priority) {
131 var lastIndex, found;
132
133 if (this.systems.indexOf(system) >= 0) {
134 return false;
135 }
136
137 system.priority = priority;
138
139 found = false;
140 lastIndex = 0;
141
142 this.systems.some(function findPriority(existingSystem, i) {
143 lastIndex = i;
144 if (existingSystem.priority >= system.priority) {
145 found = true;
146 return true;
147 }
148 });
149
150 if (!found) {
151 lastIndex += 1
152 }
153
154 this.systems.splice(lastIndex, 0, system);
155 system.added(this);
156 return true;
157 },
158
159 /*
160 * Removes a system from the engine, so its update method will no
161 * longer will be called. Triggers the removed hook.
162 *
163 * returns true if removed succesfully, false if already added
164 */
165 removeSystem : function removeSystem(system) {
166 var position;
167
168 position = this.systems.indexOf(system);
169 if (position >= 0) {
170 this.systems[position].removed(this);
171 this.systems.splice(position, 1);
172 return true;
173 }
174
175 return false;
176 },
177
178 /*
179 * Adds an entity to the engine, adds to existing node collections
180 *
181 * returns true if added, false if already there
182 */
183 addEntity : 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 removeEntity : function removeEntity(entity) {
202 var position;
203
204 position = this.entities.indexOf(entity);
205 if (position >= 0) {
206 this._nodeCollections.forEach(function (collection) {
207 collection.remove(entity);
208 });
209
210 this.entities.splice(position, 1);
211 return true;
212 }
213
214 return false;
215 },
216
217 /*
218 * Given a Node Class, retrieves a list of all the nodes for each
219 * applicable entity.
220 */
221 getNodes : function getNodes(nodeType) {
222 var position, nodeCollection;
223
224 position = this._nodeCollectionKeys.indexOf(nodeType);
225
226 if (position >= 0) {
227 return this._nodeCollections[position].nodes;
228 }
229
230 nodeCollection = new Serpentity.NodeCollection({
231 type : nodeType,
232 });
233
234 this._nodeCollectionKeys.push(nodeType);
235 this._nodeCollections.push(nodeCollection);
236
237 this.entities.forEach(function (entity) {
238 nodeCollection.add(entity);
239 });
240
241 return nodeCollection.nodes;
242 },
243
244 /*
245 * Calls update for every loaded system.
246 */
247 update : function update(dt) {
248 this.systems.forEach(function (system) {
249 system.update(dt);
250 });
251 }
252 }
253 });
254
255 if (typeof require !== "undefined") {
256 require("./component.js");
257 require("./entity.js");
258 require("./node.js");
259 require("./node_collection.js");
260 require("./system.js");
261 }
262
263 /*
264 * The entity gives the entity framework its name. It exists only
265 * to hold components.
266 */
267 Class(Serpentity, "Entity")({
268 prototype : {
269 _components : null,
270 _componentKeys : null,
271
272 init : function init(config) {
273 var property;
274
275 this._componentKeys = [];
276 this._components = [];
277
278 for (property in config) {
279 if (config.hasOwnProperty(property)) {
280 this[property] = config[property];
281 }
282 }
283 },
284
285 /*
286 * Adds a component to the entity.
287 *
288 * returns true if added, false if already present
289 */
290 addComponent : function addComponent(component) {
291 if (this._componentKeys.indexOf(component.constructor) >= 0) {
292 return false;
293 }
294 this._componentKeys.push(component.constructor);
295 this._components.push(component);
296 return true;
297 },
298
299 /*
300 * returns true if component is included, false otherwise
301 */
302 hasComponent : function hasComponent(componentClass) {
303 if (this._componentKeys.indexOf(componentClass) >= 0) {
304 return true;
305 }
306 return false;
307 },
308
309 /*
310 * returns the component associated with that key
311 */
312 getComponent : function getComponent(componentClass) {
313 var position;
314 position = this._componentKeys.indexOf(componentClass);
315 if (position >= 0) {
316 return this._components[position];
317 }
318 }
319 }
320 });
321
322 /*
323 * A node describes a set of components in order to describe entities
324 * that include them.
325 */
326 Class(Serpentity, "Node")({
327
328 /*
329 * Returns true if the given entity matches the defined protocol,
330 * false otherwise
331 */
332 matches : function matches(entity) {
333 var property, matched, types;
334
335 types = this.types;
336
337 for (property in this.types) {
338
339 if (this.types.hasOwnProperty(property)) {
340 matched = false;
341
342 if (entity.hasComponent(types[property])) {
343 matched = true;
344 }
345
346 if (!matched) {
347 return false;
348 }
349 }
350 }
351
352 return true;
353 },
354
355 prototype : {
356
357 types : null,
358
359 init : function (config) {
360 var property;
361
362 this.types = {};
363
364 for (property in this.constructor) {
365 if (this.constructor.hasOwnProperty(property)) {
366 this.types[property] = this.constructor[property];
367 }
368 }
369 }
370 }
371 });
372
373 /*
374 * Node Collections contain nodes, in order to keep the lists of nodes
375 * that belong to each type.
376 *
377 * It has a type which is the class name of the node, and an array of
378 * instances of that class.
379 */
380 Class(Serpentity, "NodeCollection")({
381 prototype : {
382 type : null,
383 nodes : null,
384
385 init : function init(config) {
386 var property;
387
388 config = config || {};
389
390 this.nodes = [];
391
392 for (property in config) {
393 if (config.hasOwnProperty(property)) {
394 this[property] = config[property];
395 }
396 }
397 },
398
399 /*
400 * Creates a node for an entity if it matches, and adds it to the
401 * node list.
402 *
403 * Returns true if added, false otherwise.
404 */
405 add : function add(entity) {
406 var node, types, property;
407
408 if (this.type.matches(entity) && !this._entityExists(entity)) {
409 node = new this.type({});
410
411 node.entity = entity;
412
413 types = this.type.types;
414
415 for (property in types) {
416 if (types.hasOwnProperty(property)) {
417 node[property] = entity.getComponent(types[property]);
418 }
419 }
420
421 this.nodes.push(node);
422
423 return true;
424 }
425
426 return false;
427 },
428
429 /*
430 * Removes an entity by removing its related node from the list of nodes
431 *
432 * returns true if it was removed, false otherwise.
433 */
434 remove : function (entity) {
435 var found;
436 found = -1;
437 this.nodes.forEach(function (node, i) {
438 if (node.entity === entity) {
439 found = i;
440 }
441 });
442
443 if (found >= 0) {
444 this.nodes.splice(found, 1);
445 return true;
446 }
447
448 return false;
449 },
450
451 /*
452 * Checks whether we already have nodes for this entity.
453 */
454 _entityExists : function entityExists(entity) {
455 var found;
456 found = false;
457 this.nodes.forEach(function (node) {
458 if (node.entity === entity) {
459 found = true;
460 }
461 });
462
463 return found;
464 }
465 }
466 });
467
468 /*
469 * Components store data. Nothing to say here really, just
470 * inherit and add a prototype, or don't even inherit, see?
471 * It's just an empty class, so what I'm trying to say is your
472 * components can be any class whatsoever.
473 */
474 Class(Serpentity, "Component")({
475 prototype : {
476 init : function init(config) {
477 var property;
478
479 config = config || {};
480
481 for (property in config) {
482 if (config.hasOwnProperty(property)) {
483 this[property] = config[property];
484 }
485 }
486 }
487 }
488 });
489
490 /*
491 * Systems contain most of the logic, and work with nodes in order to
492 * act and change their values.
493 *
494 * You usually want to inherit from this class and override the
495 * three methods.
496 */
497 Class(Serpentity, "System")({
498 prototype : {
499
500 /*
501 * This will be run when the system is added to the engine
502 */
503 added : function added(engine) {
504 // Override
505 },
506
507 /*
508 * This will be run when the system is removed from the engine
509 */
510 removed : function removed(engine) {
511 // Override
512 },
513
514 /*
515 * This will run every time the engine's update method is called
516 */
517 update : function update(dt) {
518 // Override
519 }
520 }
521 });