@react-text-game/core 0.4.1 → 0.5.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.
Files changed (46) hide show
  1. package/README.md +389 -0
  2. package/dist/audio/audioTrack.d.ts +407 -0
  3. package/dist/audio/audioTrack.d.ts.map +1 -0
  4. package/dist/audio/audioTrack.js +656 -0
  5. package/dist/audio/audioTrack.js.map +1 -0
  6. package/dist/audio/constants.d.ts +19 -0
  7. package/dist/audio/constants.d.ts.map +1 -0
  8. package/dist/audio/constants.js +20 -0
  9. package/dist/audio/constants.js.map +1 -0
  10. package/dist/audio/fabric.d.ts +35 -0
  11. package/dist/audio/fabric.d.ts.map +1 -0
  12. package/dist/audio/fabric.js +36 -0
  13. package/dist/audio/fabric.js.map +1 -0
  14. package/dist/audio/index.d.ts +33 -0
  15. package/dist/audio/index.d.ts.map +1 -0
  16. package/dist/audio/index.js +32 -0
  17. package/dist/audio/index.js.map +1 -0
  18. package/dist/audio/types.d.ts +105 -0
  19. package/dist/audio/types.d.ts.map +1 -0
  20. package/dist/audio/types.js +2 -0
  21. package/dist/audio/types.js.map +1 -0
  22. package/dist/hooks/index.d.ts +2 -0
  23. package/dist/hooks/index.d.ts.map +1 -1
  24. package/dist/hooks/index.js +2 -0
  25. package/dist/hooks/index.js.map +1 -1
  26. package/dist/hooks/useAudio.d.ts +50 -0
  27. package/dist/hooks/useAudio.d.ts.map +1 -0
  28. package/dist/hooks/useAudio.js +42 -0
  29. package/dist/hooks/useAudio.js.map +1 -0
  30. package/dist/hooks/useAudioManager.d.ts +66 -0
  31. package/dist/hooks/useAudioManager.d.ts.map +1 -0
  32. package/dist/hooks/useAudioManager.js +70 -0
  33. package/dist/hooks/useAudioManager.js.map +1 -0
  34. package/dist/passages/interactiveMap/interactiveMap.d.ts +17 -5
  35. package/dist/passages/interactiveMap/interactiveMap.d.ts.map +1 -1
  36. package/dist/passages/interactiveMap/interactiveMap.js +19 -6
  37. package/dist/passages/interactiveMap/interactiveMap.js.map +1 -1
  38. package/dist/passages/interactiveMap/types.d.ts +28 -2
  39. package/dist/passages/interactiveMap/types.d.ts.map +1 -1
  40. package/dist/tests/audio.test.d.ts +2 -0
  41. package/dist/tests/audio.test.d.ts.map +1 -0
  42. package/dist/tests/audio.test.js +605 -0
  43. package/dist/tests/audio.test.js.map +1 -0
  44. package/dist/tests/interactiveMap.test.js +227 -0
  45. package/dist/tests/interactiveMap.test.js.map +1 -1
  46. package/package.json +5 -1
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.