dopaminejs 1.1.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.
- package/dist/dopamine.js +1947 -0
- package/dist/dopamine.umd.cjs +45 -0
- package/package.json +24 -32
- 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/ParticleSystem.js +327 -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 -27
- package/README.md +0 -213
- package/dist/dopamine.mjs +0 -724
- package/dist/dopamine.umd.js +0 -45
- package/src/effects/ParticleSystem.js +0 -257
- /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/{ui → dopamine/ui}/GameUI.js +0 -0
- /package/src/{ui → dopamine/ui}/dopamine.css +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { RewardSystem } from './core/RewardSystem.js';
|
|
2
|
+
import { DataService } from './core/DataService.js';
|
|
3
|
+
import { GameUI } from './ui/GameUI.js';
|
|
4
|
+
import { ParticleSystem } from './effects/ParticleSystem.js';
|
|
5
|
+
import { SoundManager } from './audio/SoundManager.js';
|
|
6
|
+
import './ui/dopamine.css';
|
|
7
|
+
|
|
8
|
+
export { RewardSystem, DataService, GameUI, ParticleSystem, SoundManager };
|
|
9
|
+
|
|
10
|
+
export default class Dopamine {
|
|
11
|
+
constructor(config = {}) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
|
|
14
|
+
// Initialize subsystems with config
|
|
15
|
+
this.dataService = new DataService(config.data || {});
|
|
16
|
+
this.rewardSystem = new RewardSystem(this.dataService, config.rewards || {});
|
|
17
|
+
this.soundManager = new SoundManager(config.sound || {});
|
|
18
|
+
this.particleSystem = new ParticleSystem(config.particles || {});
|
|
19
|
+
this.gameUI = new GameUI(this.particleSystem);
|
|
20
|
+
|
|
21
|
+
// Bind UI to RewardSystem events
|
|
22
|
+
this._bindEvents();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async init() {
|
|
26
|
+
await this.rewardSystem.init();
|
|
27
|
+
console.log('DopamineJS Initialized 🚀');
|
|
28
|
+
return {
|
|
29
|
+
rewardSystem: this.rewardSystem,
|
|
30
|
+
gameUI: this.gameUI,
|
|
31
|
+
particleSystem: this.particleSystem,
|
|
32
|
+
soundManager: this.soundManager
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_bindEvents() {
|
|
37
|
+
this.rewardSystem.on('xp_gained', (data) => {
|
|
38
|
+
const { total, needed } = this.rewardSystem.getXPForNextLevel();
|
|
39
|
+
this.gameUI.updateXP(this.rewardSystem.player.xp, needed, total);
|
|
40
|
+
|
|
41
|
+
if (data.xpGained > 0) {
|
|
42
|
+
// Show floating text for XP gain
|
|
43
|
+
// We might need a way to know WHERE to show this, or just show it in a standard place
|
|
44
|
+
// For now, let's just update the bar
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
this.rewardSystem.on('level_up', (data) => {
|
|
49
|
+
this.gameUI.updateLevel(data.newLevel);
|
|
50
|
+
this.gameUI.showLevelUp(data.oldLevel, data.newLevel);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
this.rewardSystem.on('achievement_unlocked', (achievement) => {
|
|
54
|
+
this.gameUI.showAchievement(achievement);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
this.rewardSystem.on('new_high_score', (data) => {
|
|
58
|
+
this.gameUI.showNotification(`New High Score: ${data.score}!`, 'legendary');
|
|
59
|
+
this.soundManager.playSuccess();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates procedural textures for prototyping.
|
|
3
|
+
*/
|
|
4
|
+
export class TextureGenerator {
|
|
5
|
+
static createGround(width, height, color = '#333') {
|
|
6
|
+
const canvas = document.createElement('canvas');
|
|
7
|
+
canvas.width = width;
|
|
8
|
+
canvas.height = height;
|
|
9
|
+
const ctx = canvas.getContext('2d');
|
|
10
|
+
|
|
11
|
+
ctx.fillStyle = color;
|
|
12
|
+
ctx.fillRect(0, 0, width, height);
|
|
13
|
+
|
|
14
|
+
// Noise / Grit
|
|
15
|
+
for (let i = 0; i < 1000; i++) {
|
|
16
|
+
ctx.fillStyle = Math.random() > 0.5 ? '#444' : '#222';
|
|
17
|
+
const size = Math.random() * 4 + 2;
|
|
18
|
+
ctx.fillRect(Math.random() * width, Math.random() * height, size, size);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Grid lines overlay (faint)
|
|
22
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.05)';
|
|
23
|
+
ctx.lineWidth = 2;
|
|
24
|
+
ctx.beginPath();
|
|
25
|
+
for (let i = 0; i <= width; i += 64) {
|
|
26
|
+
ctx.moveTo(i, 0); ctx.lineTo(i, height);
|
|
27
|
+
}
|
|
28
|
+
for (let i = 0; i <= height; i += 64) {
|
|
29
|
+
ctx.moveTo(0, i); ctx.lineTo(width, i);
|
|
30
|
+
}
|
|
31
|
+
ctx.stroke();
|
|
32
|
+
|
|
33
|
+
return canvas;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static createCharacterSheet(colorBody, colorDetail) {
|
|
37
|
+
// Create a 4-frame walk cycle strip (Vertical or Horizontal)
|
|
38
|
+
// Let's do Horizontal: 64x64 frames, 4 frames -> 256x64 image
|
|
39
|
+
const cellW = 64;
|
|
40
|
+
const cellH = 64;
|
|
41
|
+
const canvas = document.createElement('canvas');
|
|
42
|
+
canvas.width = cellW * 4;
|
|
43
|
+
canvas.height = cellH;
|
|
44
|
+
const ctx = canvas.getContext('2d');
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < 4; i++) {
|
|
47
|
+
const x = i * cellW;
|
|
48
|
+
const y = 0;
|
|
49
|
+
const cx = x + cellW / 2;
|
|
50
|
+
const cy = y + cellH / 2;
|
|
51
|
+
|
|
52
|
+
// -- Frame Animation Variances --
|
|
53
|
+
// Bobbing effect
|
|
54
|
+
const bob = (i % 2 === 0) ? 0 : -2;
|
|
55
|
+
// Leg swing (simulated by shape change or just simple offset)
|
|
56
|
+
const legOffset = (i === 1) ? 5 : (i === 3 ? -5 : 0);
|
|
57
|
+
|
|
58
|
+
// Body
|
|
59
|
+
ctx.fillStyle = colorBody;
|
|
60
|
+
ctx.beginPath();
|
|
61
|
+
ctx.arc(cx, cy + bob, 20, 0, Math.PI * 2);
|
|
62
|
+
ctx.fill();
|
|
63
|
+
|
|
64
|
+
// Shoulders/Hands (Top Down)
|
|
65
|
+
ctx.fillStyle = colorDetail;
|
|
66
|
+
ctx.beginPath();
|
|
67
|
+
ctx.arc(cx - 15, cy + bob + legOffset, 8, 0, Math.PI * 2); // Left hand
|
|
68
|
+
ctx.arc(cx + 15, cy + bob - legOffset, 8, 0, Math.PI * 2); // Right hand
|
|
69
|
+
ctx.fill();
|
|
70
|
+
|
|
71
|
+
// Head/Helmet
|
|
72
|
+
ctx.fillStyle = adjustColor(colorBody, -20);
|
|
73
|
+
ctx.beginPath();
|
|
74
|
+
ctx.arc(cx, cy + bob, 15, 0, Math.PI * 2);
|
|
75
|
+
ctx.fill();
|
|
76
|
+
|
|
77
|
+
// Gun (if soldier)
|
|
78
|
+
if (colorDetail === '#333') { // Hacky check
|
|
79
|
+
ctx.fillStyle = '#000';
|
|
80
|
+
ctx.fillRect(cx + 10, cy + bob - 5, 25, 6);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return canvas;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function adjustColor(color, amount) {
|
|
89
|
+
return color; // Placeholder for color darkening logic
|
|
90
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,62 +1,34 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
_bindEvents() {
|
|
37
|
-
this.rewardSystem.on('xp_gained', (data) => {
|
|
38
|
-
const { total, needed } = this.rewardSystem.getXPForNextLevel();
|
|
39
|
-
this.gameUI.updateXP(this.rewardSystem.player.xp, needed, total);
|
|
40
|
-
|
|
41
|
-
if (data.xpGained > 0) {
|
|
42
|
-
// Show floating text for XP gain
|
|
43
|
-
// We might need a way to know WHERE to show this, or just show it in a standard place
|
|
44
|
-
// For now, let's just update the bar
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
this.rewardSystem.on('level_up', (data) => {
|
|
49
|
-
this.gameUI.updateLevel(data.newLevel);
|
|
50
|
-
this.gameUI.showLevelUp(data.oldLevel, data.newLevel);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
this.rewardSystem.on('achievement_unlocked', (achievement) => {
|
|
54
|
-
this.gameUI.showAchievement(achievement);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
this.rewardSystem.on('new_high_score', (data) => {
|
|
58
|
-
this.gameUI.showNotification(`New High Score: ${data.score}!`, 'legendary');
|
|
59
|
-
this.soundManager.playSuccess();
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
import Dopamine from './dopamine/index.js';
|
|
2
|
+
|
|
3
|
+
// Re-export legacy systems
|
|
4
|
+
export * from './dopamine/index.js';
|
|
5
|
+
|
|
6
|
+
// Export New Kernel Architecture (Phase 0)
|
|
7
|
+
export { DopamineKernel } from './core/DopamineKernel.js';
|
|
8
|
+
export { EventBus } from './core/EventBus.js';
|
|
9
|
+
export { SystemRegistry } from './core/SystemRegistry.js';
|
|
10
|
+
export { PluginRegistry } from './core/PluginRegistry.js';
|
|
11
|
+
|
|
12
|
+
// Export System Interfaces
|
|
13
|
+
export * from './interfaces/index.js';
|
|
14
|
+
|
|
15
|
+
// Export New Engine Core
|
|
16
|
+
export { Game } from './core/Game.js';
|
|
17
|
+
export { Scene } from './core/Scene.js';
|
|
18
|
+
export { GameObject } from './core/GameObject.js';
|
|
19
|
+
export { Component } from './core/Component.js';
|
|
20
|
+
export { Renderer } from './renderer/Renderer.js';
|
|
21
|
+
export { Ticker } from './systems/Ticker.js';
|
|
22
|
+
export { Loader, GlobalLoader } from './systems/Loader.js';
|
|
23
|
+
export { Input, GlobalInput } from './systems/Input.js';
|
|
24
|
+
export { Director } from './systems/Director.js';
|
|
25
|
+
export { Physics, GlobalPhysics } from './systems/Physics.js';
|
|
26
|
+
export { Vector2 } from './core/Vector2.js';
|
|
27
|
+
export { Collider } from './core/Collider.js';
|
|
28
|
+
export { Sprite } from './core/Sprite.js';
|
|
29
|
+
export { Animator } from './core/Animator.js';
|
|
30
|
+
export { TextureGenerator } from './dopamine/utils/TextureGenerator.js';
|
|
31
|
+
export { ParticleEmitter, ScreenShake } from './dopamine/components/index.js';
|
|
32
|
+
|
|
33
|
+
// Export default for backward compatibility
|
|
34
|
+
export default Dopamine;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ISystem } from './ISystem.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IAudioSystem - Interface for audio implementations
|
|
5
|
+
*
|
|
6
|
+
* Allows different audio engines (Web Audio, Howler.js, etc.) to be swapped
|
|
7
|
+
*/
|
|
8
|
+
export const IAudioSystem = {
|
|
9
|
+
...ISystem,
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Register a sound with a key
|
|
13
|
+
* @param {string} key - Sound identifier
|
|
14
|
+
* @param {string} url - Path to audio file
|
|
15
|
+
*/
|
|
16
|
+
registerSound(key, url) { },
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Preload multiple sounds
|
|
20
|
+
* @param {Object} soundMap - Map of key -> url
|
|
21
|
+
* @returns {Promise<void>}
|
|
22
|
+
*/
|
|
23
|
+
preloadSounds(soundMap) { },
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Play a sound by key
|
|
27
|
+
* @param {string} key - Sound identifier
|
|
28
|
+
* @param {Object} options - Playback options (volume, loop, etc.)
|
|
29
|
+
*/
|
|
30
|
+
play(key, options) { },
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Play a synthesized tone
|
|
34
|
+
* @param {number} frequency - Frequency in Hz
|
|
35
|
+
* @param {number} duration - Duration in seconds
|
|
36
|
+
* @param {string} type - Waveform type ('sine', 'square', etc.)
|
|
37
|
+
* @param {number} volume - Volume (0-1)
|
|
38
|
+
*/
|
|
39
|
+
playTone(frequency, duration, type, volume) { },
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Stop a playing sound
|
|
43
|
+
* @param {string} key - Sound identifier
|
|
44
|
+
*/
|
|
45
|
+
stop(key) { },
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Toggle mute state
|
|
49
|
+
* @returns {boolean} - New mute state
|
|
50
|
+
*/
|
|
51
|
+
toggleMute() { },
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Set master volume
|
|
55
|
+
* @param {number} volume - Volume (0-1)
|
|
56
|
+
*/
|
|
57
|
+
setVolume(volume) { },
|
|
58
|
+
};
|
|
@@ -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.');
|