dopaminejs 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/dopamine.cjs +37 -0
- package/dist/dopamine.js +584 -1403
- package/dist/engine.cjs +1 -0
- package/dist/engine.js +1158 -0
- package/dist/style.css +1 -1
- package/package.json +27 -13
- package/src/core/Collider.js +43 -7
- package/src/core/PluginRegistry.js +84 -7
- package/src/core/Scene.js +50 -5
- package/src/core/SystemRegistry.js +32 -11
- package/src/dopamine/audio/SoundManager.js +91 -20
- package/src/dopamine/components/ScreenShake.js +7 -6
- package/src/dopamine/components/index.js +0 -2
- package/src/dopamine/core/DataService.js +31 -3
- package/src/dopamine/core/RewardSystem.js +178 -22
- package/src/dopamine/effects/ParticleSystem.js +72 -15
- package/src/dopamine/index.js +56 -24
- package/src/dopamine/ui/GameUI.js +48 -5
- package/src/dopamine/ui/dopamine.css +18 -0
- package/src/engine.js +38 -0
- package/src/systems/Director.js +7 -0
- package/src/systems/Ticker.js +14 -3
- package/types/index.d.ts +59 -14
- package/dist/dopamine.umd.cjs +0 -37
package/dist/engine.js
ADDED
|
@@ -0,0 +1,1158 @@
|
|
|
1
|
+
class a {
|
|
2
|
+
constructor() {
|
|
3
|
+
this._listeners = /* @__PURE__ */ new Map(), this._onceListeners = /* @__PURE__ */ new Map(), this._sortedCache = /* @__PURE__ */ new Map();
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Register an event listener
|
|
7
|
+
* @param {string} event - Event name
|
|
8
|
+
* @param {Function} callback - Callback function
|
|
9
|
+
* @param {number} priority - Higher priority = called first (default: 0)
|
|
10
|
+
* @returns {EventBus} - For chaining
|
|
11
|
+
*/
|
|
12
|
+
on(e, t, s = 0) {
|
|
13
|
+
return this._listeners.has(e) || this._listeners.set(e, /* @__PURE__ */ new Set()), this._listeners.get(e).add({ callback: t, priority: s }), this._sortedCache.delete(e), this;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Register a one-time event listener
|
|
17
|
+
* @param {string} event - Event name
|
|
18
|
+
* @param {Function} callback - Callback function
|
|
19
|
+
* @returns {EventBus} - For chaining
|
|
20
|
+
*/
|
|
21
|
+
once(e, t) {
|
|
22
|
+
return this._onceListeners.has(e) || this._onceListeners.set(e, /* @__PURE__ */ new Set()), this._onceListeners.get(e).add(t), this;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Remove an event listener
|
|
26
|
+
* @param {string} event - Event name
|
|
27
|
+
* @param {Function} callback - Callback function to remove
|
|
28
|
+
* @returns {EventBus} - For chaining
|
|
29
|
+
*/
|
|
30
|
+
off(e, t) {
|
|
31
|
+
const s = this._listeners.get(e);
|
|
32
|
+
if (s)
|
|
33
|
+
for (const r of [...s])
|
|
34
|
+
r.callback === t && (s.delete(r), this._sortedCache.delete(e));
|
|
35
|
+
const i = this._onceListeners.get(e);
|
|
36
|
+
return i && i.delete(t), this;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Emit an event to all registered listeners
|
|
40
|
+
* @param {string} event - Event name
|
|
41
|
+
* @param {*} data - Event data
|
|
42
|
+
*/
|
|
43
|
+
emit(e, t) {
|
|
44
|
+
const s = this._getSortedListeners(e);
|
|
45
|
+
for (let r = 0; r < s.length; r++)
|
|
46
|
+
this._invoke(s[r].callback, t, e);
|
|
47
|
+
const i = this._onceListeners.get(e);
|
|
48
|
+
if (i && i.size > 0) {
|
|
49
|
+
const r = [...i];
|
|
50
|
+
i.clear();
|
|
51
|
+
for (const n of r)
|
|
52
|
+
this._invoke(n, t, e);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Call one listener without letting it abort the dispatch.
|
|
57
|
+
*
|
|
58
|
+
* A throwing listener used to take out every lower-priority listener for
|
|
59
|
+
* that event, which on 'tick' means the game silently stops updating.
|
|
60
|
+
*
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
_invoke(e, t, s) {
|
|
64
|
+
try {
|
|
65
|
+
e(t);
|
|
66
|
+
} catch (i) {
|
|
67
|
+
console.error(`[DopamineJS] Listener for "${s}" threw:`, i);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get sorted listeners for an event (with caching)
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
_getSortedListeners(e) {
|
|
75
|
+
if (this._sortedCache.has(e))
|
|
76
|
+
return this._sortedCache.get(e);
|
|
77
|
+
const t = this._listeners.get(e);
|
|
78
|
+
if (!t || t.size === 0)
|
|
79
|
+
return [];
|
|
80
|
+
const s = Array.from(t).sort((i, r) => r.priority - i.priority);
|
|
81
|
+
return this._sortedCache.set(e, s), s;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Remove all listeners for an event, or all events if no event specified
|
|
85
|
+
* @param {string} [event] - Event name (optional)
|
|
86
|
+
*/
|
|
87
|
+
clear(e) {
|
|
88
|
+
e ? (this._listeners.delete(e), this._onceListeners.delete(e), this._sortedCache.delete(e)) : (this._listeners.clear(), this._onceListeners.clear(), this._sortedCache.clear());
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if an event has listeners
|
|
92
|
+
* @param {string} event - Event name
|
|
93
|
+
* @returns {boolean}
|
|
94
|
+
*/
|
|
95
|
+
hasListeners(e) {
|
|
96
|
+
const t = this._listeners.get(e), s = this._onceListeners.get(e);
|
|
97
|
+
return !!t?.size || !!s?.size;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
a.Events = {
|
|
101
|
+
// Core game loop events
|
|
102
|
+
TICK: "tick",
|
|
103
|
+
FIXED_UPDATE: "fixed_update",
|
|
104
|
+
RENDER: "render",
|
|
105
|
+
// Physics events
|
|
106
|
+
COLLISION_ENTER: "collision_enter",
|
|
107
|
+
COLLISION_EXIT: "collision_exit",
|
|
108
|
+
// Dopamine events (from RewardSystem)
|
|
109
|
+
XP_GAINED: "xp_gained",
|
|
110
|
+
LEVEL_UP: "level_up",
|
|
111
|
+
ACHIEVEMENT_UNLOCKED: "achievement_unlocked",
|
|
112
|
+
NEW_HIGH_SCORE: "new_high_score",
|
|
113
|
+
STREAK_UPDATED: "streak_updated",
|
|
114
|
+
// System lifecycle events
|
|
115
|
+
SYSTEM_REGISTERED: "system_registered",
|
|
116
|
+
SYSTEM_UNREGISTERED: "system_unregistered",
|
|
117
|
+
PLUGIN_LOADED: "plugin_loaded",
|
|
118
|
+
PLUGIN_UNLOADED: "plugin_unloaded"
|
|
119
|
+
};
|
|
120
|
+
class g {
|
|
121
|
+
constructor(e) {
|
|
122
|
+
this.kernel = e, this._systems = /* @__PURE__ */ new Map(), this._updateOrder = [], this._fixedUpdateOrder = [], this._needsRecompute = !1, this._failed = /* @__PURE__ */ new WeakSet();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Run one system's update without letting a throw stop the others.
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
_run(e, t, s) {
|
|
129
|
+
try {
|
|
130
|
+
e[t](s);
|
|
131
|
+
} catch (i) {
|
|
132
|
+
this._failed.has(e) || (this._failed.add(e), console.error(`[SystemRegistry] System ${t}() threw:`, i));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Register a system
|
|
137
|
+
* @param {string} name - Unique system name
|
|
138
|
+
* @param {Object} system - System instance (must implement ISystem interface)
|
|
139
|
+
* @param {Object} options - Configuration options
|
|
140
|
+
* @param {number} options.priority - Update priority (higher = earlier, default: 0)
|
|
141
|
+
* @param {string[]} options.dependencies - System names this depends on
|
|
142
|
+
* @returns {SystemRegistry} - For chaining
|
|
143
|
+
*/
|
|
144
|
+
register(e, t, { priority: s = 0, dependencies: i = [] } = {}) {
|
|
145
|
+
this._systems.has(e) && (console.warn(`[SystemRegistry] System "${e}" already registered. Replacing.`), this.unregister(e));
|
|
146
|
+
for (const r of i)
|
|
147
|
+
this._systems.has(r) || console.warn(`[SystemRegistry] Dependency "${r}" not found for system "${e}"`);
|
|
148
|
+
return this._systems.set(e, {
|
|
149
|
+
system: t,
|
|
150
|
+
priority: s,
|
|
151
|
+
dependencies: i
|
|
152
|
+
}), t.init && t.init(this.kernel), this._needsRecompute = !0, this.kernel.events.emit("system_registered", { name: e, system: t }), this;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get a system by name
|
|
156
|
+
* @param {string} name - System name
|
|
157
|
+
* @returns {Object|undefined} - System instance
|
|
158
|
+
*/
|
|
159
|
+
get(e) {
|
|
160
|
+
const t = this._systems.get(e);
|
|
161
|
+
return t ? t.system : void 0;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Check if a system is registered
|
|
165
|
+
* @param {string} name - System name
|
|
166
|
+
* @returns {boolean}
|
|
167
|
+
*/
|
|
168
|
+
has(e) {
|
|
169
|
+
return this._systems.has(e);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Unregister a system
|
|
173
|
+
* @param {string} name - System name
|
|
174
|
+
* @returns {boolean} - True if system was found and removed
|
|
175
|
+
*/
|
|
176
|
+
unregister(e) {
|
|
177
|
+
const t = this._systems.get(e);
|
|
178
|
+
if (!t)
|
|
179
|
+
return !1;
|
|
180
|
+
try {
|
|
181
|
+
t.system.destroy && t.system.destroy();
|
|
182
|
+
} finally {
|
|
183
|
+
this._systems.delete(e), this._needsRecompute = !0, this.kernel.events.emit("system_unregistered", { name: e });
|
|
184
|
+
}
|
|
185
|
+
return !0;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Update all systems (called each frame)
|
|
189
|
+
* @param {number} dt - Delta time in seconds
|
|
190
|
+
*/
|
|
191
|
+
update(e) {
|
|
192
|
+
this._needsRecompute && this._recomputeUpdateOrder();
|
|
193
|
+
for (let t = 0; t < this._updateOrder.length; t++) {
|
|
194
|
+
const s = this._updateOrder[t];
|
|
195
|
+
s.update && this._run(s, "update", e);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Fixed update for physics (called at fixed timestep)
|
|
200
|
+
* @param {number} dt - Fixed delta time
|
|
201
|
+
*/
|
|
202
|
+
fixedUpdate(e) {
|
|
203
|
+
this._needsRecompute && this._recomputeUpdateOrder();
|
|
204
|
+
for (let t = 0; t < this._fixedUpdateOrder.length; t++) {
|
|
205
|
+
const s = this._fixedUpdateOrder[t];
|
|
206
|
+
s.fixedUpdate && this._run(s, "fixedUpdate", e);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Recompute the update order based on dependencies and priorities
|
|
211
|
+
* Uses topological sort for dependencies
|
|
212
|
+
* @private
|
|
213
|
+
*/
|
|
214
|
+
_recomputeUpdateOrder() {
|
|
215
|
+
const e = Array.from(this._systems.entries()), t = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map();
|
|
216
|
+
for (const [n, o] of e)
|
|
217
|
+
t.set(n, o.dependencies), s.set(n, 0);
|
|
218
|
+
for (const [n, o] of t)
|
|
219
|
+
for (const c of o)
|
|
220
|
+
s.has(c) && s.set(n, s.get(n) + 1);
|
|
221
|
+
const i = [], r = [];
|
|
222
|
+
for (const [n, o] of s)
|
|
223
|
+
o === 0 && i.push(n);
|
|
224
|
+
for (; i.length > 0; ) {
|
|
225
|
+
i.sort((o, c) => {
|
|
226
|
+
const d = this._systems.get(o).priority;
|
|
227
|
+
return this._systems.get(c).priority - d;
|
|
228
|
+
});
|
|
229
|
+
const n = i.shift();
|
|
230
|
+
r.push(n);
|
|
231
|
+
for (const [o, c] of t)
|
|
232
|
+
if (c.includes(n)) {
|
|
233
|
+
const d = s.get(o) - 1;
|
|
234
|
+
s.set(o, d), d === 0 && i.push(o);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
r.length !== e.length && (console.error("[SystemRegistry] Circular dependency detected in systems!"), r.length = 0, r.push(...e.map(([n]) => n).sort((n, o) => this._systems.get(o).priority - this._systems.get(n).priority))), this._updateOrder = r.map((n) => this._systems.get(n).system).filter((n) => n.update), this._fixedUpdateOrder = r.map((n) => this._systems.get(n).system).filter((n) => n.fixedUpdate), this._needsRecompute = !1;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get all registered system names
|
|
241
|
+
* @returns {string[]}
|
|
242
|
+
*/
|
|
243
|
+
getSystemNames() {
|
|
244
|
+
return Array.from(this._systems.keys());
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Clear all systems
|
|
248
|
+
*/
|
|
249
|
+
clear() {
|
|
250
|
+
for (const e of this._systems.keys())
|
|
251
|
+
this.unregister(e);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
class p {
|
|
255
|
+
constructor(e) {
|
|
256
|
+
this.kernel = e, this._plugins = /* @__PURE__ */ new Map(), this._loadOrder = [], this._inFlight = /* @__PURE__ */ new Set();
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* @private
|
|
260
|
+
*/
|
|
261
|
+
_systemsSince(e) {
|
|
262
|
+
return this.kernel.systems.getSystemNames().filter((t) => !e.has(t));
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Mark systems as belonging to a plugin that loaded.
|
|
266
|
+
* @private
|
|
267
|
+
*/
|
|
268
|
+
_claim(e) {
|
|
269
|
+
for (const t of this._inFlight)
|
|
270
|
+
for (const s of e)
|
|
271
|
+
t.foreign.add(s);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Remove the systems a failed plugin registered. They would keep updating
|
|
275
|
+
* with nothing left to remove them.
|
|
276
|
+
* @private
|
|
277
|
+
*/
|
|
278
|
+
_rollback(e) {
|
|
279
|
+
for (const t of e)
|
|
280
|
+
try {
|
|
281
|
+
this.kernel.systems.unregister(t);
|
|
282
|
+
} catch (s) {
|
|
283
|
+
console.error(`[PluginRegistry] Error removing system "${t}":`, s);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Forget a plugin whose init failed.
|
|
288
|
+
*
|
|
289
|
+
* Removed by name: with two async inits in flight, the failed plugin is
|
|
290
|
+
* not necessarily the last entry.
|
|
291
|
+
* @private
|
|
292
|
+
*/
|
|
293
|
+
_forget(e) {
|
|
294
|
+
this._plugins.delete(e);
|
|
295
|
+
const t = this._loadOrder.indexOf(e);
|
|
296
|
+
t > -1 && this._loadOrder.splice(t, 1);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Register and initialize a plugin
|
|
300
|
+
*
|
|
301
|
+
* Plugin interface:
|
|
302
|
+
* {
|
|
303
|
+
* name: string,
|
|
304
|
+
* version?: string,
|
|
305
|
+
* init(kernel): void,
|
|
306
|
+
* destroy?(): void
|
|
307
|
+
* }
|
|
308
|
+
*
|
|
309
|
+
* @param {Object} plugin - Plugin instance
|
|
310
|
+
* @returns {PluginRegistry} - For chaining
|
|
311
|
+
*/
|
|
312
|
+
use(e) {
|
|
313
|
+
if (!e.name)
|
|
314
|
+
throw new Error("[PluginRegistry] Plugin must have a name");
|
|
315
|
+
if (this._plugins.has(e.name))
|
|
316
|
+
return console.warn(`[PluginRegistry] Plugin "${e.name}" already registered. Skipping.`), this;
|
|
317
|
+
if (!e.init || typeof e.init != "function")
|
|
318
|
+
throw new Error(`[PluginRegistry] Plugin "${e.name}" must have an init() method`);
|
|
319
|
+
this._plugins.set(e.name, e), this._loadOrder.push(e.name);
|
|
320
|
+
const t = new Set(this.kernel.systems.getSystemNames());
|
|
321
|
+
try {
|
|
322
|
+
e.init(this.kernel), this.kernel.events.emit("plugin_loaded", {
|
|
323
|
+
name: e.name,
|
|
324
|
+
version: e.version
|
|
325
|
+
}), this._claim(this._systemsSince(t));
|
|
326
|
+
} catch (s) {
|
|
327
|
+
throw console.error(`[PluginRegistry] Failed to initialize plugin "${e.name}":`, s), this._forget(e.name), this._rollback(this._systemsSince(t)), s;
|
|
328
|
+
}
|
|
329
|
+
return this;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Register and initialize a plugin asynchronously
|
|
333
|
+
* Useful for plugins that need to load external resources
|
|
334
|
+
*
|
|
335
|
+
* @param {Object} plugin - Plugin instance with async init
|
|
336
|
+
* @returns {Promise<PluginRegistry>} - For chaining
|
|
337
|
+
*/
|
|
338
|
+
async useAsync(e) {
|
|
339
|
+
if (!e.name)
|
|
340
|
+
throw new Error("[PluginRegistry] Plugin must have a name");
|
|
341
|
+
if (this._plugins.has(e.name))
|
|
342
|
+
return console.warn(`[PluginRegistry] Plugin "${e.name}" already registered. Skipping.`), this;
|
|
343
|
+
if (!e.init || typeof e.init != "function")
|
|
344
|
+
throw new Error(`[PluginRegistry] Plugin "${e.name}" must have an init() method`);
|
|
345
|
+
this._plugins.set(e.name, e), this._loadOrder.push(e.name);
|
|
346
|
+
const t = {
|
|
347
|
+
before: new Set(this.kernel.systems.getSystemNames()),
|
|
348
|
+
foreign: /* @__PURE__ */ new Set()
|
|
349
|
+
}, s = () => this._systemsSince(t.before).filter((i) => !t.foreign.has(i));
|
|
350
|
+
this._inFlight.add(t);
|
|
351
|
+
try {
|
|
352
|
+
await e.init(this.kernel), this._inFlight.delete(t), this._claim(s()), this.kernel.events.emit("plugin_loaded", {
|
|
353
|
+
name: e.name,
|
|
354
|
+
version: e.version
|
|
355
|
+
});
|
|
356
|
+
} catch (i) {
|
|
357
|
+
console.error(`[PluginRegistry] Failed to initialize plugin "${e.name}":`, i), this._inFlight.delete(t), this._forget(e.name);
|
|
358
|
+
const r = s();
|
|
359
|
+
throw this._inFlight.size === 0 ? this._rollback(r) : r.length > 0 && console.warn(`[PluginRegistry] Left systems in place after "${e.name}" failed: ${r.join(", ")}`), i;
|
|
360
|
+
}
|
|
361
|
+
return this;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Remove a plugin
|
|
365
|
+
* @param {string} name - Plugin name
|
|
366
|
+
* @returns {boolean} - True if plugin was found and removed
|
|
367
|
+
*/
|
|
368
|
+
remove(e) {
|
|
369
|
+
const t = this._plugins.get(e);
|
|
370
|
+
if (!t)
|
|
371
|
+
return !1;
|
|
372
|
+
if (t.destroy)
|
|
373
|
+
try {
|
|
374
|
+
t.destroy();
|
|
375
|
+
} catch (i) {
|
|
376
|
+
console.error(`[PluginRegistry] Error destroying plugin "${e}":`, i);
|
|
377
|
+
}
|
|
378
|
+
this._plugins.delete(e);
|
|
379
|
+
const s = this._loadOrder.indexOf(e);
|
|
380
|
+
return s > -1 && this._loadOrder.splice(s, 1), this.kernel.events.emit("plugin_unloaded", { name: e }), !0;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Get a plugin by name
|
|
384
|
+
* @param {string} name - Plugin name
|
|
385
|
+
* @returns {Object|undefined} - Plugin instance
|
|
386
|
+
*/
|
|
387
|
+
get(e) {
|
|
388
|
+
return this._plugins.get(e);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Check if a plugin is registered
|
|
392
|
+
* @param {string} name - Plugin name
|
|
393
|
+
* @returns {boolean}
|
|
394
|
+
*/
|
|
395
|
+
has(e) {
|
|
396
|
+
return this._plugins.has(e);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get all registered plugin names
|
|
400
|
+
* @returns {string[]}
|
|
401
|
+
*/
|
|
402
|
+
getPluginNames() {
|
|
403
|
+
return Array.from(this._plugins.keys());
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Get plugin load order
|
|
407
|
+
* @returns {string[]}
|
|
408
|
+
*/
|
|
409
|
+
getLoadOrder() {
|
|
410
|
+
return [...this._loadOrder];
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Clear all plugins (in reverse load order)
|
|
414
|
+
*/
|
|
415
|
+
clear() {
|
|
416
|
+
for (let e = this._loadOrder.length - 1; e >= 0; e--)
|
|
417
|
+
this.remove(this._loadOrder[e]);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
class k {
|
|
421
|
+
constructor(e = {}) {
|
|
422
|
+
this.width = e.width || 800, this.height = e.height || 600, this.backgroundColor = e.backgroundColor || "#000000", e.canvas ? (this.canvas = e.canvas, this.canvas.width = this.width, this.canvas.height = this.height) : (this.canvas = document.createElement("canvas"), this.canvas.width = this.width, this.canvas.height = this.height, (e.parent ? typeof e.parent == "string" ? document.querySelector(e.parent) : e.parent : document.body).appendChild(this.canvas)), this.ctx = this.canvas.getContext("2d");
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Clear the canvas.
|
|
426
|
+
*/
|
|
427
|
+
clear() {
|
|
428
|
+
this.ctx.fillStyle = this.backgroundColor, this.ctx.fillRect(0, 0, this.width, this.height);
|
|
429
|
+
}
|
|
430
|
+
resize(e, t) {
|
|
431
|
+
this.width = e, this.height = t, this.canvas.width = e, this.canvas.height = t;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
class w {
|
|
435
|
+
constructor() {
|
|
436
|
+
this.running = !1, this.lastTime = 0, this.callbacks = /* @__PURE__ */ new Set(), this._frameHandle = null, this._tick = this._tick.bind(this), this._failed = /* @__PURE__ */ new WeakSet();
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Add a callback to the loop.
|
|
440
|
+
* @param {Function} callback - Function to call each frame (receives dt).
|
|
441
|
+
*/
|
|
442
|
+
add(e) {
|
|
443
|
+
this.callbacks.add(e);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Remove a callback from the loop.
|
|
447
|
+
* @param {Function} callback
|
|
448
|
+
*/
|
|
449
|
+
remove(e) {
|
|
450
|
+
this.callbacks.delete(e);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Start the loop.
|
|
454
|
+
*/
|
|
455
|
+
start() {
|
|
456
|
+
this.running || (this.running = !0, this.lastTime = performance.now(), this._frameHandle !== null && cancelAnimationFrame(this._frameHandle), this._frameHandle = requestAnimationFrame(this._tick));
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Stop the loop.
|
|
460
|
+
*/
|
|
461
|
+
stop() {
|
|
462
|
+
this.running = !1, this._frameHandle !== null && (cancelAnimationFrame(this._frameHandle), this._frameHandle = null);
|
|
463
|
+
}
|
|
464
|
+
_tick(e) {
|
|
465
|
+
if (this._frameHandle = null, !this.running) return;
|
|
466
|
+
const t = (e - this.lastTime) / 1e3;
|
|
467
|
+
this.lastTime = e;
|
|
468
|
+
const s = Math.max(0, Math.min(t, 0.1));
|
|
469
|
+
for (const i of [...this.callbacks])
|
|
470
|
+
try {
|
|
471
|
+
i(s);
|
|
472
|
+
} catch (r) {
|
|
473
|
+
this._failed.has(i) || (this._failed.add(i), console.error("[Ticker] Callback threw:", r));
|
|
474
|
+
}
|
|
475
|
+
this.running && (this._frameHandle = requestAnimationFrame(this._tick));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
function u(h, e) {
|
|
479
|
+
let t = !1;
|
|
480
|
+
return new Proxy(h, {
|
|
481
|
+
get(s, i, r) {
|
|
482
|
+
t || (t = !0, console.warn(e));
|
|
483
|
+
const n = Reflect.get(s, i, r);
|
|
484
|
+
return typeof n == "function" ? n.bind(s) : n;
|
|
485
|
+
},
|
|
486
|
+
set(s, i, r) {
|
|
487
|
+
return t || (t = !0, console.warn(e)), Reflect.set(s, i, r);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
class l {
|
|
492
|
+
constructor() {
|
|
493
|
+
this.gameObject = null, this.kernel = null;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Access systems through kernel (no more globals!)
|
|
497
|
+
*/
|
|
498
|
+
get physics() {
|
|
499
|
+
return this.kernel?.systems.get("physics");
|
|
500
|
+
}
|
|
501
|
+
get input() {
|
|
502
|
+
return this.kernel?.systems.get("input");
|
|
503
|
+
}
|
|
504
|
+
get loader() {
|
|
505
|
+
return this.kernel?.systems.get("loader");
|
|
506
|
+
}
|
|
507
|
+
get events() {
|
|
508
|
+
return this.kernel?.events;
|
|
509
|
+
}
|
|
510
|
+
onAttach() {
|
|
511
|
+
}
|
|
512
|
+
onDetach() {
|
|
513
|
+
}
|
|
514
|
+
update(e) {
|
|
515
|
+
}
|
|
516
|
+
render(e) {
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
class b extends l {
|
|
520
|
+
constructor(e = "box", t = 50, s = 50, i = 25) {
|
|
521
|
+
super(), this.type = e, this.width = t, this.height = s, this.radius = i, this.tag = null;
|
|
522
|
+
}
|
|
523
|
+
get kernel() {
|
|
524
|
+
return this._kernel ?? null;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* The kernel can arrive after onAttach(), when the collider was added to
|
|
528
|
+
* an object before that object joined a scene. Registering only in
|
|
529
|
+
* onAttach() left such colliders out of physics for good.
|
|
530
|
+
*/
|
|
531
|
+
set kernel(e) {
|
|
532
|
+
e !== this._kernel && (this._unregister(), this._kernel = e, this._register());
|
|
533
|
+
}
|
|
534
|
+
onAttach() {
|
|
535
|
+
super.onAttach(), this._attached = !0, this._register();
|
|
536
|
+
}
|
|
537
|
+
onDetach() {
|
|
538
|
+
super.onDetach(), this._attached = !1, this._unregister();
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* @private
|
|
542
|
+
*/
|
|
543
|
+
_register() {
|
|
544
|
+
if (this._physics || !this._attached) return;
|
|
545
|
+
const e = this.physics;
|
|
546
|
+
e && (e.add(this), this._physics = e);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* @private
|
|
550
|
+
*/
|
|
551
|
+
_unregister() {
|
|
552
|
+
this._physics && (this._physics.remove(this), this._physics = null);
|
|
553
|
+
}
|
|
554
|
+
getBounds() {
|
|
555
|
+
const e = this.gameObject;
|
|
556
|
+
if (this.type === "box")
|
|
557
|
+
return {
|
|
558
|
+
left: e.x - this.width / 2,
|
|
559
|
+
right: e.x + this.width / 2,
|
|
560
|
+
top: e.y - this.height / 2,
|
|
561
|
+
bottom: e.y + this.height / 2
|
|
562
|
+
};
|
|
563
|
+
if (this.type === "circle")
|
|
564
|
+
return {
|
|
565
|
+
x: e.x,
|
|
566
|
+
y: e.y,
|
|
567
|
+
radius: this.radius
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
class m {
|
|
572
|
+
constructor() {
|
|
573
|
+
this.colliders = [], this.kernel = null;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* ISystem interface - called when registered
|
|
577
|
+
*/
|
|
578
|
+
init(e) {
|
|
579
|
+
this.kernel = e;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* ISystem interface - called at fixed timestep
|
|
583
|
+
*/
|
|
584
|
+
fixedUpdate(e) {
|
|
585
|
+
this.step();
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* ISystem interface - cleanup
|
|
589
|
+
*/
|
|
590
|
+
destroy() {
|
|
591
|
+
this.colliders = [];
|
|
592
|
+
}
|
|
593
|
+
add(e) {
|
|
594
|
+
this.colliders.push(e);
|
|
595
|
+
}
|
|
596
|
+
remove(e) {
|
|
597
|
+
const t = this.colliders.indexOf(e);
|
|
598
|
+
t > -1 && this.colliders.splice(t, 1);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Main Physics Step.
|
|
602
|
+
* Checks all pairs and triggers callbacks.
|
|
603
|
+
*/
|
|
604
|
+
step() {
|
|
605
|
+
for (let e = 0; e < this.colliders.length; e++)
|
|
606
|
+
for (let t = e + 1; t < this.colliders.length; t++) {
|
|
607
|
+
const s = this.colliders[e], i = this.colliders[t];
|
|
608
|
+
this._intersects(s, s.getBounds(), i, i.getBounds()) && (this._notifyCollision(s.gameObject, i.gameObject), this._notifyCollision(i.gameObject, s.gameObject));
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
_notifyCollision(e, t) {
|
|
612
|
+
!e || !e.components || e.components.forEach((s) => {
|
|
613
|
+
s.onCollisionEnter && s.onCollisionEnter(t);
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Check if a specific collider overlaps with anything in the target group (tag).
|
|
618
|
+
* @param {Collider} source
|
|
619
|
+
* @param {string} targetTag
|
|
620
|
+
*/
|
|
621
|
+
checkOverlap(e, t) {
|
|
622
|
+
const s = [], i = e.getBounds();
|
|
623
|
+
for (const r of this.colliders) {
|
|
624
|
+
if (r === e || t && r.tag !== t) continue;
|
|
625
|
+
const n = r.getBounds();
|
|
626
|
+
this._intersects(e, i, r, n) && s.push(r);
|
|
627
|
+
}
|
|
628
|
+
return s;
|
|
629
|
+
}
|
|
630
|
+
_intersects(e, t, s, i) {
|
|
631
|
+
if (e.type === "box" && s.type === "box")
|
|
632
|
+
return t.left < i.right && t.right > i.left && t.top < i.bottom && t.bottom > i.top;
|
|
633
|
+
if (e.type === "circle" && s.type === "circle") {
|
|
634
|
+
const r = t.x - i.x, n = t.y - i.y;
|
|
635
|
+
return Math.sqrt(r * r + n * n) < t.radius + i.radius;
|
|
636
|
+
}
|
|
637
|
+
return !1;
|
|
638
|
+
}
|
|
639
|
+
// IPhysicsSystem interface methods (for plugin compatibility)
|
|
640
|
+
addBody(e) {
|
|
641
|
+
this.add(e);
|
|
642
|
+
}
|
|
643
|
+
removeBody(e) {
|
|
644
|
+
this.remove(e);
|
|
645
|
+
}
|
|
646
|
+
checkCollision(e, t) {
|
|
647
|
+
return this._intersects(e, e.getBounds(), t, t.getBounds());
|
|
648
|
+
}
|
|
649
|
+
raycast(e, t, s) {
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
setGravity(e, t) {
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
const O = u(
|
|
656
|
+
new m(),
|
|
657
|
+
"[DopamineJS] GlobalPhysics is deprecated. Use kernel.physics instead."
|
|
658
|
+
);
|
|
659
|
+
class f {
|
|
660
|
+
constructor() {
|
|
661
|
+
this.keys = /* @__PURE__ */ new Map(), this.mouse = { x: 0, y: 0, buttons: /* @__PURE__ */ new Map() }, this.kernel = null, this._onKeyDown = this._onKeyDown.bind(this), this._onKeyUp = this._onKeyUp.bind(this), this._onMouseMove = this._onMouseMove.bind(this), this._onMouseDown = this._onMouseDown.bind(this), this._onMouseUp = this._onMouseUp.bind(this);
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* ISystem interface - called when registered
|
|
665
|
+
*/
|
|
666
|
+
init(e) {
|
|
667
|
+
this.kernel = e, window.addEventListener("keydown", this._onKeyDown), window.addEventListener("keyup", this._onKeyUp), window.addEventListener("mousemove", this._onMouseMove), window.addEventListener("mousedown", this._onMouseDown), window.addEventListener("mouseup", this._onMouseUp);
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* ISystem interface - cleanup
|
|
671
|
+
*/
|
|
672
|
+
destroy() {
|
|
673
|
+
window.removeEventListener("keydown", this._onKeyDown), window.removeEventListener("keyup", this._onKeyUp), window.removeEventListener("mousemove", this._onMouseMove), window.removeEventListener("mousedown", this._onMouseDown), window.removeEventListener("mouseup", this._onMouseUp);
|
|
674
|
+
}
|
|
675
|
+
_onKeyDown(e) {
|
|
676
|
+
this.keys.set(e.key, !0);
|
|
677
|
+
}
|
|
678
|
+
_onKeyUp(e) {
|
|
679
|
+
this.keys.set(e.key, !1);
|
|
680
|
+
}
|
|
681
|
+
_onMouseMove(e) {
|
|
682
|
+
this.mouse.x = e.clientX, this.mouse.y = e.clientY;
|
|
683
|
+
}
|
|
684
|
+
_onMouseDown(e) {
|
|
685
|
+
this.mouse.buttons.set(e.button, !0);
|
|
686
|
+
}
|
|
687
|
+
_onMouseUp(e) {
|
|
688
|
+
this.mouse.buttons.set(e.button, !1);
|
|
689
|
+
}
|
|
690
|
+
isKeyDown(e) {
|
|
691
|
+
return this.keys.get(e) || !1;
|
|
692
|
+
}
|
|
693
|
+
isMouseButtonDown(e = 0) {
|
|
694
|
+
return this.mouse.buttons.get(e) || !1;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const M = u(
|
|
698
|
+
new f(),
|
|
699
|
+
"[DopamineJS] GlobalInput is deprecated. Use kernel.input instead."
|
|
700
|
+
);
|
|
701
|
+
class y {
|
|
702
|
+
constructor() {
|
|
703
|
+
this.assets = /* @__PURE__ */ new Map(), this.kernel = null;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* ISystem interface - called when registered
|
|
707
|
+
*/
|
|
708
|
+
init(e) {
|
|
709
|
+
this.kernel = e;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* ISystem interface - cleanup
|
|
713
|
+
*/
|
|
714
|
+
destroy() {
|
|
715
|
+
this.assets.clear();
|
|
716
|
+
}
|
|
717
|
+
async loadImage(e, t) {
|
|
718
|
+
return new Promise((s, i) => {
|
|
719
|
+
const r = new Image();
|
|
720
|
+
r.onload = () => {
|
|
721
|
+
this.assets.set(e, r), s(r);
|
|
722
|
+
}, r.onerror = i, r.src = t;
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
get(e) {
|
|
726
|
+
return this.assets.get(e);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
const C = u(
|
|
730
|
+
new y(),
|
|
731
|
+
"[DopamineJS] GlobalLoader is deprecated. Use kernel.loader instead."
|
|
732
|
+
);
|
|
733
|
+
class x {
|
|
734
|
+
constructor(e = {}) {
|
|
735
|
+
this.config = e, this._boundUpdate = this._update.bind(this), this.events = new a(), this.systems = new g(this), this.plugins = new p(this), this._registerCoreSystems(e), this.fixedTimestep = e.fixedTimestep || 1 / 60, this._accumulator = 0, this._maxAccumulator = this.fixedTimestep * 5;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Register core engine systems
|
|
739
|
+
* These can be replaced by plugins
|
|
740
|
+
* @private
|
|
741
|
+
*/
|
|
742
|
+
_registerCoreSystems(e) {
|
|
743
|
+
this.systems.register("ticker", new w(), {
|
|
744
|
+
priority: 100
|
|
745
|
+
});
|
|
746
|
+
const t = {
|
|
747
|
+
width: e.width,
|
|
748
|
+
height: e.height,
|
|
749
|
+
backgroundColor: e.backgroundColor,
|
|
750
|
+
canvas: e.canvas,
|
|
751
|
+
...e.renderer
|
|
752
|
+
};
|
|
753
|
+
this.systems.register("renderer", new k(t), {
|
|
754
|
+
priority: 90
|
|
755
|
+
}), this.systems.register("input", new f(), {
|
|
756
|
+
priority: 80
|
|
757
|
+
}), this.systems.register("loader", new y(), {
|
|
758
|
+
priority: 70
|
|
759
|
+
}), this.systems.register("physics", new m(), {
|
|
760
|
+
priority: 60
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Start the kernel (begins game loop)
|
|
765
|
+
*/
|
|
766
|
+
start() {
|
|
767
|
+
const e = this.systems.get("ticker");
|
|
768
|
+
e && (e.add(this._boundUpdate), e.start()), this.events.emit("kernel_started");
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Stop the kernel
|
|
772
|
+
*/
|
|
773
|
+
stop() {
|
|
774
|
+
const e = this.systems.get("ticker");
|
|
775
|
+
e && e.stop(), this.events.emit("kernel_stopped");
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Main update loop
|
|
779
|
+
* @private
|
|
780
|
+
*/
|
|
781
|
+
_update(e) {
|
|
782
|
+
for (this.events.emit(a.Events.TICK, { dt: e }), this._accumulator += e, this._accumulator > this._maxAccumulator && (this._accumulator = this._maxAccumulator); this._accumulator >= this.fixedTimestep; )
|
|
783
|
+
this.events.emit(a.Events.FIXED_UPDATE, { dt: this.fixedTimestep }), this.systems.fixedUpdate(this.fixedTimestep), this._accumulator -= this.fixedTimestep;
|
|
784
|
+
this.systems.update(e), this.events.emit(a.Events.RENDER, { dt: e });
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Shorthand accessors for common systems (backward compatibility)
|
|
788
|
+
*/
|
|
789
|
+
get physics() {
|
|
790
|
+
return this.systems.get("physics");
|
|
791
|
+
}
|
|
792
|
+
get input() {
|
|
793
|
+
return this.systems.get("input");
|
|
794
|
+
}
|
|
795
|
+
get loader() {
|
|
796
|
+
return this.systems.get("loader");
|
|
797
|
+
}
|
|
798
|
+
get renderer() {
|
|
799
|
+
return this.systems.get("renderer");
|
|
800
|
+
}
|
|
801
|
+
get ticker() {
|
|
802
|
+
return this.systems.get("ticker");
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Destroy the kernel and cleanup all systems
|
|
806
|
+
*/
|
|
807
|
+
destroy() {
|
|
808
|
+
this.stop();
|
|
809
|
+
const e = this.systems.get("ticker");
|
|
810
|
+
e && e.remove(this._boundUpdate), this.plugins.clear(), this.systems.clear(), this.events.clear();
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
class D {
|
|
814
|
+
constructor() {
|
|
815
|
+
this.gameObjects = [], this._kernel = null;
|
|
816
|
+
}
|
|
817
|
+
get kernel() {
|
|
818
|
+
return this._kernel;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Objects added before the scene had a kernel receive it here. Scenes are
|
|
822
|
+
* often built in a constructor, before Game.setScene() injects the kernel.
|
|
823
|
+
* The Director sets it to null on exit, which takes colliders out of
|
|
824
|
+
* physics until the scene runs again.
|
|
825
|
+
*/
|
|
826
|
+
set kernel(e) {
|
|
827
|
+
this._kernel = e;
|
|
828
|
+
for (const t of this.gameObjects)
|
|
829
|
+
t.kernel = e;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Called when the scene is added to the Game.
|
|
833
|
+
*/
|
|
834
|
+
onEnter() {
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Called when the scene is removed from the Game.
|
|
838
|
+
*/
|
|
839
|
+
onExit() {
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Add a GameObject to the scene.
|
|
843
|
+
* @param {GameObject} gameObject
|
|
844
|
+
*/
|
|
845
|
+
add(e) {
|
|
846
|
+
return this.kernel && (e.kernel = this.kernel), e._detached && (e._detached = !1, this._eachComponent(e, (t) => t.onAttach())), this.gameObjects.push(e), e;
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Remove a GameObject from the scene.
|
|
850
|
+
* @param {GameObject} gameObject
|
|
851
|
+
*/
|
|
852
|
+
remove(e) {
|
|
853
|
+
const t = this.gameObjects.indexOf(e);
|
|
854
|
+
t > -1 && (this.gameObjects.splice(t, 1), e._detached = !0, this._eachComponent(e, (s) => s.onDetach()));
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Visit the components of an object and of all its children.
|
|
858
|
+
* @private
|
|
859
|
+
*/
|
|
860
|
+
_eachComponent(e, t) {
|
|
861
|
+
for (const s of [...e.components])
|
|
862
|
+
t(s);
|
|
863
|
+
for (const s of e.children)
|
|
864
|
+
this._eachComponent(s, t);
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Update loop for the scene.
|
|
868
|
+
* @param {number} dt
|
|
869
|
+
*/
|
|
870
|
+
update(e) {
|
|
871
|
+
for (const t of [...this.gameObjects])
|
|
872
|
+
t._detached || t.update(e);
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Render loop for the scene.
|
|
876
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
877
|
+
*/
|
|
878
|
+
render(e) {
|
|
879
|
+
for (const t of [...this.gameObjects])
|
|
880
|
+
t._detached || t.render(e);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
class v {
|
|
884
|
+
constructor(e) {
|
|
885
|
+
this.game = e, this.currentScene = null;
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Switch to a new scene.
|
|
889
|
+
* @param {Scene} scene
|
|
890
|
+
*/
|
|
891
|
+
run(e) {
|
|
892
|
+
this.currentScene && (this.currentScene.onExit(), this.currentScene.kernel = null), this.currentScene = e, this.currentScene.game = this.game, this.game?.kernel && (e.kernel = this.game.kernel), this.game.scene = e, e.onEnter();
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
class L {
|
|
896
|
+
constructor(e = {}) {
|
|
897
|
+
this.config = e, this.kernel = new x(e), this.director = new v(this), this.kernel.systems.register("director", this.director), this.kernel.events.on(a.Events.TICK, this._update.bind(this), 10), this.kernel.events.on(a.Events.RENDER, this._render.bind(this), 10);
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Backward compatibility accessors
|
|
901
|
+
*/
|
|
902
|
+
get renderer() {
|
|
903
|
+
return this.kernel.renderer;
|
|
904
|
+
}
|
|
905
|
+
get ticker() {
|
|
906
|
+
return this.kernel.ticker;
|
|
907
|
+
}
|
|
908
|
+
get physics() {
|
|
909
|
+
return this.kernel.physics;
|
|
910
|
+
}
|
|
911
|
+
get input() {
|
|
912
|
+
return this.kernel.input;
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Start the game loop.
|
|
916
|
+
*/
|
|
917
|
+
start() {
|
|
918
|
+
this.kernel.start();
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Stop the game loop.
|
|
922
|
+
*/
|
|
923
|
+
stop() {
|
|
924
|
+
this.kernel.stop();
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Switch to a new scene.
|
|
928
|
+
* @param {Scene} newScene
|
|
929
|
+
*/
|
|
930
|
+
setScene(e) {
|
|
931
|
+
e.kernel = this.kernel, this.director.run(e);
|
|
932
|
+
}
|
|
933
|
+
_update({ dt: e }) {
|
|
934
|
+
this.director.currentScene && this.director.currentScene.update(e);
|
|
935
|
+
}
|
|
936
|
+
_render({ dt: e }) {
|
|
937
|
+
this.renderer.clear(), this.director.currentScene && this.director.currentScene.render(this.renderer.ctx);
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Destroy the game and cleanup
|
|
941
|
+
*/
|
|
942
|
+
destroy() {
|
|
943
|
+
this.kernel.destroy();
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
class R {
|
|
947
|
+
constructor(e = 0, t = 0) {
|
|
948
|
+
this.x = e, this.y = t, this.rotation = 0, this.scale = { x: 1, y: 1 }, this.components = [], this.children = [], this.parent = null, this._kernel = null;
|
|
949
|
+
}
|
|
950
|
+
get kernel() {
|
|
951
|
+
return this._kernel;
|
|
952
|
+
}
|
|
953
|
+
set kernel(e) {
|
|
954
|
+
this._kernel = e;
|
|
955
|
+
for (const t of this.components)
|
|
956
|
+
t.kernel = e;
|
|
957
|
+
for (const t of this.children)
|
|
958
|
+
t.kernel = e;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Add a child object.
|
|
962
|
+
* @param {GameObject} child
|
|
963
|
+
*/
|
|
964
|
+
addChild(e) {
|
|
965
|
+
return e.parent = this, e.kernel = this.kernel, this.children.push(e), e;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Add a component.
|
|
969
|
+
* @param {Component} component
|
|
970
|
+
*/
|
|
971
|
+
addComponent(e) {
|
|
972
|
+
return e.gameObject = this, e.kernel = this.kernel, this.components.push(e), e.onAttach(), e;
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Get a component by class type.
|
|
976
|
+
* @param {class} type
|
|
977
|
+
*/
|
|
978
|
+
getComponent(e) {
|
|
979
|
+
return this.components.find((t) => t instanceof e);
|
|
980
|
+
}
|
|
981
|
+
update(e) {
|
|
982
|
+
for (const t of this.components)
|
|
983
|
+
t.update && t.update(e);
|
|
984
|
+
for (const t of this.children)
|
|
985
|
+
t.update(e);
|
|
986
|
+
}
|
|
987
|
+
render(e) {
|
|
988
|
+
e.save(), e.translate(this.x, this.y), e.rotate(this.rotation), e.scale(this.scale.x, this.scale.y);
|
|
989
|
+
for (const t of this.components)
|
|
990
|
+
t.render && t.render(e);
|
|
991
|
+
for (const t of this.children)
|
|
992
|
+
t.render(e);
|
|
993
|
+
e.restore();
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
class _ {
|
|
997
|
+
constructor(e = 0, t = 0) {
|
|
998
|
+
this.x = e, this.y = t;
|
|
999
|
+
}
|
|
1000
|
+
add(e) {
|
|
1001
|
+
return this.x += e.x, this.y += e.y, this;
|
|
1002
|
+
}
|
|
1003
|
+
sub(e) {
|
|
1004
|
+
return this.x -= e.x, this.y -= e.y, this;
|
|
1005
|
+
}
|
|
1006
|
+
scale(e) {
|
|
1007
|
+
return this.x *= e, this.y *= e, this;
|
|
1008
|
+
}
|
|
1009
|
+
distance(e) {
|
|
1010
|
+
const t = this.x - e.x, s = this.y - e.y;
|
|
1011
|
+
return Math.sqrt(t * t + s * s);
|
|
1012
|
+
}
|
|
1013
|
+
normalize() {
|
|
1014
|
+
const e = Math.sqrt(this.x * this.x + this.y * this.y);
|
|
1015
|
+
return e > 0 && (this.x /= e, this.y /= e), this;
|
|
1016
|
+
}
|
|
1017
|
+
clone() {
|
|
1018
|
+
return new _(this.x, this.y);
|
|
1019
|
+
}
|
|
1020
|
+
static distance(e, t) {
|
|
1021
|
+
return Math.sqrt(Math.pow(t.x - e.x, 2) + Math.pow(t.y - e.y, 2));
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
class E extends l {
|
|
1025
|
+
constructor(e) {
|
|
1026
|
+
super(), this.image = e, this.anchor = { x: 0.5, y: 0.5 }, this.width = e ? e.width : 0, this.height = e ? e.height : 0, this.frame = { x: 0, y: 0, w: this.width, h: this.height };
|
|
1027
|
+
}
|
|
1028
|
+
setFrame(e, t, s, i) {
|
|
1029
|
+
this.frame = { x: e, y: t, w: s, h: i }, this.width = s, this.height = i;
|
|
1030
|
+
}
|
|
1031
|
+
setTexture(e) {
|
|
1032
|
+
this.image = e, e && this.setFrame(0, 0, e.width, e.height);
|
|
1033
|
+
}
|
|
1034
|
+
render(e) {
|
|
1035
|
+
if (!this.image) return;
|
|
1036
|
+
const t = this.frame.w, s = this.frame.h, i = -t * this.anchor.x, r = -s * this.anchor.y;
|
|
1037
|
+
e.drawImage(
|
|
1038
|
+
this.image,
|
|
1039
|
+
this.frame.x,
|
|
1040
|
+
this.frame.y,
|
|
1041
|
+
t,
|
|
1042
|
+
s,
|
|
1043
|
+
// Source
|
|
1044
|
+
i,
|
|
1045
|
+
r,
|
|
1046
|
+
t,
|
|
1047
|
+
s
|
|
1048
|
+
// Destination
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
class P extends l {
|
|
1053
|
+
constructor() {
|
|
1054
|
+
super(), this.animations = {}, this.currentAnim = null, this.timer = 0, this.currentFrameIndex = 0, this.sprite = null;
|
|
1055
|
+
}
|
|
1056
|
+
onAttach() {
|
|
1057
|
+
this.sprite = this.gameObject.getComponent(E), this.sprite || console.warn("Animator attached to GameObject without a Sprite component.");
|
|
1058
|
+
}
|
|
1059
|
+
addAnimation(e, t, s = 10, i = !0) {
|
|
1060
|
+
this.animations[e] = { frames: t, fps: s, loop: i };
|
|
1061
|
+
}
|
|
1062
|
+
play(e) {
|
|
1063
|
+
this.currentAnim && this.currentAnim.name === e || this.animations[e] && (this.currentAnim = { name: e, ...this.animations[e] }, this.currentFrameIndex = 0, this.timer = 0, this._updateFrame());
|
|
1064
|
+
}
|
|
1065
|
+
update(e) {
|
|
1066
|
+
if (!this.currentAnim || !this.sprite) return;
|
|
1067
|
+
this.timer += e;
|
|
1068
|
+
const t = 1 / this.currentAnim.fps;
|
|
1069
|
+
this.timer >= t && (this.timer -= t, this.currentFrameIndex++, this.currentFrameIndex >= this.currentAnim.frames.length && (this.currentAnim.loop ? this.currentFrameIndex = 0 : this.currentFrameIndex = this.currentAnim.frames.length - 1), this._updateFrame());
|
|
1070
|
+
}
|
|
1071
|
+
_updateFrame() {
|
|
1072
|
+
const e = this.currentAnim.frames[this.currentFrameIndex];
|
|
1073
|
+
this.sprite.setFrame(e.x, e.y, e.w, e.h);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
class U extends l {
|
|
1077
|
+
constructor(e) {
|
|
1078
|
+
super(), this.system = e;
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Trigger an effect at the GameObject's current position.
|
|
1082
|
+
* @param {string} effectName - 'confetti', 'fire', 'sparkle', etc.
|
|
1083
|
+
* @param {object} options - override options
|
|
1084
|
+
*/
|
|
1085
|
+
play(e, t = {}) {
|
|
1086
|
+
const s = this.gameObject.x, i = this.gameObject.y;
|
|
1087
|
+
switch (e) {
|
|
1088
|
+
case "confetti":
|
|
1089
|
+
this.system.confetti(s, i, t.count);
|
|
1090
|
+
break;
|
|
1091
|
+
case "fire":
|
|
1092
|
+
this.system.fire(s, i, t.count);
|
|
1093
|
+
break;
|
|
1094
|
+
case "sparkle":
|
|
1095
|
+
this.system.sparkle(s, i, t.count, t.color);
|
|
1096
|
+
break;
|
|
1097
|
+
case "blood":
|
|
1098
|
+
this.system.emit({
|
|
1099
|
+
x: s,
|
|
1100
|
+
y: i,
|
|
1101
|
+
count: 10,
|
|
1102
|
+
color: ["#8a0303", "#ff0000"],
|
|
1103
|
+
speed: 4,
|
|
1104
|
+
gravity: 0.1,
|
|
1105
|
+
decay: 0.05
|
|
1106
|
+
});
|
|
1107
|
+
break;
|
|
1108
|
+
default:
|
|
1109
|
+
this.system.emit({
|
|
1110
|
+
x: s,
|
|
1111
|
+
y: i,
|
|
1112
|
+
...t
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
class A extends l {
|
|
1118
|
+
constructor() {
|
|
1119
|
+
super(), this.intensity = 0, this.duration = 0, this.timer = 0, this.originalPos = { x: 0, y: 0 }, this.isShaking = !1;
|
|
1120
|
+
}
|
|
1121
|
+
shake(e = 5, t = 0.2) {
|
|
1122
|
+
this.isShaking || (this.originalPos.x = this.gameObject.x, this.originalPos.y = this.gameObject.y), this.intensity = e, this.duration = t, this.timer = 0, this.isShaking = !0;
|
|
1123
|
+
}
|
|
1124
|
+
update(e) {
|
|
1125
|
+
if (!this.isShaking) return;
|
|
1126
|
+
if (this.timer += e, this.timer >= this.duration) {
|
|
1127
|
+
this.isShaking = !1, this.gameObject.x = this.originalPos.x, this.gameObject.y = this.originalPos.y;
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
const t = (Math.random() - 0.5) * 2 * this.intensity, s = (Math.random() - 0.5) * 2 * this.intensity;
|
|
1131
|
+
this.gameObject.x = this.originalPos.x + t, this.gameObject.y = this.originalPos.y + s;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
export {
|
|
1135
|
+
P as Animator,
|
|
1136
|
+
b as Collider,
|
|
1137
|
+
l as Component,
|
|
1138
|
+
v as Director,
|
|
1139
|
+
x as DopamineKernel,
|
|
1140
|
+
a as EventBus,
|
|
1141
|
+
L as Game,
|
|
1142
|
+
R as GameObject,
|
|
1143
|
+
M as GlobalInput,
|
|
1144
|
+
C as GlobalLoader,
|
|
1145
|
+
O as GlobalPhysics,
|
|
1146
|
+
f as Input,
|
|
1147
|
+
y as Loader,
|
|
1148
|
+
U as ParticleEmitter,
|
|
1149
|
+
m as Physics,
|
|
1150
|
+
p as PluginRegistry,
|
|
1151
|
+
k as Renderer,
|
|
1152
|
+
D as Scene,
|
|
1153
|
+
A as ScreenShake,
|
|
1154
|
+
E as Sprite,
|
|
1155
|
+
g as SystemRegistry,
|
|
1156
|
+
w as Ticker,
|
|
1157
|
+
_ as Vector2
|
|
1158
|
+
};
|