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