dopaminejs 1.2.0 → 2.0.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/dist/dopamine.js +1947 -0
- package/dist/dopamine.umd.cjs +45 -0
- package/package.json +25 -33
- package/src/core/Animator.js +62 -0
- package/src/core/Collider.js +50 -0
- package/src/core/Component.js +38 -0
- package/src/core/DopamineKernel.js +153 -0
- package/src/core/EventBus.js +183 -0
- package/src/core/Game.js +89 -0
- package/src/core/GameObject.js +83 -0
- package/src/core/PluginRegistry.js +197 -0
- package/src/core/Scene.js +65 -0
- package/src/core/Sprite.js +51 -0
- package/src/core/SystemRegistry.js +242 -0
- package/src/core/Vector2.js +50 -0
- package/src/{audio → dopamine/audio}/SoundManager.js +1 -3
- package/src/dopamine/components/ParticleEmitter.js +50 -0
- package/src/dopamine/components/ScreenShake.js +49 -0
- package/src/dopamine/components/index.js +4 -0
- package/src/dopamine/effects/WebGLParticleSystem.js +348 -0
- package/src/dopamine/index.js +62 -0
- package/src/dopamine/utils/TextureGenerator.js +90 -0
- package/src/index.js +34 -62
- package/src/interfaces/IAudioSystem.js +58 -0
- package/src/interfaces/IParticleSystem.js +51 -0
- package/src/interfaces/IPhysicsSystem.js +52 -0
- package/src/interfaces/ISystem.js +48 -0
- package/src/interfaces/index.js +7 -0
- package/src/renderer/Renderer.js +37 -0
- package/src/systems/Director.js +27 -0
- package/src/systems/Input.js +77 -0
- package/src/systems/Loader.js +44 -0
- package/src/systems/Physics.js +117 -0
- package/src/systems/Ticker.js +61 -0
- package/ARCHITECTURE.md +0 -71
- package/CHANGELOG.md +0 -36
- package/README.md +0 -264
- package/dist/dopamine.mjs +0 -737
- package/dist/dopamine.umd.js +0 -45
- /package/src/{core → dopamine/core}/DataService.js +0 -0
- /package/src/{core → dopamine/core}/EventEmitter.js +0 -0
- /package/src/{core → dopamine/core}/RewardSystem.js +0 -0
- /package/src/{effects → dopamine/effects}/ParticleSystem.js +0 -0
- /package/src/{ui → dopamine/ui}/GameUI.js +0 -0
- /package/src/{ui → dopamine/ui}/dopamine.css +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ISystem } from './ISystem.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IParticleSystem - Interface for particle system implementations
|
|
5
|
+
*
|
|
6
|
+
* Allows different renderers (Canvas, WebGL, custom) to be swapped
|
|
7
|
+
*/
|
|
8
|
+
export const IParticleSystem = {
|
|
9
|
+
...ISystem,
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Register a sprite for particles
|
|
13
|
+
* @param {string} key - Sprite identifier
|
|
14
|
+
* @param {string} url - Path to image
|
|
15
|
+
*/
|
|
16
|
+
registerSprite(key, url) { },
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Register a custom effect
|
|
20
|
+
* @param {string} name - Effect name
|
|
21
|
+
* @param {Function} callback - Effect function (x, y, ...args) => void
|
|
22
|
+
*/
|
|
23
|
+
registerEffect(name, callback) { },
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Play a registered effect
|
|
27
|
+
* @param {string} name - Effect name
|
|
28
|
+
* @param {number} x - X position
|
|
29
|
+
* @param {number} y - Y position
|
|
30
|
+
* @param {...any} args - Additional arguments
|
|
31
|
+
*/
|
|
32
|
+
play(name, x, y, ...args) { },
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Emit particles with custom configuration
|
|
36
|
+
* @param {Object} config - Particle configuration
|
|
37
|
+
*/
|
|
38
|
+
emit(config) { },
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Clear all particles
|
|
42
|
+
*/
|
|
43
|
+
clear() { },
|
|
44
|
+
|
|
45
|
+
// Built-in effects (optional, but recommended)
|
|
46
|
+
confetti(x, y, count) { },
|
|
47
|
+
coinShower(x, y, count) { },
|
|
48
|
+
sparkle(x, y, count, color) { },
|
|
49
|
+
fire(x, y, count) { },
|
|
50
|
+
starBurst(x, y, count) { },
|
|
51
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ISystem } from './ISystem.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IPhysicsSystem - Interface for physics implementations
|
|
5
|
+
*
|
|
6
|
+
* Allows different physics engines (custom, Matter.js, Box2D) to be swapped
|
|
7
|
+
*/
|
|
8
|
+
export const IPhysicsSystem = {
|
|
9
|
+
...ISystem,
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Add a physics body to the simulation
|
|
13
|
+
* @param {Object} body - Physics body (implementation-specific)
|
|
14
|
+
*/
|
|
15
|
+
addBody(body) { },
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Remove a physics body from the simulation
|
|
19
|
+
* @param {Object} body - Physics body to remove
|
|
20
|
+
*/
|
|
21
|
+
removeBody(body) { },
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check collision between two bodies
|
|
25
|
+
* @param {Object} a - First body
|
|
26
|
+
* @param {Object} b - Second body
|
|
27
|
+
* @returns {boolean} - True if colliding
|
|
28
|
+
*/
|
|
29
|
+
checkCollision(a, b) { },
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Perform a raycast
|
|
33
|
+
* @param {Object} origin - Ray origin {x, y}
|
|
34
|
+
* @param {Object} direction - Ray direction {x, y}
|
|
35
|
+
* @param {number} distance - Maximum ray distance
|
|
36
|
+
* @returns {Object|null} - Hit result or null
|
|
37
|
+
*/
|
|
38
|
+
raycast(origin, direction, distance) { },
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Set gravity for the physics world
|
|
42
|
+
* @param {number} x - Gravity X component
|
|
43
|
+
* @param {number} y - Gravity Y component
|
|
44
|
+
*/
|
|
45
|
+
setGravity(x, y) { },
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Step the physics simulation
|
|
49
|
+
* @param {number} dt - Delta time
|
|
50
|
+
*/
|
|
51
|
+
step(dt) { },
|
|
52
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISystem - Base interface for all systems
|
|
3
|
+
*
|
|
4
|
+
* All systems should implement this interface.
|
|
5
|
+
* Methods are optional - only implement what you need.
|
|
6
|
+
*/
|
|
7
|
+
export const ISystem = {
|
|
8
|
+
/**
|
|
9
|
+
* Called when system is registered to the kernel
|
|
10
|
+
* @param {DopamineKernel} kernel - The kernel instance
|
|
11
|
+
*/
|
|
12
|
+
init(kernel) { },
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Called each frame (variable timestep)
|
|
16
|
+
* @param {number} dt - Delta time in seconds
|
|
17
|
+
*/
|
|
18
|
+
update(dt) { },
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Called at fixed timestep (for physics)
|
|
22
|
+
* @param {number} dt - Fixed delta time
|
|
23
|
+
*/
|
|
24
|
+
fixedUpdate(dt) { },
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Called when system is unregistered
|
|
28
|
+
*/
|
|
29
|
+
destroy() { },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Base System class that implements ISystem
|
|
34
|
+
* Systems can extend this for convenience
|
|
35
|
+
*/
|
|
36
|
+
export class System {
|
|
37
|
+
constructor() {
|
|
38
|
+
this.kernel = null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
init(kernel) {
|
|
42
|
+
this.kernel = kernel;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
update(dt) { }
|
|
46
|
+
fixedUpdate(dt) { }
|
|
47
|
+
destroy() { }
|
|
48
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles the Canvas and Context for rendering.
|
|
3
|
+
*/
|
|
4
|
+
export class Renderer {
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this.width = options.width || 800;
|
|
7
|
+
this.height = options.height || 600;
|
|
8
|
+
this.backgroundColor = options.backgroundColor || '#000000';
|
|
9
|
+
|
|
10
|
+
this.canvas = document.createElement('canvas');
|
|
11
|
+
this.canvas.width = this.width;
|
|
12
|
+
this.canvas.height = this.height;
|
|
13
|
+
this.ctx = this.canvas.getContext('2d');
|
|
14
|
+
|
|
15
|
+
// Append to body or specified container
|
|
16
|
+
const parent = options.parent ?
|
|
17
|
+
(typeof options.parent === 'string' ? document.querySelector(options.parent) : options.parent)
|
|
18
|
+
: document.body;
|
|
19
|
+
|
|
20
|
+
parent.appendChild(this.canvas);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Clear the canvas.
|
|
25
|
+
*/
|
|
26
|
+
clear() {
|
|
27
|
+
this.ctx.fillStyle = this.backgroundColor;
|
|
28
|
+
this.ctx.fillRect(0, 0, this.width, this.height);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
resize(width, height) {
|
|
32
|
+
this.width = width;
|
|
33
|
+
this.height = height;
|
|
34
|
+
this.canvas.width = width;
|
|
35
|
+
this.canvas.height = height;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages Scene transitions and state.
|
|
3
|
+
*/
|
|
4
|
+
export class Director {
|
|
5
|
+
constructor(game) {
|
|
6
|
+
this.game = game;
|
|
7
|
+
this.currentScene = null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Switch to a new scene.
|
|
12
|
+
* @param {Scene} scene
|
|
13
|
+
*/
|
|
14
|
+
run(scene) {
|
|
15
|
+
if (this.currentScene) {
|
|
16
|
+
this.currentScene.onExit();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
this.currentScene = scene;
|
|
20
|
+
this.currentScene.game = this.game;
|
|
21
|
+
|
|
22
|
+
// Let the game know (if game engine needs to reference it directly)
|
|
23
|
+
this.game.scene = scene;
|
|
24
|
+
|
|
25
|
+
scene.onEnter();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input System
|
|
3
|
+
* Tracks keyboard and mouse input.
|
|
4
|
+
*/
|
|
5
|
+
export class Input {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.keys = new Map();
|
|
8
|
+
this.mouse = { x: 0, y: 0, buttons: new Map() };
|
|
9
|
+
this.kernel = null;
|
|
10
|
+
|
|
11
|
+
// Bind event listeners
|
|
12
|
+
this._onKeyDown = this._onKeyDown.bind(this);
|
|
13
|
+
this._onKeyUp = this._onKeyUp.bind(this);
|
|
14
|
+
this._onMouseMove = this._onMouseMove.bind(this);
|
|
15
|
+
this._onMouseDown = this._onMouseDown.bind(this);
|
|
16
|
+
this._onMouseUp = this._onMouseUp.bind(this);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ISystem interface - called when registered
|
|
21
|
+
*/
|
|
22
|
+
init(kernel) {
|
|
23
|
+
this.kernel = kernel;
|
|
24
|
+
|
|
25
|
+
// Attach event listeners
|
|
26
|
+
window.addEventListener('keydown', this._onKeyDown);
|
|
27
|
+
window.addEventListener('keyup', this._onKeyUp);
|
|
28
|
+
window.addEventListener('mousemove', this._onMouseMove);
|
|
29
|
+
window.addEventListener('mousedown', this._onMouseDown);
|
|
30
|
+
window.addEventListener('mouseup', this._onMouseUp);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* ISystem interface - cleanup
|
|
35
|
+
*/
|
|
36
|
+
destroy() {
|
|
37
|
+
window.removeEventListener('keydown', this._onKeyDown);
|
|
38
|
+
window.removeEventListener('keyup', this._onKeyUp);
|
|
39
|
+
window.removeEventListener('mousemove', this._onMouseMove);
|
|
40
|
+
window.removeEventListener('mousedown', this._onMouseDown);
|
|
41
|
+
window.removeEventListener('mouseup', this._onMouseUp);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_onKeyDown(e) {
|
|
45
|
+
this.keys.set(e.key, true);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_onKeyUp(e) {
|
|
49
|
+
this.keys.set(e.key, false);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_onMouseMove(e) {
|
|
53
|
+
this.mouse.x = e.clientX;
|
|
54
|
+
this.mouse.y = e.clientY;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_onMouseDown(e) {
|
|
58
|
+
this.mouse.buttons.set(e.button, true);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_onMouseUp(e) {
|
|
62
|
+
this.mouse.buttons.set(e.button, false);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
isKeyDown(key) {
|
|
66
|
+
return this.keys.get(key) || false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
isMouseButtonDown(button = 0) {
|
|
70
|
+
return this.mouse.buttons.get(button) || false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// DEPRECATED: Keep for backward compatibility
|
|
75
|
+
export const GlobalInput = new Input();
|
|
76
|
+
// Note: GlobalInput won't have listeners attached unless init() is called
|
|
77
|
+
console.warn('[DopamineJS] GlobalInput is deprecated. Use kernel.input instead.');
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset Loader
|
|
3
|
+
* Loads and caches images, audio, etc.
|
|
4
|
+
*/
|
|
5
|
+
export class Loader {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.assets = new Map();
|
|
8
|
+
this.kernel = null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ISystem interface - called when registered
|
|
13
|
+
*/
|
|
14
|
+
init(kernel) {
|
|
15
|
+
this.kernel = kernel;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* ISystem interface - cleanup
|
|
20
|
+
*/
|
|
21
|
+
destroy() {
|
|
22
|
+
this.assets.clear();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async loadImage(key, url) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const img = new Image();
|
|
28
|
+
img.onload = () => {
|
|
29
|
+
this.assets.set(key, img);
|
|
30
|
+
resolve(img);
|
|
31
|
+
};
|
|
32
|
+
img.onerror = reject;
|
|
33
|
+
img.src = url;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get(key) {
|
|
38
|
+
return this.assets.get(key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// DEPRECATED: Keep for backward compatibility
|
|
43
|
+
export const GlobalLoader = new Loader();
|
|
44
|
+
console.warn('[DopamineJS] GlobalLoader is deprecated. Use kernel.loader instead.');
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Collider } from '../core/Collider.js';
|
|
2
|
+
|
|
3
|
+
export class Physics {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.colliders = [];
|
|
6
|
+
this.kernel = null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ISystem interface - called when registered
|
|
11
|
+
*/
|
|
12
|
+
init(kernel) {
|
|
13
|
+
this.kernel = kernel;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* ISystem interface - called at fixed timestep
|
|
18
|
+
*/
|
|
19
|
+
fixedUpdate(dt) {
|
|
20
|
+
this.step();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* ISystem interface - cleanup
|
|
25
|
+
*/
|
|
26
|
+
destroy() {
|
|
27
|
+
this.colliders = [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
add(collider) {
|
|
31
|
+
this.colliders.push(collider);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
remove(collider) {
|
|
35
|
+
const idx = this.colliders.indexOf(collider);
|
|
36
|
+
if (idx > -1) this.colliders.splice(idx, 1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Main Physics Step.
|
|
41
|
+
* Checks all pairs and triggers callbacks.
|
|
42
|
+
*/
|
|
43
|
+
step() {
|
|
44
|
+
// Naive O(N^2)
|
|
45
|
+
for (let i = 0; i < this.colliders.length; i++) {
|
|
46
|
+
for (let j = i + 1; j < this.colliders.length; j++) {
|
|
47
|
+
const a = this.colliders[i];
|
|
48
|
+
const b = this.colliders[j];
|
|
49
|
+
|
|
50
|
+
if (this._intersects(a, a.getBounds(), b, b.getBounds())) {
|
|
51
|
+
// Notify GameObjects
|
|
52
|
+
this._notifyCollision(a.gameObject, b.gameObject);
|
|
53
|
+
this._notifyCollision(b.gameObject, a.gameObject);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_notifyCollision(go, other) {
|
|
60
|
+
if (!go || !go.components) return;
|
|
61
|
+
go.components.forEach(c => {
|
|
62
|
+
if (c.onCollisionEnter) c.onCollisionEnter(other);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if a specific collider overlaps with anything in the target group (tag).
|
|
68
|
+
* @param {Collider} source
|
|
69
|
+
* @param {string} targetTag
|
|
70
|
+
*/
|
|
71
|
+
checkOverlap(source, targetTag) {
|
|
72
|
+
// ... (Keep existing manual check for flexibility)
|
|
73
|
+
const hits = [];
|
|
74
|
+
const sourceBounds = source.getBounds();
|
|
75
|
+
|
|
76
|
+
for (const target of this.colliders) {
|
|
77
|
+
if (target === source) continue;
|
|
78
|
+
if (targetTag && target.tag !== targetTag) continue;
|
|
79
|
+
|
|
80
|
+
const targetBounds = target.getBounds();
|
|
81
|
+
|
|
82
|
+
if (this._intersects(source, sourceBounds, target, targetBounds)) {
|
|
83
|
+
hits.push(target);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return hits;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
_intersects(a, bA, b, bB) {
|
|
90
|
+
// Box vs Box
|
|
91
|
+
if (a.type === 'box' && b.type === 'box') {
|
|
92
|
+
return (bA.left < bB.right && bA.right > bB.left &&
|
|
93
|
+
bA.top < bB.bottom && bA.bottom > bB.top);
|
|
94
|
+
}
|
|
95
|
+
// Circle vs Circle
|
|
96
|
+
if (a.type === 'circle' && b.type === 'circle') {
|
|
97
|
+
const dx = bA.x - bB.x;
|
|
98
|
+
const dy = bA.y - bB.y;
|
|
99
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
100
|
+
return dist < (bA.radius + bB.radius);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// TODO: Box vs Circle
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// IPhysicsSystem interface methods (for plugin compatibility)
|
|
108
|
+
addBody(body) { this.add(body); }
|
|
109
|
+
removeBody(body) { this.remove(body); }
|
|
110
|
+
checkCollision(a, b) { return this._intersects(a, a.getBounds(), b, b.getBounds()); }
|
|
111
|
+
raycast(origin, direction, distance) { /* TODO */ return null; }
|
|
112
|
+
setGravity(x, y) { /* TODO: Add gravity support */ }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// DEPRECATED: Keep for backward compatibility, but log warning
|
|
116
|
+
export const GlobalPhysics = new Physics();
|
|
117
|
+
console.warn('[DopamineJS] GlobalPhysics is deprecated. Use kernel.physics instead.');
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Ticker class manages the game loop.
|
|
3
|
+
* It uses requestAnimationFrame to provide a smooth loop.
|
|
4
|
+
*/
|
|
5
|
+
export class Ticker {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.running = false;
|
|
8
|
+
this.lastTime = 0;
|
|
9
|
+
this.callbacks = new Set();
|
|
10
|
+
this._tick = this._tick.bind(this);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Add a callback to the loop.
|
|
15
|
+
* @param {Function} callback - Function to call each frame (receives dt).
|
|
16
|
+
*/
|
|
17
|
+
add(callback) {
|
|
18
|
+
this.callbacks.add(callback);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Remove a callback from the loop.
|
|
23
|
+
* @param {Function} callback
|
|
24
|
+
*/
|
|
25
|
+
remove(callback) {
|
|
26
|
+
this.callbacks.delete(callback);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Start the loop.
|
|
31
|
+
*/
|
|
32
|
+
start() {
|
|
33
|
+
if (this.running) return;
|
|
34
|
+
this.running = true;
|
|
35
|
+
this.lastTime = performance.now();
|
|
36
|
+
requestAnimationFrame(this._tick);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Stop the loop.
|
|
41
|
+
*/
|
|
42
|
+
stop() {
|
|
43
|
+
this.running = false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_tick(time) {
|
|
47
|
+
if (!this.running) return;
|
|
48
|
+
|
|
49
|
+
const dt = (time - this.lastTime) / 1000; // Delta time in seconds
|
|
50
|
+
this.lastTime = time;
|
|
51
|
+
|
|
52
|
+
// Cap dt to prevent huge jumps if tab was inactive
|
|
53
|
+
const safeDt = Math.min(dt, 0.1);
|
|
54
|
+
|
|
55
|
+
for (const callback of this.callbacks) {
|
|
56
|
+
callback(safeDt);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
requestAnimationFrame(this._tick);
|
|
60
|
+
}
|
|
61
|
+
}
|
package/ARCHITECTURE.md
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# DopamineJS Architecture & Roadmap 🏗️
|
|
2
|
-
|
|
3
|
-
**Vision**: To become the standard "Game Feel Engine" for the web—a plug-and-play layer that handles the "juice" (feedback, rewards, satisfaction) so developers can focus on core mechanics.
|
|
4
|
-
|
|
5
|
-
## 🧠 Design Philosophy
|
|
6
|
-
1. **Zero-Config Start**: It must work beautifully out of the box.
|
|
7
|
-
2. **Infinite Extensibility**: Every default (sound, particle, UI style) must be replaceable.
|
|
8
|
-
3. **Asset Agnostic**: Support synthesized sounds/shapes (no assets) AND custom assets (mp3/png).
|
|
9
|
-
4. **AI-First**: APIs should be self-documenting and predictable for AI agents to use effectively.
|
|
10
|
-
|
|
11
|
-
## 🗺️ Roadmap
|
|
12
|
-
|
|
13
|
-
### Phase 1: Audio Extensibility (v1.1.0)
|
|
14
|
-
**Goal**: Allow developers to bring their own soundscapes.
|
|
15
|
-
|
|
16
|
-
- **[NEW] Asset Loader**: A lightweight preloader for audio files.
|
|
17
|
-
- **[UPDATE] SoundManager**:
|
|
18
|
-
- Add `registerSound(key, url)` method.
|
|
19
|
-
- Support `Howler.js` integration (optional) or robust Web Audio buffer caching.
|
|
20
|
-
- **Feature**: "Sound Packs" - Allow switching between 'Retro', 'Modern', 'Cute' preset packs.
|
|
21
|
-
|
|
22
|
-
### Phase 2: Visual Customization (v1.2.0)
|
|
23
|
-
**Goal**: Break free from geometric primitives.
|
|
24
|
-
|
|
25
|
-
- **[UPDATE] ParticleSystem**:
|
|
26
|
-
- **Sprite Support**: Allow `image` or `sprite` properties in particle config.
|
|
27
|
-
- **Custom Emitters**: Define custom particle behaviors (gravity, velocity, life, decay) via JSON config.
|
|
28
|
-
- **Editor**: (Long term) A web-based particle editor that exports JSON for DopamineJS.
|
|
29
|
-
|
|
30
|
-
### Phase 3: UI Theming & Templates (v1.3.0)
|
|
31
|
-
**Goal**: Make the UI fit any game art style.
|
|
32
|
-
|
|
33
|
-
- **[NEW] Theme Engine**:
|
|
34
|
-
- Use CSS Variables for all colors, fonts, and spacing.
|
|
35
|
-
- `Dopamine.setTheme('dark-cyberpunk')`
|
|
36
|
-
- **[UPDATE] GameUI**:
|
|
37
|
-
- **Icon Sets**: Allow passing an icon map (SVG strings or URLs) to replace default emojis.
|
|
38
|
-
- **Slots**: Allow developers to inject custom HTML into notifications or level-up screens.
|
|
39
|
-
|
|
40
|
-
### Phase 4: The "Dopamine Ecosystem" (v2.0.0)
|
|
41
|
-
**Goal**: Community-driven content.
|
|
42
|
-
|
|
43
|
-
- **Plugin System**: Middleware for the `RewardSystem` (e.g., "BattlePass Plugin", "Leaderboard Plugin").
|
|
44
|
-
- **Backend Integration**: Webhooks for `onLevelUp` or `onAchievement` to validate rewards on a server.
|
|
45
|
-
|
|
46
|
-
## 📝 Developer Guide: How to Extend (Current Best Practices)
|
|
47
|
-
|
|
48
|
-
While we build these features, here is how you can extend DopamineJS today:
|
|
49
|
-
|
|
50
|
-
### Custom Achievements
|
|
51
|
-
You can already define any achievement logic you want:
|
|
52
|
-
|
|
53
|
-
```javascript
|
|
54
|
-
achievements: {
|
|
55
|
-
'die_100_times': {
|
|
56
|
-
name: 'You Tried',
|
|
57
|
-
icon: '<img src="skull.png" width="20">', // HTML is supported in icons!
|
|
58
|
-
check: (p) => p.stats.deaths >= 100
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Custom CSS
|
|
64
|
-
Override the default styles by adding your own CSS *after* importing DopamineJS:
|
|
65
|
-
|
|
66
|
-
```css
|
|
67
|
-
/* Override the XP bar color */
|
|
68
|
-
.xp-bar-fill {
|
|
69
|
-
background: linear-gradient(90deg, #ff00cc, #3333ff) !important;
|
|
70
|
-
}
|
|
71
|
-
```
|
package/CHANGELOG.md
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
## [1.2.0] - 2025-12-04
|
|
6
|
-
### Added
|
|
7
|
-
- **Visual Customization**:
|
|
8
|
-
- `registerSprite(key, url)`: Use custom images for particles.
|
|
9
|
-
- `emit(config)`: Create fully custom particle explosions.
|
|
10
|
-
- `registerEffect(name, callback)`: Define reusable custom effects.
|
|
11
|
-
- **Multi-Screen Support**: Pass `container` selector to `ParticleSystem` to target specific elements.
|
|
12
|
-
- **Optimization**: Implemented Object Pooling to reduce garbage collection and improve performance.
|
|
13
|
-
|
|
14
|
-
## [1.1.0] - 2025-12-04
|
|
15
|
-
### Added
|
|
16
|
-
- **Audio Extensibility**: Support for custom audio files (`.mp3`, `.wav`).
|
|
17
|
-
- `SoundManager.registerSound(key, url)`: Register custom assets.
|
|
18
|
-
- `SoundManager.play(key)`: Unified API to play custom sounds or fallback to synth.
|
|
19
|
-
- Support for `customSounds` in the initial configuration.
|
|
20
|
-
|
|
21
|
-
## [1.0.2] - 2025-12-04
|
|
22
|
-
### Fixed
|
|
23
|
-
- Fixed `npm run release` script failing due to missing `vitest` dependency.
|
|
24
|
-
- Uncommented `npm publish` in deployment script to ensure package is actually published to npm.
|
|
25
|
-
|
|
26
|
-
## [1.0.1] - 2025-12-04
|
|
27
|
-
### Added
|
|
28
|
-
- Initial release of DopamineJS.
|
|
29
|
-
- Core systems: `RewardSystem`, `ParticleSystem`, `SoundManager`, `GameUI`.
|
|
30
|
-
- Basic gamification features: XP, Levels, Streaks, Achievements.
|
|
31
|
-
- Built-in visual effects: Confetti, Coin Shower, Sparkles, Fire, Star Burst.
|
|
32
|
-
- Synthesized sound effects.
|
|
33
|
-
- Comprehensive README with "Vibe Coding" instructions for AI agents.
|
|
34
|
-
|
|
35
|
-
## [1.0.0] - 2025-12-04
|
|
36
|
-
- Initial scaffold.
|