]> git.r.bdr.sh - rbdr/serpentity/blame_incremental - dist/serpentity.js
Normalizes API for entities
[rbdr/serpentity] / dist / serpentity.js
... / ...
CommitLineData
1if (typeof require !== "undefined") {
2 require("neon");
3}
4
5/*
6Serpentity is a simple entity framework inspired by Ash.
7
8Usage:
9
10 require('serpentity');
11
12## Instantiating an engine
13
14 var engine = Serpentity();
15
16Add entities or systems, systems are added with a priority (the smaller
17the number, the earlier it will be called):
18
19 engine.addEntity(entityFactory());
20 engine.addSystem(new GameSystem(), priority);
21
22Update all systems:
23
24 engine.update(dt);
25
26Remove entities or systems:
27
28 engine.removeEntity(entityReference);
29 engine.removeSystem(systemReference);
30
31## Creating Entities
32
33Entities are the basic object of Serpentity, and they do nothing.
34
35 var entity = new Serpentity.Entity();
36
37All the behavior is added through components
38
39## Creating Components
40
41Components define data that we can add to an entity. This data will
42eventually be consumed by "Systems"
43
44 Class("PositionComponent").inherits(Serpentity.Component)({
45 prototype : {
46 x : 0,
47 y : 0
48 }
49 });
50
51You can add components to entities by using the add method:
52
53 entity.addComponent(new PositionComponent());
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
63 Class("MovementNode").inherits(Serpentity.Node)({
64 types : {
65 position : PositionComponent,
66 motion : MotionComponent
67 }
68 });
69
70You can then request an array of all the nodes representing entities
71that comply with that API
72
73 engine.getNodes(MovementNode);
74
75## Creating Systems
76
77Systems 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
97Just run `engine.update(dt)` in your game loop :D
98
99*/
100Class("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
255if (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 */
267Class(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 */
326Class(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 */
380Class(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 */
474Class(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 */
497Class(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});