]>
Commit | Line | Data |
---|---|---|
d0eb71f3 BB |
1 | 'use strict'; |
2 | ||
3 | /* | |
4 | Serpentity is a simple entity framework inspired by Ash. | |
5 | ||
6 | Usage: | |
7 | ||
b3b840f8 | 8 | const Serpentity = require('serpentity'); |
d0eb71f3 BB |
9 | |
10 | ## Instantiating an engine | |
11 | ||
b3b840f8 | 12 | const engine = new Serpentity(); |
d0eb71f3 BB |
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 | ||
b3b840f8 | 33 | const entity = new Serpentity.Entity(); |
d0eb71f3 BB |
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 | ||
b3b840f8 RBR |
42 | const PositionComponent = class PositionComponent extends Serpentity.Component { |
43 | constructor(config) { | |
44 | ||
d0eb71f3 BB |
45 | this.x = 0; |
46 | this.y = 0; | |
47 | ||
48 | super(config); | |
49 | } | |
50 | }; | |
51 | ||
52 | You can add components to entities by using the add method: | |
53 | ||
54 | entity.addComponent(new PositionComponent()); | |
55 | ||
56 | ||
57 | Systems can refer to entities by requesting nodes. | |
58 | ||
59 | ## Working with Nodes | |
60 | ||
61 | Nodes are sets of components that you define, so your system can require | |
62 | entities that always follow the API defined in the node. | |
63 | ||
b3b840f8 | 64 | const MovementNode = class MovementNode extends Serpentity.Node; |
d0eb71f3 BB |
65 | MovementNode.position = PositionComponent; |
66 | MovementNode.motion = MotionComponent; | |
67 | ||
68 | You can then request an array of all the nodes representing entities | |
69 | that comply with that API | |
70 | ||
71 | engine.getNodes(MovementNode); | |
72 | ||
73 | ## Creating Systems | |
74 | ||
75 | Systems are called on every update, and they use components through nodes. | |
76 | ||
b3b840f8 RBR |
77 | const TestSystem = class TestSystem extends Serpentity.System { |
78 | added(engine){ | |
79 | ||
d0eb71f3 | 80 | this.nodeList = engine.getNodes(MovementNode); |
b3b840f8 RBR |
81 | } |
82 | ||
83 | removed(engine){ | |
84 | ||
d0eb71f3 BB |
85 | this.nodeList = undefined; |
86 | } | |
b3b840f8 RBR |
87 | |
88 | update(dt){ | |
89 | ||
90 | for (const node of this.nodeList) { | |
d0eb71f3 BB |
91 | console.log(`Current position is: ${node.position.x},${node.position.y}`); |
92 | } | |
93 | } | |
94 | }; | |
95 | ||
96 | ## That's it | |
97 | ||
98 | Just run `engine.update(dt)` in your game loop :D | |
99 | ||
100 | */ | |
b3b840f8 RBR |
101 | const Serpentity = class Serpentity { |
102 | ||
103 | constructor(config) { | |
d0eb71f3 | 104 | |
d0eb71f3 BB |
105 | this.systems = []; |
106 | this.entities = []; | |
107 | this._nodeCollections = []; | |
108 | this._nodeCollectionKeys = []; | |
109 | ||
b3b840f8 | 110 | Object.assign(this, config); |
d0eb71f3 BB |
111 | } |
112 | ||
113 | /* | |
114 | * Adds a system to the engine, so its update method will be called | |
115 | * with the others. Triggers added hook. | |
116 | * | |
117 | * returns true if added succesfully, false if already added | |
118 | */ | |
b3b840f8 | 119 | addSystem(system, priority) { |
d0eb71f3 BB |
120 | |
121 | if (this.systems.indexOf(system) >= 0) { | |
122 | return false; | |
123 | } | |
124 | ||
125 | system.priority = priority; | |
126 | ||
b3b840f8 RBR |
127 | let lastIndex = 0; |
128 | ||
129 | const found = this.systems.some((existingSystem, i) => { | |
d0eb71f3 | 130 | |
d0eb71f3 BB |
131 | lastIndex = i; |
132 | if (existingSystem.priority >= system.priority) { | |
d0eb71f3 BB |
133 | return true; |
134 | } | |
135 | }); | |
136 | ||
137 | if (!found) { | |
138 | lastIndex += 1; | |
139 | } | |
140 | ||
141 | this.systems.splice(lastIndex, 0, system); | |
142 | system.added(this); | |
143 | return true; | |
144 | } | |
145 | ||
146 | /* | |
147 | * Removes a system from the engine, so its update method will no | |
148 | * longer will be called. Triggers the removed hook. | |
149 | * | |
150 | * returns true if removed succesfully, false if already added | |
151 | */ | |
b3b840f8 | 152 | removeSystem(system) { |
d0eb71f3 | 153 | |
b3b840f8 | 154 | const position = this.systems.indexOf(system); |
d0eb71f3 BB |
155 | if (position >= 0) { |
156 | this.systems[position].removed(this); | |
157 | this.systems.splice(position, 1); | |
158 | return true; | |
159 | } | |
160 | ||
161 | return false; | |
162 | } | |
163 | ||
164 | /* | |
165 | * Adds an entity to the engine, adds to existing node collections | |
166 | * | |
167 | * returns true if added, false if already there | |
168 | */ | |
b3b840f8 RBR |
169 | addEntity(entity) { |
170 | ||
d0eb71f3 BB |
171 | if (this.entities.indexOf(entity) >= 0) { |
172 | return false; | |
173 | } | |
174 | this.entities.push(entity); | |
175 | ||
b3b840f8 | 176 | for (const collection of this._nodeCollections) { |
d0eb71f3 | 177 | collection.add(entity); |
b3b840f8 | 178 | } |
d0eb71f3 BB |
179 | |
180 | return true; | |
181 | } | |
182 | ||
183 | /* | |
184 | * Removes entity from system, removing from all node collections | |
185 | * | |
186 | * returns true if removed, false if not present | |
187 | */ | |
b3b840f8 | 188 | removeEntity(entity) { |
d0eb71f3 | 189 | |
b3b840f8 | 190 | const position = this.entities.indexOf(entity); |
d0eb71f3 | 191 | if (position >= 0) { |
b3b840f8 | 192 | for (const collection of this._nodeCollections) { |
d0eb71f3 | 193 | collection.remove(entity); |
b3b840f8 | 194 | } |
d0eb71f3 BB |
195 | |
196 | this.entities.splice(position, 1); | |
197 | return true; | |
198 | } | |
199 | ||
200 | return false; | |
201 | } | |
202 | ||
203 | /* | |
204 | * Given a Node Class, retrieves a list of all the nodes for each | |
205 | * applicable entity. | |
206 | */ | |
b3b840f8 | 207 | getNodes(nodeType) { |
d0eb71f3 | 208 | |
b3b840f8 | 209 | const position = this._nodeCollectionKeys.indexOf(nodeType); |
d0eb71f3 BB |
210 | |
211 | if (position >= 0) { | |
212 | return this._nodeCollections[position].nodes; | |
213 | } | |
214 | ||
b3b840f8 | 215 | const nodeCollection = new Serpentity.NodeCollection({ |
d0eb71f3 BB |
216 | type : nodeType |
217 | }); | |
218 | ||
219 | this._nodeCollectionKeys.push(nodeType); | |
220 | this._nodeCollections.push(nodeCollection); | |
221 | ||
b3b840f8 | 222 | for (const entity of this.entities) { |
d0eb71f3 | 223 | nodeCollection.add(entity); |
b3b840f8 | 224 | } |
d0eb71f3 | 225 | |
17e4efc7 | 226 | return nodeCollection; |
d0eb71f3 BB |
227 | } |
228 | ||
229 | /* | |
230 | * Calls update for every loaded system. | |
231 | */ | |
b3b840f8 RBR |
232 | update(dt) { |
233 | ||
234 | for (const system of this.systems) { | |
d0eb71f3 | 235 | system.update(dt); |
b3b840f8 | 236 | } |
d0eb71f3 BB |
237 | } |
238 | }; | |
239 | ||
240 | // Add namespaced objects. | |
b3b840f8 RBR |
241 | Serpentity.Component = require('./serpentity/component.js'); |
242 | Serpentity.Entity = require('./serpentity/entity.js'); | |
243 | Serpentity.Node = require('./serpentity/node.js'); | |
244 | Serpentity.NodeCollection = require('./serpentity/node_collection.js'); | |
245 | Serpentity.System = require('./serpentity/system.js'); | |
246 | ||
247 | module.exports = Serpentity; |