]> git.r.bdr.sh - rbdr/serpentity/blame_incremental - lib/serpentity.js
Replace code and lab with ndoe test runner
[rbdr/serpentity] / lib / serpentity.js
... / ...
CommitLineData
1// Export submodules
2export { Component } from './serpentity/component.js';
3export { Entity } from './serpentity/entity.js';
4export { Node } from './serpentity/node.js';
5export { System } from './serpentity/system.js';
6
7import { NodeCollection } from './serpentity/node_collection.js';
8
9/*
10Serpentity is a simple entity framework inspired by Ash.
11
12Usage:
13
14 import Serpentity from '@serpentity/serpentity';
15
16## Instantiating an engine
17
18 const engine = new Serpentity();
19
20Add entities or systems, systems are added with a priority (the smaller
21the number, the earlier it will be called):
22
23 engine.addEntity(entityFactory());
24 engine.addSystem(new GameSystem(), priority);
25
26Update all systems:
27
28 engine.update(dt);
29
30Remove entities or systems:
31
32 engine.removeEntity(entityReference);
33 engine.removeSystem(systemReference);
34
35## Creating Entities
36
37Entities are the basic object of Serpentity, and they do nothing.
38
39 import { Entity } from '@serpentity/serpentity';
40 const entity = new Entity();
41
42All the behavior is added through components
43
44## Creating Components
45
46Components define data that we can add to an entity. This data will
47eventually 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
60You can add components to entities by using the add method:
61
62 entity.addComponent(new PositionComponent());
63
64
65Systems can refer to entities by requesting nodes.
66
67## Working with Nodes
68
69Nodes are sets of components that you define, so your system can require
70entities 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
77You can then request an array of all the nodes representing entities
78that comply with that API
79
80 engine.getNodes(MovementNode);
81
82## Creating Systems
83
84Systems 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
108Just run `engine.update(dt)` in your game loop :D
109
110*/
111export 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}