SoundController.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. import { _decorator, Component, Node, Slider, Button, Sprite, SpriteFrame, ProgressBar, resources, tween, Vec3 } from 'cc';
  2. import { AudioManager } from '../../AudioManager/AudioManager';
  3. import { SaveDataManager } from '../../LevelSystem/SaveDataManager';
  4. import { Audio } from '../../AudioManager/AudioManager';
  5. const { ccclass, property } = _decorator;
  6. /**
  7. * 音频控制器
  8. * 管理音效和音乐的音量控制以及开关状态
  9. */
  10. @ccclass('SoundController')
  11. export class SoundController extends Component {
  12. // 音效控制组件
  13. @property({ type: Slider, tooltip: '音效音量滑动条' })
  14. public soundEffectSlider: Slider = null;
  15. @property({ type: Button, tooltip: '音效开关按钮' })
  16. public soundEffectCheckbox: Button = null;
  17. @property({ type: Node, tooltip: '音效勾选标记节点' })
  18. public soundEffectCheck: Node = null;
  19. @property({ type: ProgressBar, tooltip: '音效进度条' })
  20. public soundEffectProgressBar: ProgressBar = null;
  21. // 音乐控制组件
  22. @property({ type: Slider, tooltip: '音乐音量滑动条' })
  23. public musicSlider: Slider = null;
  24. @property({ type: Button, tooltip: '音乐开关按钮' })
  25. public musicCheckbox: Button = null;
  26. @property({ type: Node, tooltip: '音乐勾选标记节点' })
  27. public musicCheck: Node = null;
  28. @property({ type: ProgressBar, tooltip: '音乐进度条' })
  29. public musicProgressBar: ProgressBar = null;
  30. // 总音频开关(控制音效与音乐的总开/关)
  31. @property({ type: Button, tooltip: '总音频开关:开 按钮' })
  32. public vibrationOnButton: Button = null;
  33. @property({ type: Button, tooltip: '总音频开关:关 按钮' })
  34. public vibrationOffButton: Button = null;
  35. @property({ type: Node, tooltip: '总音频开关滑块节点' })
  36. public vibrationSlideNode: Node = null;
  37. // 音量状态
  38. private soundEffectEnabled: boolean = true;
  39. private musicEnabled: boolean = true;
  40. private savedSoundEffectVolume: number = 0.5;
  41. private savedMusicVolume: number = 0.5;
  42. private vibrationEnabled: boolean = true;
  43. onLoad() {
  44. this.initializeSliders();
  45. this.bindEvents();
  46. this.loadSettings();
  47. }
  48. /**
  49. * 初始化滑动条
  50. */
  51. private initializeSliders() {
  52. if (this.soundEffectSlider) {
  53. this.soundEffectSlider.progress = this.savedSoundEffectVolume;
  54. this.updateProgressBar(this.soundEffectProgressBar, this.soundEffectSlider.progress);
  55. }
  56. if (this.musicSlider) {
  57. this.musicSlider.progress = this.savedMusicVolume;
  58. this.updateProgressBar(this.musicProgressBar, this.musicSlider.progress);
  59. }
  60. }
  61. /**
  62. * 绑定事件
  63. */
  64. private bindEvents() {
  65. // 音效滑动条事件
  66. if (this.soundEffectSlider) {
  67. this.soundEffectSlider.node.on('slide', this.onSoundEffectSliderChange, this);
  68. }
  69. // 音乐滑动条事件
  70. if (this.musicSlider) {
  71. this.musicSlider.node.on('slide', this.onMusicSliderChange, this);
  72. }
  73. // 音效开关按钮事件
  74. if (this.soundEffectCheckbox) {
  75. this.soundEffectCheckbox.node.on(Button.EventType.CLICK, this.onSoundEffectCheckboxClick, this);
  76. }
  77. // 音乐开关按钮事件
  78. if (this.musicCheckbox) {
  79. this.musicCheckbox.node.on(Button.EventType.CLICK, this.onMusicCheckboxClick, this);
  80. }
  81. // 震动开关按钮事件
  82. if (this.vibrationOnButton) {
  83. this.vibrationOnButton.node.on(Button.EventType.CLICK, this.onVibrationOnClick, this);
  84. }
  85. if (this.vibrationOffButton) {
  86. this.vibrationOffButton.node.on(Button.EventType.CLICK, this.onVibrationOffClick, this);
  87. }
  88. }
  89. /**
  90. * 音效滑动条变化事件
  91. */
  92. private onSoundEffectSliderChange(slider: Slider) {
  93. if (this.soundEffectEnabled) {
  94. this.savedSoundEffectVolume = slider.progress;
  95. this.updateProgressBar(this.soundEffectProgressBar, slider.progress);
  96. // TODO: 应用音效音量到音频系统
  97. this.applySoundEffectVolume(slider.progress);
  98. // 立即保存设置,确保退出前已落盘
  99. this.saveSettings();
  100. }
  101. }
  102. /**
  103. * 音乐滑动条变化事件
  104. */
  105. private onMusicSliderChange(slider: Slider) {
  106. if (this.musicEnabled) {
  107. this.savedMusicVolume = slider.progress;
  108. this.updateProgressBar(this.musicProgressBar, slider.progress);
  109. // TODO: 应用音乐音量到音频系统
  110. this.applyMusicVolume(slider.progress);
  111. // 立即保存设置,确保退出前已落盘
  112. this.saveSettings();
  113. }
  114. }
  115. /**
  116. * 音效开关按钮点击事件
  117. */
  118. private onSoundEffectCheckboxClick() {
  119. Audio.playUISound('data/弹球音效/ui play');
  120. this.soundEffectEnabled = !this.soundEffectEnabled;
  121. if (this.soundEffectEnabled) {
  122. // 开启音效:恢复之前保存的音量
  123. this.soundEffectSlider.progress = this.savedSoundEffectVolume;
  124. this.updateProgressBar(this.soundEffectProgressBar, this.savedSoundEffectVolume);
  125. this.applySoundEffectVolume(this.savedSoundEffectVolume);
  126. } else {
  127. // 关闭音效:保存当前音量并设置为0
  128. this.savedSoundEffectVolume = this.soundEffectSlider.progress;
  129. this.soundEffectSlider.progress = 0;
  130. this.updateProgressBar(this.soundEffectProgressBar, 0);
  131. this.applySoundEffectVolume(0);
  132. }
  133. // 更新勾选标记显示
  134. if (this.soundEffectCheck) {
  135. this.soundEffectCheck.active = this.soundEffectEnabled;
  136. }
  137. this.saveSettings();
  138. // 同步总开关位置:当音效与音乐均开启时为“开”,否则为“关”
  139. this.vibrationEnabled = this.soundEffectEnabled && this.musicEnabled;
  140. this.updateVibrationSlideButtonImmediate();
  141. this.saveVibrationSetting();
  142. }
  143. /**
  144. * 音乐开关按钮点击事件
  145. */
  146. private onMusicCheckboxClick() {
  147. Audio.playUISound('data/弹球音效/ui play');
  148. this.musicEnabled = !this.musicEnabled;
  149. if (this.musicEnabled) {
  150. // 开启音乐:恢复之前保存的音量
  151. this.musicSlider.progress = this.savedMusicVolume;
  152. this.updateProgressBar(this.musicProgressBar, this.savedMusicVolume);
  153. this.applyMusicVolume(this.savedMusicVolume);
  154. } else {
  155. // 关闭音乐:保存当前音量并设置为0
  156. this.savedMusicVolume = this.musicSlider.progress;
  157. this.musicSlider.progress = 0;
  158. this.updateProgressBar(this.musicProgressBar, 0);
  159. this.applyMusicVolume(0);
  160. }
  161. // 更新勾选标记显示
  162. if (this.musicCheck) {
  163. this.musicCheck.active = this.musicEnabled;
  164. }
  165. this.saveSettings();
  166. // 同步总开关位置:当音效与音乐均开启时为“开”,否则为“关”
  167. this.vibrationEnabled = this.soundEffectEnabled && this.musicEnabled;
  168. this.updateVibrationSlideButtonImmediate();
  169. this.saveVibrationSetting();
  170. }
  171. /**
  172. * 震动开启按钮点击事件
  173. */
  174. private onVibrationOnClick() {
  175. Audio.playUISound('data/弹球音效/ui play');
  176. this.vibrationEnabled = true;
  177. this.updateVibrationSlideButton();
  178. this.saveVibrationSetting();
  179. // 开启音效和音乐
  180. this.enableSoundEffectAndMusic();
  181. }
  182. /**
  183. * 震动关闭按钮点击事件
  184. */
  185. private onVibrationOffClick() {
  186. Audio.playUISound('data/弹球音效/ui play');
  187. this.vibrationEnabled = false;
  188. this.updateVibrationSlideButton();
  189. this.saveVibrationSetting();
  190. // 关闭音效和音乐
  191. this.disableSoundEffectAndMusic();
  192. }
  193. /**
  194. * 更新震动滑动按钮位置
  195. */
  196. private updateVibrationSlideButton() {
  197. if (!this.vibrationSlideNode || !this.vibrationOnButton || !this.vibrationOffButton) {
  198. return;
  199. }
  200. // 获取目标按钮的位置
  201. const targetButton = this.vibrationEnabled ? this.vibrationOnButton : this.vibrationOffButton;
  202. const targetPosition = targetButton.node.position.clone();
  203. // 使用缓动动画移动滑动节点
  204. tween(this.vibrationSlideNode)
  205. .to(0.3, { position: targetPosition }, { easing: 'sineInOut' })
  206. .start();
  207. }
  208. /**
  209. * 保存震动设置
  210. */
  211. private saveVibrationSetting() {
  212. const saveDataManager = SaveDataManager.getInstance();
  213. if (saveDataManager) {
  214. saveDataManager.updateSetting('vibrationEnabled', this.vibrationEnabled);
  215. }
  216. }
  217. /**
  218. * 立即更新震动滑动按钮位置(不使用动画)
  219. */
  220. private updateVibrationSlideButtonImmediate() {
  221. if (!this.vibrationSlideNode || !this.vibrationOnButton || !this.vibrationOffButton) {
  222. return;
  223. }
  224. // 获取目标按钮的位置
  225. const targetButton = this.vibrationEnabled ? this.vibrationOnButton : this.vibrationOffButton;
  226. const targetPosition = targetButton.node.position.clone();
  227. // 直接设置位置,不使用动画
  228. this.vibrationSlideNode.position = targetPosition;
  229. }
  230. /**
  231. * 更新进度条显示
  232. */
  233. private updateProgressBar(progressBar: ProgressBar, progress: number) {
  234. if (!progressBar) return;
  235. // 直接设置ProgressBar的进度值
  236. // ProgressBar会自动处理背景和前景的显示,保持黑色背景不变
  237. progressBar.progress = progress;
  238. }
  239. /**
  240. * 应用音效音量到音频系统
  241. */
  242. private applySoundEffectVolume(volume: number) {
  243. const audioManager = AudioManager.getInstance();
  244. if (audioManager) {
  245. audioManager.setAllSoundVolume(volume);
  246. }
  247. //console.log(`[SoundController] 设置音效音量: ${volume}`);
  248. }
  249. /**
  250. * 应用音乐音量到音频系统
  251. */
  252. private applyMusicVolume(volume: number) {
  253. const audioManager = AudioManager.getInstance();
  254. if (audioManager) {
  255. audioManager.setMusicVolume(volume);
  256. }
  257. //console.log(`[SoundController] 设置音乐音量: ${volume}`);
  258. }
  259. /**
  260. * 保存设置到本地存储
  261. */
  262. private saveSettings() {
  263. const settings = {
  264. soundEffectEnabled: this.soundEffectEnabled,
  265. musicEnabled: this.musicEnabled,
  266. soundEffectVolume: this.savedSoundEffectVolume,
  267. musicVolume: this.savedMusicVolume
  268. };
  269. // 兼容已有的本地键(微信端也可用)
  270. localStorage.setItem('audioSettings', JSON.stringify(settings));
  271. // 写入到 SaveDataManager 的玩家设置,保证随存档持久化
  272. const sdm = SaveDataManager.getInstance();
  273. if (sdm && typeof sdm.updateSetting === 'function') {
  274. sdm.updateSetting('soundEnabled', this.soundEffectEnabled);
  275. sdm.updateSetting('musicEnabled', this.musicEnabled);
  276. sdm.updateSetting('soundVolume', this.savedSoundEffectVolume);
  277. sdm.updateSetting('musicVolume', this.savedMusicVolume);
  278. // 设置变更需要立即强制保存,避免5秒节流导致未写盘
  279. if (typeof sdm.savePlayerData === 'function') {
  280. sdm.savePlayerData(true);
  281. }
  282. }
  283. }
  284. /**
  285. * 从本地存储加载设置
  286. */
  287. private loadSettings() {
  288. const sdm = SaveDataManager.getInstance();
  289. let loadedFromSDM = false;
  290. if (sdm && typeof sdm.getSetting === 'function' && sdm.getPlayerData()) {
  291. try {
  292. this.soundEffectEnabled = sdm.getSetting('soundEnabled') ?? true;
  293. this.musicEnabled = sdm.getSetting('musicEnabled') ?? true;
  294. this.savedSoundEffectVolume = sdm.getSetting('soundVolume') ?? 0.8;
  295. this.savedMusicVolume = sdm.getSetting('musicVolume') ?? 0.6;
  296. loadedFromSDM = true;
  297. } catch (e) {
  298. // 读取失败则走本地兜底
  299. }
  300. }
  301. if (!loadedFromSDM) {
  302. const savedSettings = localStorage.getItem('audioSettings');
  303. if (savedSettings) {
  304. try {
  305. const settings = JSON.parse(savedSettings);
  306. this.soundEffectEnabled = settings.soundEffectEnabled ?? true;
  307. this.musicEnabled = settings.musicEnabled ?? true;
  308. this.savedSoundEffectVolume = settings.soundEffectVolume ?? 0.8;
  309. this.savedMusicVolume = settings.musicVolume ?? 0.6;
  310. } catch (e) {
  311. console.warn('[SoundController] 加载音频设置失败:', e);
  312. }
  313. }
  314. }
  315. // 总音频开关的显示应与当前音效和音乐状态一致
  316. this.vibrationEnabled = this.soundEffectEnabled && this.musicEnabled;
  317. // 更新UI显示
  318. this.updateUI();
  319. }
  320. /**
  321. * 更新UI显示
  322. */
  323. private updateUI() {
  324. // 更新勾选标记
  325. if (this.soundEffectCheck) {
  326. this.soundEffectCheck.active = this.soundEffectEnabled;
  327. }
  328. if (this.musicCheck) {
  329. this.musicCheck.active = this.musicEnabled;
  330. }
  331. // 更新滑动条和进度条
  332. if (this.soundEffectSlider) {
  333. this.soundEffectSlider.progress = this.soundEffectEnabled ? this.savedSoundEffectVolume : 0;
  334. this.updateProgressBar(this.soundEffectProgressBar, this.soundEffectSlider.progress);
  335. // 应用当前滑条音量到音频系统(初始化时也生效)
  336. this.applySoundEffectVolume(this.soundEffectSlider.progress);
  337. }
  338. if (this.musicSlider) {
  339. this.musicSlider.progress = this.musicEnabled ? this.savedMusicVolume : 0;
  340. this.updateProgressBar(this.musicProgressBar, this.musicSlider.progress);
  341. // 应用当前滑条音量到音频系统(初始化时也生效)
  342. this.applyMusicVolume(this.musicSlider.progress);
  343. }
  344. // 更新震动滑动按钮位置(不使用动画,直接设置位置)
  345. // 同步总音频开关位置:当音效与音乐均为开启时显示“开”,否则显示“关”
  346. this.vibrationEnabled = this.soundEffectEnabled && this.musicEnabled;
  347. this.updateVibrationSlideButtonImmediate();
  348. }
  349. /**
  350. * 获取当前音效音量
  351. */
  352. public getSoundEffectVolume(): number {
  353. return this.soundEffectEnabled ? this.savedSoundEffectVolume : 0;
  354. }
  355. /**
  356. * 获取当前音乐音量
  357. */
  358. public getMusicVolume(): number {
  359. return this.musicEnabled ? this.savedMusicVolume : 0;
  360. }
  361. /**
  362. * 设置音效音量
  363. */
  364. public setSoundEffectVolume(volume: number) {
  365. this.savedSoundEffectVolume = Math.max(0, Math.min(1, volume));
  366. if (this.soundEffectEnabled && this.soundEffectSlider) {
  367. this.soundEffectSlider.progress = this.savedSoundEffectVolume;
  368. this.updateProgressBar(this.soundEffectProgressBar, this.savedSoundEffectVolume);
  369. this.applySoundEffectVolume(this.savedSoundEffectVolume);
  370. }
  371. this.saveSettings();
  372. }
  373. /**
  374. * 设置音乐音量
  375. */
  376. public setMusicVolume(volume: number) {
  377. this.savedMusicVolume = Math.max(0, Math.min(1, volume));
  378. if (this.musicEnabled && this.musicSlider) {
  379. this.musicSlider.progress = this.savedMusicVolume;
  380. this.updateProgressBar(this.musicProgressBar, this.savedMusicVolume);
  381. this.applyMusicVolume(this.savedMusicVolume);
  382. }
  383. this.saveSettings();
  384. }
  385. /**
  386. * 获取当前震动状态
  387. */
  388. public getVibrationEnabled(): boolean {
  389. return this.vibrationEnabled;
  390. }
  391. /**
  392. * 设置震动状态
  393. */
  394. public setVibrationEnabled(enabled: boolean) {
  395. this.vibrationEnabled = enabled;
  396. this.updateVibrationSlideButton();
  397. this.saveVibrationSetting();
  398. }
  399. /**
  400. * 开启音效和音乐
  401. */
  402. private enableSoundEffectAndMusic() {
  403. // 如果音效未开启,则开启音效
  404. if (!this.soundEffectEnabled) {
  405. this.soundEffectEnabled = true;
  406. // 恢复之前保存的音量或使用默认值0.5
  407. const volumeToRestore = this.savedSoundEffectVolume > 0 ? this.savedSoundEffectVolume : 0.5;
  408. this.soundEffectSlider.progress = volumeToRestore;
  409. this.updateProgressBar(this.soundEffectProgressBar, volumeToRestore);
  410. this.applySoundEffectVolume(volumeToRestore);
  411. // 更新勾选标记显示
  412. if (this.soundEffectCheck) {
  413. this.soundEffectCheck.active = true;
  414. }
  415. }
  416. // 如果音乐未开启,则开启音乐
  417. if (!this.musicEnabled) {
  418. this.musicEnabled = true;
  419. // 恢复之前保存的音量或使用默认值0.5
  420. const volumeToRestore = this.savedMusicVolume > 0 ? this.savedMusicVolume : 0.5;
  421. this.musicSlider.progress = volumeToRestore;
  422. this.updateProgressBar(this.musicProgressBar, volumeToRestore);
  423. this.applyMusicVolume(volumeToRestore);
  424. // 更新勾选标记显示
  425. if (this.musicCheck) {
  426. this.musicCheck.active = true;
  427. }
  428. }
  429. this.saveSettings();
  430. // 同步总开关视觉状态为“开”
  431. this.vibrationEnabled = true;
  432. this.updateVibrationSlideButtonImmediate();
  433. this.saveVibrationSetting();
  434. }
  435. /**
  436. * 关闭音效和音乐
  437. */
  438. private disableSoundEffectAndMusic() {
  439. // 关闭音效
  440. if (this.soundEffectEnabled) {
  441. this.soundEffectEnabled = false;
  442. // 保存当前音量并设置为0
  443. this.savedSoundEffectVolume = this.soundEffectSlider.progress;
  444. this.soundEffectSlider.progress = 0;
  445. this.updateProgressBar(this.soundEffectProgressBar, 0);
  446. this.applySoundEffectVolume(0);
  447. // 更新勾选标记显示
  448. if (this.soundEffectCheck) {
  449. this.soundEffectCheck.active = false;
  450. }
  451. }
  452. // 关闭音乐
  453. if (this.musicEnabled) {
  454. this.musicEnabled = false;
  455. // 保存当前音量并设置为0
  456. this.savedMusicVolume = this.musicSlider.progress;
  457. this.musicSlider.progress = 0;
  458. this.updateProgressBar(this.musicProgressBar, 0);
  459. this.applyMusicVolume(0);
  460. // 更新勾选标记显示
  461. if (this.musicCheck) {
  462. this.musicCheck.active = false;
  463. }
  464. }
  465. this.saveSettings();
  466. // 同步总开关视觉状态为“关”
  467. this.vibrationEnabled = false;
  468. this.updateVibrationSlideButtonImmediate();
  469. this.saveVibrationSetting();
  470. }
  471. }