@react-text-game/core 0.4.0 → 0.5.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/README.md +389 -0
- package/dist/audio/audioTrack.d.ts +407 -0
- package/dist/audio/audioTrack.d.ts.map +1 -0
- package/dist/audio/audioTrack.js +656 -0
- package/dist/audio/audioTrack.js.map +1 -0
- package/dist/audio/constants.d.ts +19 -0
- package/dist/audio/constants.d.ts.map +1 -0
- package/dist/audio/constants.js +20 -0
- package/dist/audio/constants.js.map +1 -0
- package/dist/audio/fabric.d.ts +35 -0
- package/dist/audio/fabric.d.ts.map +1 -0
- package/dist/audio/fabric.js +36 -0
- package/dist/audio/fabric.js.map +1 -0
- package/dist/audio/index.d.ts +33 -0
- package/dist/audio/index.d.ts.map +1 -0
- package/dist/audio/index.js +32 -0
- package/dist/audio/index.js.map +1 -0
- package/dist/audio/types.d.ts +105 -0
- package/dist/audio/types.d.ts.map +1 -0
- package/dist/audio/types.js +2 -0
- package/dist/audio/types.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useAudio.d.ts +50 -0
- package/dist/hooks/useAudio.d.ts.map +1 -0
- package/dist/hooks/useAudio.js +42 -0
- package/dist/hooks/useAudio.js.map +1 -0
- package/dist/hooks/useAudioManager.d.ts +66 -0
- package/dist/hooks/useAudioManager.d.ts.map +1 -0
- package/dist/hooks/useAudioManager.js +70 -0
- package/dist/hooks/useAudioManager.js.map +1 -0
- package/dist/i18n/utils.d.ts.map +1 -1
- package/dist/i18n/utils.js +6 -0
- package/dist/i18n/utils.js.map +1 -1
- package/dist/tests/audio.test.d.ts +2 -0
- package/dist/tests/audio.test.d.ts.map +1 -0
- package/dist/tests/audio.test.js +605 -0
- package/dist/tests/audio.test.js.map +1 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ A powerful, reactive text-based game engine built for React applications. This p
|
|
|
7
7
|
- **Reactive State Management** - Built on Valtio for automatic UI updates
|
|
8
8
|
- **Multiple Passage Types** - Story, Interactive Map, and Widget passages
|
|
9
9
|
- **Flexible Save System** - JSONPath-based storage with auto-save support
|
|
10
|
+
- **Audio System** - Comprehensive audio support with reactive state, persistence, and global controls
|
|
10
11
|
- **Entity Registry** - Automatic registration and proxying of game objects
|
|
11
12
|
- **Factory-Based Entities** - Plain-object factories for beginners with class-based escape hatches
|
|
12
13
|
- **Type-Safe** - Full TypeScript support with comprehensive types
|
|
@@ -97,6 +98,394 @@ Game.jumpTo(introStory);
|
|
|
97
98
|
|
|
98
99
|
> Prefer writing classes? Jump to [Advanced Entities](#advanced-entities-basegameobject) for a drop-in replacement using inheritance.
|
|
99
100
|
|
|
101
|
+
## Audio System
|
|
102
|
+
|
|
103
|
+
The core engine includes a comprehensive audio system with reactive state management, automatic persistence, and global controls. Built on top of the Web Audio API with Valtio for reactive state, it provides an easy-to-use interface for managing background music, sound effects, and voice-over audio.
|
|
104
|
+
|
|
105
|
+
### Features
|
|
106
|
+
|
|
107
|
+
- **Reactive State** - Valtio-powered reactive state for seamless React integration
|
|
108
|
+
- **Automatic Persistence** - Audio state (volume, position, playing status) saved automatically
|
|
109
|
+
- **Global Controls** - Master volume, mute all, pause/resume all tracks
|
|
110
|
+
- **Fade Effects** - Built-in fade in/out for smooth transitions
|
|
111
|
+
- **Multiple Tracks** - Independent control of multiple audio files simultaneously
|
|
112
|
+
- **Browser-friendly** - Handles autoplay policies and user interaction requirements
|
|
113
|
+
- **Type-Safe** - Full TypeScript support with comprehensive types
|
|
114
|
+
|
|
115
|
+
### Quick Start
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { createAudio, AudioManager } from '@react-text-game/core/audio';
|
|
119
|
+
|
|
120
|
+
// Create an audio track
|
|
121
|
+
const bgMusic = createAudio('/audio/background.mp3', {
|
|
122
|
+
id: 'bg-music',
|
|
123
|
+
volume: 0.7,
|
|
124
|
+
loop: true,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Play the track
|
|
128
|
+
await bgMusic.play();
|
|
129
|
+
|
|
130
|
+
// Control individual tracks
|
|
131
|
+
bgMusic.setVolume(0.5);
|
|
132
|
+
bgMusic.pause();
|
|
133
|
+
bgMusic.resume();
|
|
134
|
+
bgMusic.stop();
|
|
135
|
+
|
|
136
|
+
// Global controls
|
|
137
|
+
AudioManager.setMasterVolume(0.8);
|
|
138
|
+
AudioManager.muteAll();
|
|
139
|
+
AudioManager.pauseAll();
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Creating Audio Tracks
|
|
143
|
+
|
|
144
|
+
Use the `createAudio` factory function to create audio tracks:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { createAudio } from '@react-text-game/core/audio';
|
|
148
|
+
|
|
149
|
+
// Basic audio track
|
|
150
|
+
const sfx = createAudio('/audio/click.mp3');
|
|
151
|
+
|
|
152
|
+
// With options
|
|
153
|
+
const music = createAudio('/audio/theme.mp3', {
|
|
154
|
+
id: 'theme-music', // Required for persistence
|
|
155
|
+
volume: 0.6, // 0.0 to 1.0 (default: 1.0)
|
|
156
|
+
loop: true, // Auto-loop (default: false)
|
|
157
|
+
playbackRate: 1.0, // Playback speed (default: 1.0)
|
|
158
|
+
muted: false, // Start muted (default: false)
|
|
159
|
+
autoPlay: false, // Auto-play on creation (default: false)
|
|
160
|
+
preload: 'metadata', // 'none', 'metadata', or 'auto' (default: 'metadata')
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Audio Track Controls
|
|
165
|
+
|
|
166
|
+
Each audio track provides comprehensive playback controls:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Playback control
|
|
170
|
+
await audio.play(); // Start playback (returns Promise)
|
|
171
|
+
audio.pause(); // Pause playback
|
|
172
|
+
audio.resume(); // Resume from pause
|
|
173
|
+
audio.stop(); // Stop and reset to beginning
|
|
174
|
+
|
|
175
|
+
// Volume and settings
|
|
176
|
+
audio.setVolume(0.5); // Set volume (0.0 to 1.0)
|
|
177
|
+
audio.setLoop(true); // Enable/disable looping
|
|
178
|
+
audio.setPlaybackRate(1.5); // Set playback speed
|
|
179
|
+
audio.setMuted(true); // Mute/unmute
|
|
180
|
+
|
|
181
|
+
// Seeking
|
|
182
|
+
audio.seek(30); // Seek to 30 seconds
|
|
183
|
+
|
|
184
|
+
// Fade effects
|
|
185
|
+
await audio.fadeIn(2000); // Fade in over 2 seconds
|
|
186
|
+
await audio.fadeOut(1500); // Fade out over 1.5 seconds
|
|
187
|
+
|
|
188
|
+
// State and persistence
|
|
189
|
+
const state = audio.getState(); // Get reactive state
|
|
190
|
+
audio.save(); // Save state to storage
|
|
191
|
+
audio.load(); // Load state from storage
|
|
192
|
+
|
|
193
|
+
// Cleanup
|
|
194
|
+
audio.dispose(); // Remove and clean up
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Reactive State
|
|
198
|
+
|
|
199
|
+
Audio tracks use Valtio for reactive state management, making them perfect for React integration:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const audio = createAudio('/audio/music.mp3', { id: 'music' });
|
|
203
|
+
|
|
204
|
+
// Get reactive state
|
|
205
|
+
const state = audio.getState();
|
|
206
|
+
|
|
207
|
+
// Access state properties
|
|
208
|
+
console.log(state.isPlaying); // boolean
|
|
209
|
+
console.log(state.isPaused); // boolean
|
|
210
|
+
console.log(state.isStopped); // boolean
|
|
211
|
+
console.log(state.currentTime); // number (seconds)
|
|
212
|
+
console.log(state.duration); // number (seconds)
|
|
213
|
+
console.log(state.volume); // number (0.0 to 1.0)
|
|
214
|
+
console.log(state.loop); // boolean
|
|
215
|
+
console.log(state.playbackRate); // number
|
|
216
|
+
console.log(state.muted); // boolean
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Global Audio Manager
|
|
220
|
+
|
|
221
|
+
The `AudioManager` provides global controls for all registered audio tracks:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { AudioManager } from '@react-text-game/core/audio';
|
|
225
|
+
|
|
226
|
+
// Master volume control
|
|
227
|
+
AudioManager.setMasterVolume(0.5); // Set master volume (0.0 to 1.0)
|
|
228
|
+
const volume = AudioManager.getMasterVolume(); // Get master volume
|
|
229
|
+
|
|
230
|
+
// Global playback control
|
|
231
|
+
AudioManager.pauseAll(); // Pause all playing tracks
|
|
232
|
+
AudioManager.resumeAll(); // Resume all paused tracks
|
|
233
|
+
AudioManager.stopAll(); // Stop all tracks
|
|
234
|
+
|
|
235
|
+
// Global mute control
|
|
236
|
+
AudioManager.muteAll(); // Mute all tracks
|
|
237
|
+
AudioManager.unmuteAll(); // Unmute all tracks
|
|
238
|
+
|
|
239
|
+
// Track management
|
|
240
|
+
const tracks = AudioManager.getAllTracks(); // Get all registered tracks
|
|
241
|
+
const music = AudioManager.getTrackById('bg-music'); // Get specific track by ID
|
|
242
|
+
|
|
243
|
+
// Cleanup
|
|
244
|
+
AudioManager.disposeAll(); // Dispose all tracks
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Master Volume Behavior:**
|
|
248
|
+
- Master volume is a multiplier applied to all track volumes
|
|
249
|
+
- Does not modify individual track volume settings
|
|
250
|
+
- Example: Track at 0.8 volume with 0.5 master = 0.4 effective volume
|
|
251
|
+
- Useful for game-wide volume sliders in settings
|
|
252
|
+
|
|
253
|
+
### React Integration
|
|
254
|
+
|
|
255
|
+
The audio system includes React hooks for seamless component integration:
|
|
256
|
+
|
|
257
|
+
#### useAudio Hook
|
|
258
|
+
|
|
259
|
+
Monitor individual audio track state with automatic re-renders:
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
import { createAudio } from '@react-text-game/core/audio';
|
|
263
|
+
import { useAudio } from '@react-text-game/core';
|
|
264
|
+
|
|
265
|
+
const bgMusic = createAudio('/audio/background.mp3', {
|
|
266
|
+
id: 'bg-music',
|
|
267
|
+
loop: true,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
function MusicPlayer() {
|
|
271
|
+
const audioState = useAudio(bgMusic);
|
|
272
|
+
|
|
273
|
+
return (
|
|
274
|
+
<div>
|
|
275
|
+
<p>Status: {audioState.isPlaying ? 'Playing' : 'Stopped'}</p>
|
|
276
|
+
<p>Time: {audioState.currentTime.toFixed(1)}s / {audioState.duration.toFixed(1)}s</p>
|
|
277
|
+
<p>Volume: {(audioState.volume * 100).toFixed(0)}%</p>
|
|
278
|
+
|
|
279
|
+
<button onClick={() => bgMusic.play()}>Play</button>
|
|
280
|
+
<button onClick={() => bgMusic.pause()}>Pause</button>
|
|
281
|
+
<button onClick={() => bgMusic.stop()}>Stop</button>
|
|
282
|
+
|
|
283
|
+
<input
|
|
284
|
+
type="range"
|
|
285
|
+
min="0"
|
|
286
|
+
max="1"
|
|
287
|
+
step="0.01"
|
|
288
|
+
value={audioState.volume}
|
|
289
|
+
onChange={(e) => bgMusic.setVolume(parseFloat(e.target.value))}
|
|
290
|
+
/>
|
|
291
|
+
</div>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
#### useAudioManager Hook
|
|
297
|
+
|
|
298
|
+
Access global audio controls in React components:
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
import { useAudioManager } from '@react-text-game/core';
|
|
302
|
+
|
|
303
|
+
function AudioSettings() {
|
|
304
|
+
const audioManager = useAudioManager();
|
|
305
|
+
|
|
306
|
+
return (
|
|
307
|
+
<div>
|
|
308
|
+
<h2>Audio Settings</h2>
|
|
309
|
+
|
|
310
|
+
<label>
|
|
311
|
+
Master Volume: {(audioManager.masterVolume * 100).toFixed(0)}%
|
|
312
|
+
<input
|
|
313
|
+
type="range"
|
|
314
|
+
min="0"
|
|
315
|
+
max="1"
|
|
316
|
+
step="0.01"
|
|
317
|
+
value={audioManager.masterVolume}
|
|
318
|
+
onChange={(e) => audioManager.setMasterVolume(parseFloat(e.target.value))}
|
|
319
|
+
/>
|
|
320
|
+
</label>
|
|
321
|
+
|
|
322
|
+
<div>
|
|
323
|
+
<button onClick={audioManager.muteAll}>Mute All</button>
|
|
324
|
+
<button onClick={audioManager.unmuteAll}>Unmute All</button>
|
|
325
|
+
<button onClick={audioManager.pauseAll}>Pause All</button>
|
|
326
|
+
<button onClick={audioManager.resumeAll}>Resume All</button>
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
<p>Muted: {audioManager.isMuted ? 'Yes' : 'No'}</p>
|
|
330
|
+
<p>Active Tracks: {audioManager.getAllTracks().length}</p>
|
|
331
|
+
</div>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Automatic Persistence
|
|
337
|
+
|
|
338
|
+
Audio tracks with an `id` automatically persist their state:
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// Create audio with ID for persistence
|
|
342
|
+
const music = createAudio('/audio/theme.mp3', {
|
|
343
|
+
id: 'theme-music',
|
|
344
|
+
volume: 0.7,
|
|
345
|
+
loop: true,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// State is automatically saved when it changes
|
|
349
|
+
await music.play();
|
|
350
|
+
music.setVolume(0.5);
|
|
351
|
+
// State saved automatically
|
|
352
|
+
|
|
353
|
+
// On game restart/reload
|
|
354
|
+
const music = createAudio('/audio/theme.mp3', {
|
|
355
|
+
id: 'theme-music', // Same ID
|
|
356
|
+
});
|
|
357
|
+
music.load(); // Restores volume, position, playing state
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**What Gets Persisted:**
|
|
361
|
+
- Volume level
|
|
362
|
+
- Loop setting
|
|
363
|
+
- Playback rate
|
|
364
|
+
- Muted status
|
|
365
|
+
- Current playback position
|
|
366
|
+
- Playing/paused state
|
|
367
|
+
|
|
368
|
+
**Note:** Audio without an ID will not persist across page reloads.
|
|
369
|
+
|
|
370
|
+
### Common Patterns
|
|
371
|
+
|
|
372
|
+
#### Background Music with Crossfade
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
const oldMusic = AudioManager.getTrackById('current-music');
|
|
376
|
+
const newMusic = createAudio('/audio/new-theme.mp3', {
|
|
377
|
+
id: 'current-music',
|
|
378
|
+
loop: true,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Crossfade between tracks
|
|
382
|
+
if (oldMusic) {
|
|
383
|
+
await Promise.all([
|
|
384
|
+
oldMusic.fadeOut(1000),
|
|
385
|
+
newMusic.fadeIn(1000)
|
|
386
|
+
]);
|
|
387
|
+
oldMusic.dispose();
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
#### Sound Effects Pool
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
// Create sound effect without ID (no persistence needed)
|
|
395
|
+
function playSoundEffect(src: string) {
|
|
396
|
+
const sfx = createAudio(src, {
|
|
397
|
+
volume: 0.8,
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
sfx.play();
|
|
401
|
+
|
|
402
|
+
// Auto-cleanup when finished
|
|
403
|
+
sfx.audioElement.addEventListener('ended', () => {
|
|
404
|
+
sfx.dispose();
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
playSoundEffect('/audio/click.mp3');
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
#### Pause Audio During Dialogue
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
function showDialogue() {
|
|
415
|
+
// Pause background music
|
|
416
|
+
AudioManager.pauseAll();
|
|
417
|
+
|
|
418
|
+
// Show dialogue...
|
|
419
|
+
|
|
420
|
+
// Resume when done
|
|
421
|
+
AudioManager.resumeAll();
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Browser Autoplay Policies
|
|
426
|
+
|
|
427
|
+
Modern browsers restrict audio autoplay without user interaction. The audio system handles this gracefully:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
const music = createAudio('/audio/theme.mp3', {
|
|
431
|
+
autoPlay: true, // May be blocked by browser
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// Autoplay failures are logged but don't throw errors
|
|
435
|
+
// Manually play after user interaction:
|
|
436
|
+
document.addEventListener('click', async () => {
|
|
437
|
+
await music.play(); // Will work after user interaction
|
|
438
|
+
}, { once: true });
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### TypeScript Types
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
import type {
|
|
445
|
+
AudioOptions,
|
|
446
|
+
AudioState,
|
|
447
|
+
AudioSaveState,
|
|
448
|
+
} from '@react-text-game/core/audio';
|
|
449
|
+
|
|
450
|
+
// All types include comprehensive JSDoc documentation
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### API Reference
|
|
454
|
+
|
|
455
|
+
**createAudio(src, options?)**
|
|
456
|
+
- `src: string` - Audio file URL
|
|
457
|
+
- `options?: AudioOptions` - Configuration options
|
|
458
|
+
- Returns: `AudioTrack`
|
|
459
|
+
|
|
460
|
+
**AudioTrack Methods:**
|
|
461
|
+
- `play(): Promise<void>` - Start playback
|
|
462
|
+
- `pause(): void` - Pause playback
|
|
463
|
+
- `resume(): void` - Resume from pause
|
|
464
|
+
- `stop(): void` - Stop and reset
|
|
465
|
+
- `setVolume(volume: number): void` - Set volume (0.0-1.0)
|
|
466
|
+
- `setLoop(loop: boolean): void` - Enable/disable looping
|
|
467
|
+
- `setPlaybackRate(rate: number): void` - Set playback speed
|
|
468
|
+
- `setMuted(muted: boolean): void` - Mute/unmute
|
|
469
|
+
- `seek(time: number): void` - Seek to time in seconds
|
|
470
|
+
- `fadeIn(duration?: number): Promise<void>` - Fade in effect
|
|
471
|
+
- `fadeOut(duration?: number): Promise<void>` - Fade out effect
|
|
472
|
+
- `getState(): AudioState` - Get reactive state
|
|
473
|
+
- `save(): void` - Save state to storage
|
|
474
|
+
- `load(): void` - Load state from storage
|
|
475
|
+
- `dispose(): void` - Clean up and remove
|
|
476
|
+
|
|
477
|
+
**AudioManager Methods:**
|
|
478
|
+
- `setMasterVolume(volume: number): void` - Set master volume
|
|
479
|
+
- `getMasterVolume(): number` - Get master volume
|
|
480
|
+
- `muteAll(): void` - Mute all tracks
|
|
481
|
+
- `unmuteAll(): void` - Unmute all tracks
|
|
482
|
+
- `pauseAll(): void` - Pause all playing tracks
|
|
483
|
+
- `resumeAll(): void` - Resume all paused tracks
|
|
484
|
+
- `stopAll(): void` - Stop all tracks
|
|
485
|
+
- `getAllTracks(): AudioTrack[]` - Get all tracks
|
|
486
|
+
- `getTrackById(id: string): AudioTrack | undefined` - Get track by ID
|
|
487
|
+
- `disposeAll(): void` - Dispose all tracks
|
|
488
|
+
|
|
100
489
|
## Internationalization
|
|
101
490
|
|
|
102
491
|
The core engine ships with first-class i18n based on `i18next` and `react-i18next`. Language preferences are persisted to the save database and automatically restored on load.
|