Skip to content

Commit 0dbd185

Browse files
committed
add sound type, convert to ts, further docs, export/import
1 parent eef6d19 commit 0dbd185

27 files changed

+2909
-38670
lines changed

Parts/Sound.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { Part } from "./Part";
2+
3+
export class Sound extends Part {
4+
audio: HTMLAudioElement;
5+
playAfterLoad: boolean = false; // Flag to indicate if play should be attempted after load
6+
private _isLoaded: boolean = false;
7+
8+
constructor({ name, src, volume = 1, loop = false }: { name: string, src: string, volume?: number, loop?: boolean }) {
9+
super({ name });
10+
this.debugEmoji = "🔊";
11+
this.audio = new Audio(src);
12+
this.audio.volume = volume;
13+
this.audio.loop = loop;
14+
15+
this.audio.addEventListener('canplaythrough', () => {
16+
this._isLoaded = true;
17+
this.ready = true;
18+
if (this.playAfterLoad) {
19+
this.playAfterLoad = false; // Reset flag
20+
// Ensure user has interacted with the screen before playing audio
21+
if (document.readyState === "complete" && (document.hasFocus() || "ontouchstart" in window)) {
22+
this.play(); // Attempt to play after load
23+
} else {
24+
const tryPlay = () => {
25+
this.play();
26+
window.removeEventListener('pointerdown', tryPlay);
27+
window.removeEventListener('keydown', tryPlay);
28+
};
29+
window.addEventListener('pointerdown', tryPlay, { once: true });
30+
window.addEventListener('keydown', tryPlay, { once: true });
31+
}
32+
}
33+
console.log(`Sound <${this.name}> loaded successfully.`);
34+
});
35+
36+
this.audio.addEventListener('error', () => {
37+
this._isLoaded = false;
38+
this.ready = false;
39+
console.error(`Failed to load sound <${this.name}> from src: ${src}`);
40+
});
41+
}
42+
43+
play(options: { restart?: boolean, clone?: boolean } = {}) {
44+
const { restart = false, clone = false } = options;
45+
46+
if (!this._isLoaded) {
47+
console.warn(`Sound <${this.name}> is not loaded yet. Cannot play.`);
48+
this.playAfterLoad = true;
49+
return;
50+
}
51+
52+
if (clone) {
53+
// Play a new instance (overlap sounds)
54+
const cloneAudio = this.audio.cloneNode(true) as HTMLAudioElement;
55+
cloneAudio.volume = this.audio.volume;
56+
cloneAudio.loop = this.audio.loop;
57+
cloneAudio.play().catch(e => console.error(`Error playing cloned sound <${this.name}>:`, e));
58+
} else {
59+
// Restart current audio if requested
60+
if (restart) {
61+
this.audio.currentTime = 0;
62+
}
63+
this.audio.play().catch(e => console.error(`Error playing sound <${this.name}>:`, e));
64+
}
65+
}
66+
67+
pause() {
68+
this.audio.pause();
69+
}
70+
71+
stop() {
72+
this.audio.pause();
73+
this.audio.currentTime = 0;
74+
}
75+
76+
setVolume(volume: number) {
77+
this.audio.volume = Math.max(0, Math.min(1, volume)); // Clamp between 0 and 1
78+
}
79+
80+
setLoop(loop: boolean) {
81+
this.audio.loop = loop;
82+
}
83+
84+
act() {
85+
super.act();
86+
this.hoverbug = `${this.audio.paused ? "⏸️" : "▶️"} V:${this.audio.volume.toFixed(2)} L:${this.audio.loop ? "✅" : "❌"}`;
87+
}
88+
}

docs/_sidebar.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [Scene](api/Scene.md)
99
- [Layer](api/Layer.md)
1010
- [GameObject](api/GameObject.md)
11+
- [Sound](api/Sound.md)
1112
- **Components**
1213
- [Transform](api/Transform.md)
1314
- [Camera](api/Camera.md)

docs/api/Sound.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Sound
2+
3+
**Extends:** [Part](./Part.md)
4+
5+
The `Sound` component allows you to load and play audio files within your game. It wraps the browser's native `HTMLAudioElement`.
6+
7+
## Properties
8+
9+
- `audio: HTMLAudioElement`
10+
The underlying HTML Audio Element. You can access this directly for more advanced control if needed.
11+
12+
- `_isLoaded: boolean`
13+
Internal flag indicating if the audio file has successfully loaded and is ready to play.
14+
15+
## Constructor Parameters
16+
17+
- `name: string`
18+
The name of the sound part.
19+
20+
- `src: string`
21+
The path to the audio file (e.g., `"./assets/music.mp3"`, `"./assets/sfx/jump.wav"`).
22+
23+
- `volume?: number` (default: `1`)
24+
The volume of the sound, a value between 0 (silent) and 1 (full volume).
25+
26+
- `loop?: boolean` (default: `false`)
27+
If `true`, the sound will loop continuously when played.
28+
29+
## Methods
30+
31+
- `play()`
32+
Starts playing the sound. If the sound is already playing, it will restart from the beginning.
33+
34+
- `pause()`
35+
Pauses the sound at its current playback position.
36+
37+
- `stop()`
38+
Stops the sound and resets its playback position to the beginning.
39+
40+
- `setVolume(volume: number)`
41+
Sets the volume of the sound. The value will be clamped between 0 and 1.
42+
43+
- `setLoop(loop: boolean)`
44+
Sets whether the sound should loop.
45+
46+
## Examples
47+
48+
### Creating and Playing Background Music
49+
50+
```javascript
51+
import { GameObject } from './Parts/GameObject';
52+
import { Sound } from './Parts/Sound';
53+
54+
const backgroundMusic = new GameObject({ name: 'BackgroundMusic' });
55+
56+
backgroundMusic.addChildren(
57+
new Sound({
58+
name: 'GameMusic',
59+
src: './assets/audio/bg_music.mp3',
60+
volume: 0.5,
61+
loop: true
62+
})
63+
);
64+
65+
// Add the GameObject to a scene (e.g., your main menu scene)
66+
myMenuScene.addChild(backgroundMusic);
67+
68+
// To start playing the music (e.g., when the scene starts or a button is clicked)
69+
// You would typically get a reference to the Sound part and call play()
70+
const musicPart = backgroundMusic.children['GameMusic'] as Sound;
71+
musicPart.play();
72+
```
73+
74+
### Playing a Sound Effect on an Event
75+
76+
```javascript
77+
import { GameObject } from './Parts/GameObject';
78+
import { Sound } from './Parts/Sound';
79+
import { Part } from './Parts/Part';
80+
81+
// Assume 'player' is a GameObject
82+
declare const player: GameObject;
83+
84+
// Add a Sound component for the jump sound effect to the player GameObject
85+
player.addChild(
86+
new Sound({
87+
name: 'JumpSound',
88+
src: './assets/audio/jump.wav',
89+
volume: 0.8,
90+
loop: false
91+
})
92+
);
93+
94+
// In a custom PlayerController Part (sibling of the Sound part)
95+
class PlayerController extends Part {
96+
constructor() { super(); this.name = 'PlayerController'; }
97+
98+
handleJump() {
99+
const jumpSound = this.sibling<Sound>('JumpSound');
100+
if (jumpSound) {
101+
jumpSound.play(); // Play the jump sound
102+
}
103+
}
104+
105+
act() {
106+
// Example: if jump key is pressed
107+
// if (Input.isKeyPressed('Space')) {
108+
// this.handleJump();
109+
// }
110+
}
111+
}
112+
113+
player.addChild(new PlayerController());
114+
```

0 commit comments

Comments
 (0)