@playcraft/cli 0.0.53 → 0.0.56

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