]>
Commit | Line | Data |
---|---|---|
19e91cdd BB |
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'; | |
d0eb71f3 BB |
8 | |
9 | /* | |
10 | Serpentity is a simple entity framework inspired by Ash. | |
11 | ||
12 | Usage: | |
13 | ||
19e91cdd | 14 | import Serpentity from '@serpentity/serpentity'; |
d0eb71f3 BB |
15 | |
16 | ## Instantiating an engine | |
17 | ||
b3b840f8 | 18 | const engine = new Serpentity(); |
d0eb71f3 BB |
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 | ||
19e91cdd BB |
39 | import { Entity } from '@serpentity/serpentity'; |
40 | const entity = new Entity(); | |
d0eb71f3 BB |
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 | ||
19e91cdd BB |
49 | import { Component } from '@serpentity/serpentity'; |
50 | const PositionComponent = class PositionComponent extends Component { | |
b3b840f8 RBR |
51 | constructor(config) { |
52 | ||
d0eb71f3 BB |
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 | ||
19e91cdd BB |
72 | import { Node } from '@serpentity/serpentity'; |
73 | const MovementNode = class MovementNode extends Node; | |
d0eb71f3 BB |
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 | ||
19e91cdd BB |
86 | import { System } from '@serpentity/serpentity'; |
87 | const TestSystem = class TestSystem extends System { | |
b3b840f8 RBR |
88 | added(engine){ |
89 | ||
d0eb71f3 | 90 | this.nodeList = engine.getNodes(MovementNode); |
b3b840f8 RBR |
91 | } |
92 | ||
93 | removed(engine){ | |
94 | ||
d0eb71f3 BB |
95 | this.nodeList = undefined; |
96 | } | |
b3b840f8 RBR |
97 | |
98 | update(dt){ | |
99 | ||
100 | for (const node of this.nodeList) { | |
d0eb71f3 BB |
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 | */ | |
19e91cdd | 111 | export default class Serpentity { |
b3b840f8 RBR |
112 | |
113 | constructor(config) { | |
d0eb71f3 | 114 | |
d0eb71f3 BB |
115 | this.systems = []; |
116 | this.entities = []; | |
117 | this._nodeCollections = []; | |
118 | this._nodeCollectionKeys = []; | |
119 | ||
b3b840f8 | 120 | Object.assign(this, config); |
d0eb71f3 BB |
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 | */ | |
b3b840f8 | 129 | addSystem(system, priority) { |
d0eb71f3 BB |
130 | |
131 | if (this.systems.indexOf(system) >= 0) { | |
132 | return false; | |
133 | } | |
134 | ||
135 | system.priority = priority; | |
136 | ||
b3b840f8 RBR |
137 | let lastIndex = 0; |
138 | ||
139 | const found = this.systems.some((existingSystem, i) => { | |
d0eb71f3 | 140 | |
d0eb71f3 BB |
141 | lastIndex = i; |
142 | if (existingSystem.priority >= system.priority) { | |
d0eb71f3 BB |
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 | */ | |
b3b840f8 | 162 | removeSystem(system) { |
d0eb71f3 | 163 | |
b3b840f8 | 164 | const position = this.systems.indexOf(system); |
d0eb71f3 BB |
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 | */ | |
b3b840f8 RBR |
179 | addEntity(entity) { |
180 | ||
d0eb71f3 BB |
181 | if (this.entities.indexOf(entity) >= 0) { |
182 | return false; | |
183 | } | |
19e91cdd | 184 | |
d0eb71f3 BB |
185 | this.entities.push(entity); |
186 | ||
b3b840f8 | 187 | for (const collection of this._nodeCollections) { |
d0eb71f3 | 188 | collection.add(entity); |
b3b840f8 | 189 | } |
d0eb71f3 BB |
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 | */ | |
b3b840f8 | 199 | removeEntity(entity) { |
d0eb71f3 | 200 | |
b3b840f8 | 201 | const position = this.entities.indexOf(entity); |
d0eb71f3 | 202 | if (position >= 0) { |
b3b840f8 | 203 | for (const collection of this._nodeCollections) { |
d0eb71f3 | 204 | collection.remove(entity); |
b3b840f8 | 205 | } |
d0eb71f3 BB |
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 | */ | |
b3b840f8 | 218 | getNodes(nodeType) { |
d0eb71f3 | 219 | |
b3b840f8 | 220 | const position = this._nodeCollectionKeys.indexOf(nodeType); |
d0eb71f3 BB |
221 | |
222 | if (position >= 0) { | |
223 | return this._nodeCollections[position].nodes; | |
224 | } | |
225 | ||
19e91cdd BB |
226 | const nodeCollection = new NodeCollection({ |
227 | type: nodeType | |
d0eb71f3 BB |
228 | }); |
229 | ||
230 | this._nodeCollectionKeys.push(nodeType); | |
231 | this._nodeCollections.push(nodeCollection); | |
232 | ||
b3b840f8 | 233 | for (const entity of this.entities) { |
d0eb71f3 | 234 | nodeCollection.add(entity); |
b3b840f8 | 235 | } |
d0eb71f3 | 236 | |
17e4efc7 | 237 | return nodeCollection; |
d0eb71f3 BB |
238 | } |
239 | ||
240 | /* | |
241 | * Calls update for every loaded system. | |
242 | */ | |
b3b840f8 RBR |
243 | update(dt) { |
244 | ||
245 | for (const system of this.systems) { | |
d0eb71f3 | 246 | system.update(dt); |
b3b840f8 | 247 | } |
d0eb71f3 | 248 | } |
19e91cdd | 249 | } |