dopaminejs 1.2.0 → 2.0.0

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.
Files changed (45) hide show
  1. package/dist/dopamine.js +1947 -0
  2. package/dist/dopamine.umd.cjs +45 -0
  3. package/package.json +24 -32
  4. package/src/core/Animator.js +62 -0
  5. package/src/core/Collider.js +50 -0
  6. package/src/core/Component.js +38 -0
  7. package/src/core/DopamineKernel.js +153 -0
  8. package/src/core/EventBus.js +183 -0
  9. package/src/core/Game.js +89 -0
  10. package/src/core/GameObject.js +83 -0
  11. package/src/core/PluginRegistry.js +197 -0
  12. package/src/core/Scene.js +65 -0
  13. package/src/core/Sprite.js +51 -0
  14. package/src/core/SystemRegistry.js +242 -0
  15. package/src/core/Vector2.js +50 -0
  16. package/src/{audio → dopamine/audio}/SoundManager.js +1 -3
  17. package/src/dopamine/components/ParticleEmitter.js +50 -0
  18. package/src/dopamine/components/ScreenShake.js +49 -0
  19. package/src/dopamine/components/index.js +4 -0
  20. package/src/dopamine/effects/WebGLParticleSystem.js +348 -0
  21. package/src/dopamine/index.js +62 -0
  22. package/src/dopamine/utils/TextureGenerator.js +90 -0
  23. package/src/index.js +34 -62
  24. package/src/interfaces/IAudioSystem.js +58 -0
  25. package/src/interfaces/IParticleSystem.js +51 -0
  26. package/src/interfaces/IPhysicsSystem.js +52 -0
  27. package/src/interfaces/ISystem.js +48 -0
  28. package/src/interfaces/index.js +7 -0
  29. package/src/renderer/Renderer.js +37 -0
  30. package/src/systems/Director.js +27 -0
  31. package/src/systems/Input.js +77 -0
  32. package/src/systems/Loader.js +44 -0
  33. package/src/systems/Physics.js +117 -0
  34. package/src/systems/Ticker.js +61 -0
  35. package/ARCHITECTURE.md +0 -71
  36. package/CHANGELOG.md +0 -36
  37. package/README.md +0 -264
  38. package/dist/dopamine.mjs +0 -737
  39. package/dist/dopamine.umd.js +0 -45
  40. /package/src/{core → dopamine/core}/DataService.js +0 -0
  41. /package/src/{core → dopamine/core}/EventEmitter.js +0 -0
  42. /package/src/{core → dopamine/core}/RewardSystem.js +0 -0
  43. /package/src/{effects → dopamine/effects}/ParticleSystem.js +0 -0
  44. /package/src/{ui → dopamine/ui}/GameUI.js +0 -0
  45. /package/src/{ui → dopamine/ui}/dopamine.css +0 -0
@@ -0,0 +1,242 @@
1
+ /**
2
+ * SystemRegistry - Manages system lifecycle and dependency resolution
3
+ *
4
+ * Systems are registered with names and can have dependencies on other systems.
5
+ * The registry ensures systems are updated in the correct order based on dependencies.
6
+ */
7
+ export class SystemRegistry {
8
+ constructor(kernel) {
9
+ this.kernel = kernel;
10
+
11
+ // Map<systemName, { system, priority, dependencies }>
12
+ this._systems = new Map();
13
+
14
+ // Pre-computed update order for performance
15
+ this._updateOrder = [];
16
+ this._fixedUpdateOrder = [];
17
+
18
+ // Flag to track if order needs recomputation
19
+ this._needsRecompute = false;
20
+ }
21
+
22
+ /**
23
+ * Register a system
24
+ * @param {string} name - Unique system name
25
+ * @param {Object} system - System instance (must implement ISystem interface)
26
+ * @param {Object} options - Configuration options
27
+ * @param {number} options.priority - Update priority (higher = earlier, default: 0)
28
+ * @param {string[]} options.dependencies - System names this depends on
29
+ * @returns {SystemRegistry} - For chaining
30
+ */
31
+ register(name, system, { priority = 0, dependencies = [] } = {}) {
32
+ if (this._systems.has(name)) {
33
+ console.warn(`[SystemRegistry] System "${name}" already registered. Replacing.`);
34
+ this.unregister(name);
35
+ }
36
+
37
+ // Validate dependencies exist
38
+ for (const dep of dependencies) {
39
+ if (!this._systems.has(dep)) {
40
+ console.warn(`[SystemRegistry] Dependency "${dep}" not found for system "${name}"`);
41
+ }
42
+ }
43
+
44
+ // Store system metadata
45
+ this._systems.set(name, {
46
+ system,
47
+ priority,
48
+ dependencies,
49
+ });
50
+
51
+ // Initialize the system
52
+ if (system.init) {
53
+ system.init(this.kernel);
54
+ }
55
+
56
+ // Mark for recomputation
57
+ this._needsRecompute = true;
58
+
59
+ // Emit event
60
+ this.kernel.events.emit('system_registered', { name, system });
61
+
62
+ return this;
63
+ }
64
+
65
+ /**
66
+ * Get a system by name
67
+ * @param {string} name - System name
68
+ * @returns {Object|undefined} - System instance
69
+ */
70
+ get(name) {
71
+ const entry = this._systems.get(name);
72
+ return entry ? entry.system : undefined;
73
+ }
74
+
75
+ /**
76
+ * Check if a system is registered
77
+ * @param {string} name - System name
78
+ * @returns {boolean}
79
+ */
80
+ has(name) {
81
+ return this._systems.has(name);
82
+ }
83
+
84
+ /**
85
+ * Unregister a system
86
+ * @param {string} name - System name
87
+ * @returns {boolean} - True if system was found and removed
88
+ */
89
+ unregister(name) {
90
+ const entry = this._systems.get(name);
91
+ if (!entry) {
92
+ return false;
93
+ }
94
+
95
+ // Call destroy if available
96
+ if (entry.system.destroy) {
97
+ entry.system.destroy();
98
+ }
99
+
100
+ this._systems.delete(name);
101
+ this._needsRecompute = true;
102
+
103
+ // Emit event
104
+ this.kernel.events.emit('system_unregistered', { name });
105
+
106
+ return true;
107
+ }
108
+
109
+ /**
110
+ * Update all systems (called each frame)
111
+ * @param {number} dt - Delta time in seconds
112
+ */
113
+ update(dt) {
114
+ if (this._needsRecompute) {
115
+ this._recomputeUpdateOrder();
116
+ }
117
+
118
+ // Fast iteration - no lookups, just array access
119
+ for (let i = 0; i < this._updateOrder.length; i++) {
120
+ const system = this._updateOrder[i];
121
+ if (system.update) {
122
+ system.update(dt);
123
+ }
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Fixed update for physics (called at fixed timestep)
129
+ * @param {number} dt - Fixed delta time
130
+ */
131
+ fixedUpdate(dt) {
132
+ if (this._needsRecompute) {
133
+ this._recomputeUpdateOrder();
134
+ }
135
+
136
+ for (let i = 0; i < this._fixedUpdateOrder.length; i++) {
137
+ const system = this._fixedUpdateOrder[i];
138
+ if (system.fixedUpdate) {
139
+ system.fixedUpdate(dt);
140
+ }
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Recompute the update order based on dependencies and priorities
146
+ * Uses topological sort for dependencies
147
+ * @private
148
+ */
149
+ _recomputeUpdateOrder() {
150
+ const systems = Array.from(this._systems.entries());
151
+
152
+ // Build dependency graph
153
+ const graph = new Map();
154
+ const inDegree = new Map();
155
+
156
+ for (const [name, entry] of systems) {
157
+ graph.set(name, entry.dependencies);
158
+ inDegree.set(name, 0);
159
+ }
160
+
161
+ // Calculate in-degrees
162
+ for (const [name, deps] of graph) {
163
+ for (const dep of deps) {
164
+ if (inDegree.has(dep)) {
165
+ inDegree.set(name, inDegree.get(name) + 1);
166
+ }
167
+ }
168
+ }
169
+
170
+ // Topological sort (Kahn's algorithm)
171
+ const queue = [];
172
+ const sorted = [];
173
+
174
+ // Start with systems that have no dependencies
175
+ for (const [name, degree] of inDegree) {
176
+ if (degree === 0) {
177
+ queue.push(name);
178
+ }
179
+ }
180
+
181
+ while (queue.length > 0) {
182
+ // Sort queue by priority before processing
183
+ queue.sort((a, b) => {
184
+ const priorityA = this._systems.get(a).priority;
185
+ const priorityB = this._systems.get(b).priority;
186
+ return priorityB - priorityA; // Higher priority first
187
+ });
188
+
189
+ const name = queue.shift();
190
+ sorted.push(name);
191
+
192
+ // Reduce in-degree for dependent systems
193
+ for (const [otherName, deps] of graph) {
194
+ if (deps.includes(name)) {
195
+ const newDegree = inDegree.get(otherName) - 1;
196
+ inDegree.set(otherName, newDegree);
197
+ if (newDegree === 0) {
198
+ queue.push(otherName);
199
+ }
200
+ }
201
+ }
202
+ }
203
+
204
+ // Check for circular dependencies
205
+ if (sorted.length !== systems.length) {
206
+ console.error('[SystemRegistry] Circular dependency detected in systems!');
207
+ // Fallback: just use priority order
208
+ sorted.length = 0;
209
+ sorted.push(...systems.map(([name]) => name).sort((a, b) => {
210
+ return this._systems.get(b).priority - this._systems.get(a).priority;
211
+ }));
212
+ }
213
+
214
+ // Convert to system instances
215
+ this._updateOrder = sorted
216
+ .map(name => this._systems.get(name).system)
217
+ .filter(system => system.update);
218
+
219
+ this._fixedUpdateOrder = sorted
220
+ .map(name => this._systems.get(name).system)
221
+ .filter(system => system.fixedUpdate);
222
+
223
+ this._needsRecompute = false;
224
+ }
225
+
226
+ /**
227
+ * Get all registered system names
228
+ * @returns {string[]}
229
+ */
230
+ getSystemNames() {
231
+ return Array.from(this._systems.keys());
232
+ }
233
+
234
+ /**
235
+ * Clear all systems
236
+ */
237
+ clear() {
238
+ for (const name of this._systems.keys()) {
239
+ this.unregister(name);
240
+ }
241
+ }
242
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * 2D Vector class for position, velocity, and physics calculations.
3
+ */
4
+ export class Vector2 {
5
+ constructor(x = 0, y = 0) {
6
+ this.x = x;
7
+ this.y = y;
8
+ }
9
+
10
+ add(v) {
11
+ this.x += v.x;
12
+ this.y += v.y;
13
+ return this;
14
+ }
15
+
16
+ sub(v) {
17
+ this.x -= v.x;
18
+ this.y -= v.y;
19
+ return this;
20
+ }
21
+
22
+ scale(s) {
23
+ this.x *= s;
24
+ this.y *= s;
25
+ return this;
26
+ }
27
+
28
+ distance(v) {
29
+ const dx = this.x - v.x;
30
+ const dy = this.y - v.y;
31
+ return Math.sqrt(dx * dx + dy * dy);
32
+ }
33
+
34
+ normalize() {
35
+ const len = Math.sqrt(this.x * this.x + this.y * this.y);
36
+ if (len > 0) {
37
+ this.x /= len;
38
+ this.y /= len;
39
+ }
40
+ return this;
41
+ }
42
+
43
+ clone() {
44
+ return new Vector2(this.x, this.y);
45
+ }
46
+
47
+ static distance(a, b) {
48
+ return Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));
49
+ }
50
+ }
@@ -11,7 +11,6 @@ export class SoundManager {
11
11
 
12
12
  // Asset management
13
13
  this.assets = new Map(); // key -> AudioBuffer
14
- this.soundPack = config.soundPack || 'synth'; // 'synth' or custom
15
14
  this.customSounds = config.customSounds || {}; // key -> url
16
15
 
17
16
  // Preload custom sounds if provided
@@ -111,9 +110,8 @@ export class SoundManager {
111
110
  }
112
111
 
113
112
  // 3. Fallback to synth based on key name
114
- // Map common keys to synth methods
115
113
  const synthMap = {
116
- 'jump': () => this.playJump(true), // true = force synth
114
+ 'jump': () => this.playJump(true),
117
115
  'score': () => this.playScore(true),
118
116
  'success': () => this.playSuccess(true),
119
117
  'click': () => this.playClick(true),
@@ -0,0 +1,50 @@
1
+ import { Component } from '../../core/Component.js';
2
+
3
+ /**
4
+ * Wraps the Dopamine ParticleSystem for a GameObject.
5
+ * Allows easy "Emit at my position" logic.
6
+ */
7
+ export class ParticleEmitter extends Component {
8
+ constructor(particleSystem) {
9
+ super();
10
+ this.system = particleSystem;
11
+ }
12
+
13
+ /**
14
+ * Trigger an effect at the GameObject's current position.
15
+ * @param {string} effectName - 'confetti', 'fire', 'sparkle', etc.
16
+ * @param {object} options - override options
17
+ */
18
+ play(effectName, options = {}) {
19
+ const x = this.gameObject.x;
20
+ const y = this.gameObject.y;
21
+
22
+ switch (effectName) {
23
+ case 'confetti':
24
+ this.system.confetti(x, y, options.count);
25
+ break;
26
+ case 'fire':
27
+ this.system.fire(x, y, options.count);
28
+ break;
29
+ case 'sparkle':
30
+ this.system.sparkle(x, y, options.count, options.color);
31
+ break;
32
+ case 'blood': // Custom shortcut for our game
33
+ this.system.emit({
34
+ x, y,
35
+ count: 10,
36
+ color: ['#8a0303', '#ff0000'],
37
+ speed: 4,
38
+ gravity: 0.1,
39
+ decay: 0.05
40
+ });
41
+ break;
42
+ default:
43
+ // Direct emit fallback
44
+ this.system.emit({
45
+ x, y,
46
+ ...options
47
+ });
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,49 @@
1
+ import { Component } from '../../core/Component.js';
2
+
3
+ /**
4
+ * Adds screen shake capability.
5
+ * Usually attached to the Camera or the Root Scene object.
6
+ */
7
+ export class ScreenShake extends Component {
8
+ constructor() {
9
+ super();
10
+ this.intensity = 0;
11
+ this.duration = 0;
12
+ this.timer = 0;
13
+ this.originalPos = { x: 0, y: 0 };
14
+ this.isShaking = false;
15
+ }
16
+
17
+ shake(intensity = 5, duration = 0.2) {
18
+ this.intensity = intensity;
19
+ this.duration = duration;
20
+ this.timer = 0;
21
+ this.isShaking = true;
22
+
23
+ // Save original if not already shaking
24
+ if (this.timer === 0) {
25
+ this.originalPos.x = this.gameObject.x;
26
+ this.originalPos.y = this.gameObject.y;
27
+ }
28
+ }
29
+
30
+ update(dt) {
31
+ if (!this.isShaking) return;
32
+
33
+ this.timer += dt;
34
+
35
+ if (this.timer >= this.duration) {
36
+ this.isShaking = false;
37
+ this.gameObject.x = this.originalPos.x;
38
+ this.gameObject.y = this.originalPos.y;
39
+ return;
40
+ }
41
+
42
+ // Random offset
43
+ const offsetX = (Math.random() - 0.5) * 2 * this.intensity;
44
+ const offsetY = (Math.random() - 0.5) * 2 * this.intensity;
45
+
46
+ this.gameObject.x = this.originalPos.x + offsetX;
47
+ this.gameObject.y = this.originalPos.y + offsetY;
48
+ }
49
+ }
@@ -0,0 +1,4 @@
1
+ import Dopamine from '../index.js';
2
+
3
+ export { ParticleEmitter } from './ParticleEmitter.js';
4
+ export { ScreenShake } from './ScreenShake.js';