]> git.r.bdr.sh - rbdr/serpentity/blob - lib/serpentity.js
Merge branch 'release/1.0.0'
[rbdr/serpentity] / lib / 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 }