@playcraft/cli 0.0.53 → 0.0.55

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 (79) hide show
  1. package/dist/commands/create.js +164 -98
  2. package/dist/index.js +4 -1
  3. package/dist/scaffold/templates.js +35 -0
  4. package/package.json +3 -3
  5. package/project-templates/playable-template-phaser/.playcraftignore +10 -0
  6. package/project-templates/playable-template-phaser/CLAUDE.md +178 -0
  7. package/project-templates/playable-template-phaser/PLAYCRAFT-MIGRATION.md +328 -0
  8. package/project-templates/playable-template-phaser/README.md +381 -0
  9. package/project-templates/playable-template-phaser/assets/brain.png +0 -0
  10. package/project-templates/playable-template-phaser/assets/button.png +0 -0
  11. package/project-templates/playable-template-phaser/assets/interact.png +0 -0
  12. package/project-templates/playable-template-phaser/assets/logo.webp +0 -0
  13. package/project-templates/playable-template-phaser/assets/warn_brain.png +0 -0
  14. package/project-templates/playable-template-phaser/builds.config.js +12 -0
  15. package/project-templates/playable-template-phaser/globals.d.ts +2 -0
  16. package/project-templates/playable-template-phaser/index.html +12 -0
  17. package/project-templates/playable-template-phaser/package.json +45 -0
  18. package/project-templates/playable-template-phaser/scripts/base64-to-mp3.js +340 -0
  19. package/project-templates/playable-template-phaser/scripts/convert-to-webp.js +370 -0
  20. package/project-templates/playable-template-phaser/src/Game.ts +154 -0
  21. package/project-templates/playable-template-phaser/src/config/LoadingConfig.ts +40 -0
  22. package/project-templates/playable-template-phaser/src/config/ThemeConfig.ts +81 -0
  23. package/project-templates/playable-template-phaser/src/index.css +18 -0
  24. package/project-templates/playable-template-phaser/src/index.html +9 -0
  25. package/project-templates/playable-template-phaser/src/index.ts +30 -0
  26. package/project-templates/playable-template-phaser/src/json5.d.ts +4 -0
  27. package/project-templates/playable-template-phaser/src/managers/AudioManager.ts +559 -0
  28. package/project-templates/playable-template-phaser/src/scenes/MainScene.ts +301 -0
  29. package/project-templates/playable-template-phaser/src/scenes/PreloadScene.ts +256 -0
  30. package/project-templates/playable-template-phaser/src/theme/README.md +194 -0
  31. package/project-templates/playable-template-phaser/src/theme/blue-theme/assets/.gitkeep +24 -0
  32. package/project-templates/playable-template-phaser/src/theme/blue-theme/index.ts +27 -0
  33. package/project-templates/playable-template-phaser/src/theme/blue-theme/theme.data.json5 +19 -0
  34. package/project-templates/playable-template-phaser/src/theme/green-theme/assets/.gitkeep +3 -0
  35. package/project-templates/playable-template-phaser/src/theme/green-theme/index.ts +20 -0
  36. package/project-templates/playable-template-phaser/src/theme/green-theme/theme.data.json5 +19 -0
  37. package/project-templates/playable-template-phaser/src/theme/index.ts +1 -0
  38. package/project-templates/playable-template-phaser/src/theme/red-theme/assets/.gitkeep +3 -0
  39. package/project-templates/playable-template-phaser/src/theme/red-theme/index.ts +20 -0
  40. package/project-templates/playable-template-phaser/src/theme/red-theme/theme.data.json5 +19 -0
  41. package/project-templates/playable-template-phaser/src/theme/theme.metadata.md +60 -0
  42. package/project-templates/playable-template-phaser/src/theme/theme.schema.json5 +149 -0
  43. package/project-templates/playable-template-phaser/src/ui/BrainAgeUI.ts +681 -0
  44. package/project-templates/playable-template-phaser/src/ui/CountdownUI.ts +565 -0
  45. package/project-templates/playable-template-phaser/src/ui/PlayButton.ts +51 -0
  46. package/project-templates/playable-template-phaser/src/ui/ScoreUI.ts +207 -0
  47. package/project-templates/playable-template-phaser/src/ui/SuccessUI.ts +43 -0
  48. package/project-templates/playable-template-phaser/src/ui/index.ts +15 -0
  49. package/project-templates/playable-template-phaser/tsconfig.json +26 -0
  50. package/project-templates/playable-template-phaser/vite.config.ts +64 -0
  51. package/project-templates/playable-template-phaser//346/226/260/346/211/213/345/274/200/345/217/221/346/214/207/345/215/227.md +209 -0
  52. package/project-templates/playable-template-three/README.md +795 -0
  53. package/project-templates/playable-template-three/assets/Stork.glb +0 -0
  54. package/project-templates/playable-template-three/assets/button.png +0 -0
  55. package/project-templates/playable-template-three/assets/logo.webp +0 -0
  56. package/project-templates/playable-template-three/build.json +7 -0
  57. package/project-templates/playable-template-three/builds.config.example.js +151 -0
  58. package/project-templates/playable-template-three/builds.config.js +158 -0
  59. package/project-templates/playable-template-three/package.json +36 -0
  60. package/project-templates/playable-template-three/scripts/convert-to-webp.js +370 -0
  61. package/project-templates/playable-template-three/scripts/theme-builds.js +514 -0
  62. package/project-templates/playable-template-three/src/Game.ts +215 -0
  63. package/project-templates/playable-template-three/src/config/LoadingConfig.ts +27 -0
  64. package/project-templates/playable-template-three/src/config/ThemeConfig.ts +36 -0
  65. package/project-templates/playable-template-three/src/global.d.ts +108 -0
  66. package/project-templates/playable-template-three/src/index.css +18 -0
  67. package/project-templates/playable-template-three/src/index.html +9 -0
  68. package/project-templates/playable-template-three/src/index.ts +20 -0
  69. package/project-templates/playable-template-three/src/managers/AudioManager.ts +538 -0
  70. package/project-templates/playable-template-three/src/scenes/PreloadScene.ts +305 -0
  71. package/project-templates/playable-template-three/src/theme/README.md +225 -0
  72. package/project-templates/playable-template-three/src/theme/blue-theme/assets/.gitkeep +28 -0
  73. package/project-templates/playable-template-three/src/theme/blue-theme/index.ts +45 -0
  74. package/project-templates/playable-template-three/src/theme/current.ts +12 -0
  75. package/project-templates/playable-template-three/src/theme/green-theme/assets/.gitkeep +3 -0
  76. package/project-templates/playable-template-three/src/theme/green-theme/index.ts +34 -0
  77. package/project-templates/playable-template-three/src/theme/red-theme/assets/.gitkeep +3 -0
  78. package/project-templates/playable-template-three/src/theme/red-theme/index.ts +34 -0
  79. package/project-templates/playable-template-three/tsconfig.json +24 -0
@@ -0,0 +1,4 @@
1
+ declare module '*.json5' {
2
+ const value: any;
3
+ export default value;
4
+ }
@@ -0,0 +1,559 @@
1
+ /**
2
+ * 音频管理器
3
+ * 统一管理背景音乐和音效,处理后台暂停等问题
4
+ */
5
+ import { sdk } from '@playcraft/adsdk';
6
+
7
+ export interface AudioManagerConfig {
8
+ bgmUrl?: string; // 背景音乐 URL
9
+ bgmVolume?: number; // 背景音乐音量(0-1)
10
+ bgmLoop?: boolean; // 背景音乐是否循环
11
+ effectVolume?: number; // 音效音量(0-1)
12
+ enableBackgroundPause?: boolean; // 是否启用后台自动暂停
13
+ watchdogInterval?: number; // 监控守护进程检查间隔(ms)
14
+ }
15
+
16
+ export class AudioManager {
17
+ // 背景音乐
18
+ private bgm: HTMLAudioElement | null = null;
19
+ private isBgmPlaying = false;
20
+ private bgmConfig = {
21
+ url: '',
22
+ volume: 0.5,
23
+ loop: true
24
+ };
25
+
26
+ // 音效(使用 AudioContext)
27
+ private audioContext: AudioContext | null = null;
28
+ private audioBufferCache: Map<string, AudioBuffer> = new Map();
29
+ private effectVolume = 0.6;
30
+
31
+ // 场景引用
32
+ private scene: Phaser.Scene | null = null;
33
+
34
+ // 游戏状态
35
+ private isGameEnded = false;
36
+
37
+ // 事件监听器引用(用于清理)
38
+ private eventHandlers = {
39
+ visibilityChange: null as (() => void) | null,
40
+ pageHide: null as (() => void) | null,
41
+ blur: null as (() => void) | null,
42
+ pageShow: null as (() => void) | null,
43
+ focus: null as (() => void) | null,
44
+ volumeChange: null as ((volume: number) => void) | null,
45
+ scenePause: null as (() => void) | null,
46
+ sceneResume: null as (() => void) | null
47
+ };
48
+
49
+ // 后台音频监控守护进程
50
+ private watchdogInterval: number | null = null;
51
+ private watchdogIntervalTime = 500; // 默认 500ms
52
+
53
+ // 配置
54
+ private enableBackgroundPause = true;
55
+
56
+ constructor(config: AudioManagerConfig = {}) {
57
+ this.bgmConfig.volume = config.bgmVolume ?? 0.5;
58
+ this.bgmConfig.loop = config.bgmLoop ?? true;
59
+ this.effectVolume = config.effectVolume ?? 0.6;
60
+ this.enableBackgroundPause = config.enableBackgroundPause ?? true;
61
+ this.watchdogIntervalTime = config.watchdogInterval ?? 500;
62
+
63
+ if (config.bgmUrl) {
64
+ this.bgmConfig.url = config.bgmUrl;
65
+ }
66
+
67
+ console.log('🎵 AudioManager created', config);
68
+ }
69
+
70
+ /**
71
+ * 初始化音频系统
72
+ * @param scene Phaser 场景实例
73
+ */
74
+ public init(scene: Phaser.Scene): void {
75
+ this.scene = scene;
76
+
77
+ // 初始化 AudioContext(用于音效)
78
+ this.initAudioContext();
79
+
80
+ // 如果启用后台暂停,设置监听器
81
+ if (this.enableBackgroundPause) {
82
+ this.setupBackgroundPauseHandlers();
83
+ }
84
+
85
+ console.log('✅ AudioManager initialized');
86
+ }
87
+
88
+ /**
89
+ * 初始化 AudioContext(用于音效播放)
90
+ */
91
+ private initAudioContext(): void {
92
+ try {
93
+ const AudioContext = window.AudioContext || (window as any).webkitAudioContext;
94
+ if (!AudioContext) {
95
+ console.warn('⚠️ AudioContext not supported');
96
+ return;
97
+ }
98
+
99
+ this.audioContext = new AudioContext();
100
+ console.log('✅ AudioContext created');
101
+ } catch (error) {
102
+ console.error('❌ Failed to init AudioContext:', error);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * 设置后台暂停监听器
108
+ */
109
+ private setupBackgroundPauseHandlers(): void {
110
+ if (!this.scene) return;
111
+
112
+ // 1. 监听 Phaser 场景暂停/恢复
113
+ this.eventHandlers.scenePause = () => {
114
+ console.log('🎵 Scene paused, pausing audio...');
115
+ this.pauseBgm();
116
+ this.suspendAudioContext();
117
+ };
118
+
119
+ this.eventHandlers.sceneResume = () => {
120
+ console.log('🎵 Scene resumed, resuming audio...');
121
+ this.resumeBgm();
122
+ this.resumeAudioContext();
123
+ };
124
+
125
+ this.scene.events.on('pause', this.eventHandlers.scenePause);
126
+ this.scene.events.on('resume', this.eventHandlers.sceneResume);
127
+
128
+ // 2. 监听页面可见性变化
129
+ this.eventHandlers.visibilityChange = () => {
130
+ console.log('👁️ Visibility changed:', document.visibilityState);
131
+
132
+ if (document.visibilityState === 'hidden') {
133
+ this.pauseBgm();
134
+ this.suspendAudioContext();
135
+ } else if (document.visibilityState === 'visible') {
136
+ this.resumeBgm();
137
+ this.resumeAudioContext();
138
+ }
139
+ };
140
+
141
+ document.addEventListener('visibilitychange', this.eventHandlers.visibilityChange);
142
+
143
+ // 3. 监听移动端的 pagehide/pageshow
144
+ this.eventHandlers.pageHide = () => {
145
+ console.log('📱 Page hide event triggered');
146
+ this.pauseBgm();
147
+ this.suspendAudioContext();
148
+ };
149
+
150
+ this.eventHandlers.pageShow = () => {
151
+ console.log('📱 Page show event triggered');
152
+ this.resumeBgm();
153
+ this.resumeAudioContext();
154
+ };
155
+
156
+ window.addEventListener('pagehide', this.eventHandlers.pageHide);
157
+ window.addEventListener('pageshow', this.eventHandlers.pageShow);
158
+
159
+ // 4. 监听窗口 blur/focus
160
+ this.eventHandlers.blur = () => {
161
+ console.log('🔍 Window blur event triggered');
162
+ this.pauseBgm();
163
+ this.suspendAudioContext();
164
+ };
165
+
166
+ this.eventHandlers.focus = () => {
167
+ console.log('🔍 Window focus event triggered');
168
+ this.resumeBgm();
169
+ this.resumeAudioContext();
170
+ };
171
+
172
+ window.addEventListener('blur', this.eventHandlers.blur);
173
+ window.addEventListener('focus', this.eventHandlers.focus);
174
+
175
+ // 5. 监听 SDK 音量变化
176
+ this.eventHandlers.volumeChange = (volume: number) => {
177
+ console.log('🔊 SDK volume changed:', volume);
178
+ this.setVolume(volume);
179
+ };
180
+
181
+ sdk.on('playcraftVolume', this.eventHandlers.volumeChange);
182
+
183
+ // 6. 启动后台音频监控守护进程
184
+ this.startWatchdog();
185
+ }
186
+
187
+ /**
188
+ * 启动后台音频监控守护进程
189
+ */
190
+ private startWatchdog(): void {
191
+ this.watchdogInterval = window.setInterval(() => {
192
+ // 只在页面隐藏时检查
193
+ if (document.visibilityState === 'hidden' || document.hidden) {
194
+ // 如果背景音乐在播放,强制暂停
195
+ if (this.bgm && !this.bgm.paused) {
196
+ console.warn('⚠️ Watchdog: BGM still playing in background, force pausing!');
197
+ this.bgm.pause();
198
+ }
199
+
200
+ // 如果 AudioContext 在运行,强制暂停
201
+ if (this.audioContext && this.audioContext.state === 'running') {
202
+ console.warn('⚠️ Watchdog: AudioContext still running in background, force suspending!');
203
+ this.audioContext.suspend().catch(err => {
204
+ console.error('Failed to suspend AudioContext:', err);
205
+ });
206
+ }
207
+ }
208
+ }, this.watchdogIntervalTime);
209
+
210
+ console.log('✅ Background audio watchdog started');
211
+ }
212
+
213
+ /**
214
+ * 停止后台音频监控守护进程
215
+ */
216
+ private stopWatchdog(): void {
217
+ if (this.watchdogInterval !== null) {
218
+ window.clearInterval(this.watchdogInterval);
219
+ this.watchdogInterval = null;
220
+ console.log('✅ Background audio watchdog stopped');
221
+ }
222
+ }
223
+
224
+ /**
225
+ * 加载并播放背景音乐
226
+ */
227
+ public async playBgm(url?: string): Promise<void> {
228
+ if (url) {
229
+ this.bgmConfig.url = url;
230
+ }
231
+
232
+ if (!this.bgmConfig.url) {
233
+ console.warn('⚠️ BGM URL not set');
234
+ return;
235
+ }
236
+
237
+ if (this.isBgmPlaying) {
238
+ console.log('🎵 BGM already playing');
239
+ return;
240
+ }
241
+
242
+ try {
243
+ console.log('🎵 Loading and playing BGM...');
244
+
245
+ // 创建或复用 Audio 对象
246
+ if (!this.bgm) {
247
+ this.bgm = new Audio();
248
+ this.bgm.src = this.bgmConfig.url;
249
+ this.bgm.loop = this.bgmConfig.loop;
250
+ this.bgm.volume = this.bgmConfig.volume;
251
+ this.bgm.preload = 'auto';
252
+
253
+ // 禁用媒体会话(防止某些浏览器的后台播放)
254
+ if ('mediaSession' in navigator) {
255
+ try {
256
+ (navigator as any).mediaSession.playbackState = 'none';
257
+ } catch (e) {
258
+ console.warn('无法设置 mediaSession:', e);
259
+ }
260
+ }
261
+
262
+ // 监听事件
263
+ this.bgm.addEventListener('play', () => {
264
+ console.log('🎵 BGM play event triggered');
265
+ });
266
+
267
+ this.bgm.addEventListener('pause', () => {
268
+ console.log('🎵 BGM pause event triggered');
269
+ });
270
+
271
+ this.bgm.addEventListener('error', (e) => {
272
+ console.error('❌ BGM error:', e);
273
+ });
274
+
275
+ this.bgm.addEventListener('ended', () => {
276
+ console.log('🎵 BGM ended');
277
+ if (this.isBgmPlaying && !this.isGameEnded) {
278
+ this.bgm?.play().catch(err => console.error('Failed to restart BGM:', err));
279
+ }
280
+ });
281
+ }
282
+
283
+ // 恢复 AudioContext(如果需要)
284
+ if (this.audioContext && this.audioContext.state === 'suspended') {
285
+ await this.audioContext.resume();
286
+ }
287
+
288
+ // 播放
289
+ await this.bgm.play();
290
+ this.isBgmPlaying = true;
291
+ console.log('✅ BGM playing');
292
+ } catch (error) {
293
+ console.error('❌ Failed to play BGM:', error);
294
+ throw error;
295
+ }
296
+ }
297
+
298
+ /**
299
+ * 暂停背景音乐
300
+ */
301
+ public pauseBgm(): void {
302
+ if (this.bgm && !this.bgm.paused) {
303
+ this.bgm.pause();
304
+ console.log('✅ BGM paused');
305
+ }
306
+ }
307
+
308
+ /**
309
+ * 恢复背景音乐
310
+ */
311
+ public resumeBgm(): void {
312
+ if (this.bgm && this.isBgmPlaying && this.bgm.paused && !this.isGameEnded) {
313
+ this.bgm.play().catch(err => {
314
+ console.error('❌ Failed to resume BGM:', err);
315
+ });
316
+ console.log('✅ BGM resumed');
317
+ }
318
+ }
319
+
320
+ /**
321
+ * 停止背景音乐
322
+ */
323
+ public stopBgm(): void {
324
+ if (this.bgm) {
325
+ this.bgm.pause();
326
+ this.bgm.currentTime = 0;
327
+ this.isBgmPlaying = false;
328
+ console.log('✅ BGM stopped');
329
+ }
330
+ }
331
+
332
+ /**
333
+ * 暂停 AudioContext
334
+ */
335
+ private suspendAudioContext(): void {
336
+ if (this.audioContext && this.audioContext.state === 'running') {
337
+ this.audioContext.suspend().then(() => {
338
+ console.log('✅ AudioContext suspended');
339
+ }).catch(err => {
340
+ console.error('❌ Failed to suspend AudioContext:', err);
341
+ });
342
+ }
343
+ }
344
+
345
+ /**
346
+ * 恢复 AudioContext
347
+ */
348
+ private resumeAudioContext(): void {
349
+ if (this.audioContext && this.audioContext.state === 'suspended') {
350
+ this.audioContext.resume().then(() => {
351
+ console.log('✅ AudioContext resumed');
352
+ }).catch(err => {
353
+ console.error('❌ Failed to resume AudioContext:', err);
354
+ });
355
+ }
356
+ }
357
+
358
+ /**
359
+ * 设置音量
360
+ * @param volume 音量(0-1)
361
+ */
362
+ public setVolume(volume: number): void {
363
+ // 设置背景音乐音量
364
+ if (this.bgm) {
365
+ this.bgm.volume = volume;
366
+
367
+ // 如果音量为 0,暂停背景音乐
368
+ if (volume === 0 && !this.bgm.paused) {
369
+ this.pauseBgm();
370
+ }
371
+ // 如果音量大于 0 且之前在播放,恢复播放
372
+ else if (volume > 0 && this.bgm.paused && this.isBgmPlaying && !this.isGameEnded) {
373
+ this.resumeBgm();
374
+ }
375
+ }
376
+
377
+ // 音效音量会在播放时应用
378
+ this.effectVolume = volume;
379
+ }
380
+
381
+ /**
382
+ * 预加载音效
383
+ * @param key 音效标识
384
+ * @param url 音效 URL
385
+ */
386
+ public async preloadEffect(key: string, url: string): Promise<void> {
387
+ if (!this.audioContext) {
388
+ console.warn('⚠️ AudioContext not available');
389
+ return;
390
+ }
391
+
392
+ try {
393
+ console.log(`🎵 Preloading effect: ${key}`);
394
+ const response = await fetch(url);
395
+ const arrayBuffer = await response.arrayBuffer();
396
+ const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
397
+ this.audioBufferCache.set(key, audioBuffer);
398
+ console.log(`✅ Effect loaded: ${key}`);
399
+ } catch (error) {
400
+ console.error(`❌ Failed to preload effect ${key}:`, error);
401
+ }
402
+ }
403
+
404
+ /**
405
+ * 添加已解码的音效 Buffer
406
+ * @param key 音效标识
407
+ * @param buffer 音效 AudioBuffer
408
+ */
409
+ public addEffectBuffer(key: string, buffer: AudioBuffer): void {
410
+ this.audioBufferCache.set(key, buffer);
411
+ console.log(`✅ Effect buffer added: ${key}`);
412
+ }
413
+
414
+ /**
415
+ * 播放音效
416
+ * @param key 音效标识
417
+ * @param volume 音量(可选,默认使用全局音效音量)
418
+ */
419
+ public playEffect(key: string, volume?: number): void {
420
+ if (this.isGameEnded) return;
421
+
422
+ if (!this.audioContext) {
423
+ console.warn('⚠️ AudioContext not available');
424
+ return;
425
+ }
426
+
427
+ const buffer = this.audioBufferCache.get(key);
428
+ if (!buffer) {
429
+ console.warn(`⚠️ Effect not loaded: ${key}`);
430
+ return;
431
+ }
432
+
433
+ try {
434
+ // 恢复 AudioContext(如果被暂停)
435
+ if (this.audioContext.state === 'suspended') {
436
+ this.audioContext.resume();
437
+ }
438
+
439
+ // 创建音频源节点
440
+ const source = this.audioContext.createBufferSource();
441
+ source.buffer = buffer;
442
+
443
+ // 创建增益节点(用于控制音量)
444
+ const gainNode = this.audioContext.createGain();
445
+ gainNode.gain.value = volume ?? this.effectVolume;
446
+
447
+ // 连接节点
448
+ source.connect(gainNode);
449
+ gainNode.connect(this.audioContext.destination);
450
+
451
+ // 播放
452
+ source.start(0);
453
+ console.log(`🎵 Playing effect: ${key}`);
454
+ } catch (error) {
455
+ console.error(`❌ Failed to play effect ${key}:`, error);
456
+ }
457
+ }
458
+
459
+ /**
460
+ * 播放音效(使用 Audio 标签,支持重叠播放)
461
+ * @param url 音效 URL
462
+ * @param volume 音量(可选)
463
+ */
464
+ public playEffectByAudio(url: string, volume?: number): void {
465
+ if (this.isGameEnded) return;
466
+
467
+ try {
468
+ const audio = new Audio();
469
+ audio.src = url;
470
+ audio.volume = volume ?? this.effectVolume;
471
+
472
+ // 播放完成后清理
473
+ audio.addEventListener('ended', () => {
474
+ audio.src = '';
475
+ }, { once: true });
476
+
477
+ audio.play().catch(err => {
478
+ console.error('❌ Failed to play effect:', err);
479
+ });
480
+ } catch (error) {
481
+ console.error('❌ Failed to play effect by Audio:', error);
482
+ }
483
+ }
484
+
485
+ /**
486
+ * 标记游戏结束(停止音频恢复)
487
+ */
488
+ public setGameEnded(ended: boolean): void {
489
+ this.isGameEnded = ended;
490
+ if (ended) {
491
+ this.stopBgm();
492
+ }
493
+ }
494
+
495
+ /**
496
+ * 清理资源
497
+ */
498
+ public destroy(): void {
499
+ console.log('🎵 Destroying AudioManager...');
500
+
501
+ // 停止守护进程
502
+ this.stopWatchdog();
503
+
504
+ // 移除场景事件监听
505
+ if (this.scene) {
506
+ if (this.eventHandlers.scenePause) {
507
+ this.scene.events.off('pause', this.eventHandlers.scenePause);
508
+ }
509
+ if (this.eventHandlers.sceneResume) {
510
+ this.scene.events.off('resume', this.eventHandlers.sceneResume);
511
+ }
512
+ }
513
+
514
+ // 移除 document 事件监听
515
+ if (this.eventHandlers.visibilityChange) {
516
+ document.removeEventListener('visibilitychange', this.eventHandlers.visibilityChange);
517
+ }
518
+
519
+ // 移除 window 事件监听
520
+ if (this.eventHandlers.pageHide) {
521
+ window.removeEventListener('pagehide', this.eventHandlers.pageHide);
522
+ }
523
+ if (this.eventHandlers.pageShow) {
524
+ window.removeEventListener('pageshow', this.eventHandlers.pageShow);
525
+ }
526
+ if (this.eventHandlers.blur) {
527
+ window.removeEventListener('blur', this.eventHandlers.blur);
528
+ }
529
+ if (this.eventHandlers.focus) {
530
+ window.removeEventListener('focus', this.eventHandlers.focus);
531
+ }
532
+
533
+ // 移除 SDK 事件监听
534
+ if (this.eventHandlers.volumeChange) {
535
+ sdk.off('playcraftVolume', this.eventHandlers.volumeChange);
536
+ }
537
+
538
+ // 清理背景音乐
539
+ if (this.bgm) {
540
+ this.bgm.pause();
541
+ this.bgm.currentTime = 0;
542
+ this.bgm.src = '';
543
+ this.bgm = null;
544
+ }
545
+
546
+ // 清理 AudioContext
547
+ if (this.audioContext) {
548
+ this.audioContext.close().catch(err => {
549
+ console.warn('Failed to close AudioContext:', err);
550
+ });
551
+ this.audioContext = null;
552
+ }
553
+
554
+ // 清理缓存
555
+ this.audioBufferCache.clear();
556
+
557
+ console.log('✅ AudioManager destroyed');
558
+ }
559
+ }