]>
Commit | Line | Data |
---|---|---|
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 | } |