diff --git a/assets/main.scene b/assets/main.scene index fcdaca8..07afe9b 100644 --- a/assets/main.scene +++ b/assets/main.scene @@ -515,6 +515,10 @@ "__uuid__": "cff2809d-6daa-4749-a911-bb99e97b4b54", "__expectedType__": "cc.Prefab" }, + "buttonClickAudio": { + "__uuid__": "798824f1-0e20-48b7-ad8a-fb24d55bf986", + "__expectedType__": "cc.AudioClip" + }, "_id": "c2b3nbzv9JuZmP2jxQyN72" }, { diff --git a/assets/main.ts b/assets/main.ts index 4ca9f02..d818b1d 100644 --- a/assets/main.ts +++ b/assets/main.ts @@ -1,6 +1,7 @@ -import { _decorator, Component, Prefab } from 'cc'; +import { _decorator, Component, Prefab, AudioClip } from 'cc'; import { ViewManager } from './scripts/core/ViewManager'; import { ToastManager } from './scripts/utils/ToastManager'; +import { AudioManager } from './scripts/utils/AudioManager'; const { ccclass, property } = _decorator; /** @@ -33,6 +34,9 @@ export class main extends Component { @property({ type: Prefab, tooltip: 'Toast 预制体' }) toastPrefab: Prefab | null = null; + @property({ type: AudioClip, tooltip: '通用按钮点击音效' }) + buttonClickAudio: AudioClip | null = null; + /** * onLoad 比 start 更早执行 * 确保 ViewManager 在 PageLoading.start() 之前初始化 @@ -109,5 +113,7 @@ export class main extends Component { if (this.toastPrefab) { ToastManager.instance.init(this.toastPrefab, this.node); } + + AudioManager.instance.init(this.buttonClickAudio, this.node); } } diff --git a/assets/prefabs/PageHome.ts b/assets/prefabs/PageHome.ts index ea5953b..ddb5c8f 100644 --- a/assets/prefabs/PageHome.ts +++ b/assets/prefabs/PageHome.ts @@ -8,6 +8,7 @@ import { StaminaInfo } from 'db://assets/scripts/types/ApiTypes'; import { AuthManager } from 'db://assets/scripts/utils/AuthManager'; import { AchievementTitleManager } from 'db://assets/scripts/utils/AchievementTitleManager'; import { ShareManager } from 'db://assets/scripts/utils/ShareManager'; +import { AudioManager } from 'db://assets/scripts/utils/AudioManager'; const { ccclass, property } = _decorator; /** @@ -103,6 +104,7 @@ export class PageHome extends BaseView { private _onStartGameClick(): void { if (this._isAnimating) return; + AudioManager.instance.playButtonClick(); console.log('[PageHome] 开始游戏按钮点击'); // 体力检查 @@ -138,6 +140,7 @@ export class PageHome extends BaseView { * PK按钮点击回调 */ private _onPkClick(): void { + AudioManager.instance.playButtonClick(); console.log('[PageHome] PK按钮点击'); ViewManager.instance.open('PageWriteLevels'); } diff --git a/assets/prefabs/PageLevel.ts b/assets/prefabs/PageLevel.ts index 2a7df61..4c03748 100644 --- a/assets/prefabs/PageLevel.ts +++ b/assets/prefabs/PageLevel.ts @@ -18,6 +18,7 @@ import { CommonModal } from 'db://assets/prefabs/CommonModal'; import { ApiEnvelope, StaminaInfo, NextLevelData, SubmitShareLevel } from 'db://assets/scripts/types/ApiTypes'; import { AchievementTitleManager } from 'db://assets/scripts/utils/AchievementTitleManager'; import { applyRoundedCorner } from 'db://assets/scripts/utils/roundedMaterial.utils'; +import { AudioManager } from 'db://assets/scripts/utils/AudioManager'; const { ccclass, property } = _decorator; /** @@ -646,7 +647,11 @@ export class PageLevel extends BaseView { const editBox = inputNode.getComponent(EditBox); if (editBox) { editBox.placeholder = ''; - editBox.maxLength = chars.length; + // 不限制单格 maxLength:iOS 拼音 / 日韩 IME 在选词前需要键入比目标字数更长的拼写串, + // 例如答案"你好"(2 字)需键入"nihao"(5 字符)才能上屏;若 maxLength=2 会在第三个 + // 拼音字符就被原生键盘截断,用户连选词都做不到。最终长度限制由 distributeInputText + // 在 EDITING_DID_ENDED 时裁剪到每格 1 字(见 applyInputTextToBlocks)。 + editBox.maxLength = -1; editBox.string = ''; editBox.node.on(EditBox.EventType.EDITING_DID_BEGAN, this.onInputEditingBegan, this); editBox.node.on(EditBox.EventType.TEXT_CHANGED, this.onInputTextChanged, this); @@ -859,7 +864,7 @@ export class PageLevel extends BaseView { */ private onIconSettingClick(): void { console.log('[PageLevel] IconSetting 点击,返回主页'); - this.playClickSound(); + AudioManager.instance.playButtonClick(); // 分享模式下栈中没有 PageHome,需要清除分享状态并直接打开首页 if (this._isShareMode) { @@ -2018,6 +2023,17 @@ export class PageLevel extends BaseView { onShare: () => { // 分享后不关闭弹窗,用户可继续点击下一关 console.log('[PageLevel] 分享完成'); + }, + onHome: () => { + this._closePassModal(); + + if (this._isShareMode) { + ShareManager.instance.clearShareMode(); + ViewManager.instance.replace('PageHome'); + return; + } + + ViewManager.instance.back(); } }); // 动画消费完一次后清除起点,避免弹窗多次打开时复用 diff --git a/assets/prefabs/PagePKData.ts b/assets/prefabs/PagePKData.ts index cda0070..05351f3 100644 --- a/assets/prefabs/PagePKData.ts +++ b/assets/prefabs/PagePKData.ts @@ -4,6 +4,7 @@ import { ViewManager } from 'db://assets/scripts/core/ViewManager'; import { CreatedShareItem, ParticipatedShareItem, ShareParticipantRankSummary } from 'db://assets/scripts/types/ApiTypes'; import { ShareManager } from 'db://assets/scripts/utils/ShareManager'; import { ToastManager } from 'db://assets/scripts/utils/ToastManager'; +import { AudioManager } from 'db://assets/scripts/utils/AudioManager'; const { ccclass, property } = _decorator; @ccclass('PagePKData') @@ -57,6 +58,7 @@ export class PagePKData extends BaseView { } private _onBackClick(): void { + AudioManager.instance.playButtonClick(); ViewManager.instance.back(); } @@ -189,14 +191,20 @@ export class PagePKData extends BaseView { const viewButton = this._findChild(item, 'ViewButton'); if (viewButton) { - const handler = () => this._openShareDetail(share); + const handler = () => { + AudioManager.instance.playButtonClick(); + this._openShareDetail(share); + }; viewButton.on(Button.EventType.CLICK, handler, this); this._createdButtonBindings.push({ node: viewButton, handler }); } const shareButton = this._findChild(item, 'ShareButton'); if (shareButton) { - const handler = () => ShareManager.instance.triggerWxShare(share.title, share.shareCode); + const handler = () => { + AudioManager.instance.playButtonClick(); + ShareManager.instance.triggerWxShare(share.title, share.shareCode); + }; shareButton.on(Button.EventType.CLICK, handler, this); this._createdButtonBindings.push({ node: shareButton, handler }); } diff --git a/assets/prefabs/PagePKEnd.ts b/assets/prefabs/PagePKEnd.ts index 09a8c55..77ea493 100644 --- a/assets/prefabs/PagePKEnd.ts +++ b/assets/prefabs/PagePKEnd.ts @@ -3,6 +3,7 @@ import { BaseView } from 'db://assets/scripts/core/BaseView'; import { ViewManager } from 'db://assets/scripts/core/ViewManager'; import { ShareManager } from 'db://assets/scripts/utils/ShareManager'; import { SubmitShareData, SubmittedShareLevelData } from 'db://assets/scripts/types/ApiTypes'; +import { AudioManager } from 'db://assets/scripts/utils/AudioManager'; const { ccclass, property } = _decorator; @ccclass('PagePKEnd') @@ -105,6 +106,7 @@ export class PagePKEnd extends BaseView { } private _onHomeClick(): void { + AudioManager.instance.playButtonClick(); ShareManager.instance.clearShareMode(); ViewManager.instance.replace('PageHome'); } @@ -226,6 +228,7 @@ export class PagePKEnd extends BaseView { } const handler = () => { + AudioManager.instance.playButtonClick(); if (answerButton?.isValid) { answerButton.active = false; } diff --git a/assets/prefabs/PagePreviewLevels.ts b/assets/prefabs/PagePreviewLevels.ts index a2c44ca..feed169 100644 --- a/assets/prefabs/PagePreviewLevels.ts +++ b/assets/prefabs/PagePreviewLevels.ts @@ -2,6 +2,7 @@ import { _decorator, Node, Button, Label, ScrollView, instantiate, UITransform } import { BaseView } from 'db://assets/scripts/core/BaseView'; import { ViewManager } from 'db://assets/scripts/core/ViewManager'; import { CompletedLevelsManager } from 'db://assets/scripts/utils/CompletedLevelsManager'; +import { AudioManager } from 'db://assets/scripts/utils/AudioManager'; import { PreviewLevelItem } from './PreviewLevelItem'; const { ccclass, property } = _decorator; @@ -215,6 +216,7 @@ export class PagePreviewLevels extends BaseView { // ─── 事件处理 ─────────────────────────────────────── private _onBackClick(): void { + AudioManager.instance.playButtonClick(); console.log('[PagePreviewLevels] 返回'); ViewManager.instance.back(); } diff --git a/assets/prefabs/PageWriteLevels.prefab b/assets/prefabs/PageWriteLevels.prefab index 9815390..7113649 100644 --- a/assets/prefabs/PageWriteLevels.prefab +++ b/assets/prefabs/PageWriteLevels.prefab @@ -5050,10 +5050,6 @@ "__expectedType__": "cc.EffectAsset" }, "coverCornerRadius": 0.1, - "itemToggleAudio": { - "__uuid__": "798824f1-0e20-48b7-ad8a-fb24d55bf986", - "__expectedType__": "cc.AudioClip" - }, "_id": "" }, { @@ -5136,4 +5132,4 @@ "instance": null, "targetOverrides": null } -] \ No newline at end of file +] diff --git a/assets/prefabs/PageWriteLevels.ts b/assets/prefabs/PageWriteLevels.ts index 485651f..f1f25c9 100644 --- a/assets/prefabs/PageWriteLevels.ts +++ b/assets/prefabs/PageWriteLevels.ts @@ -1,4 +1,4 @@ -import { _decorator, Node, Button, Sprite, Label, Toggle, ScrollView, EditBox, instantiate, UITransform, Vec2, EventTouch, EffectAsset, AudioClip, AudioSource } from 'cc'; +import { _decorator, Node, Button, Sprite, Label, Toggle, ScrollView, EditBox, instantiate, UITransform, Vec2, EventTouch, EffectAsset } from 'cc'; import { BaseView } from 'db://assets/scripts/core/BaseView'; import { ViewManager } from 'db://assets/scripts/core/ViewManager'; import { CompletedLevelsManager } from 'db://assets/scripts/utils/CompletedLevelsManager'; @@ -11,6 +11,7 @@ import { API_ENDPOINTS, API_TIMEOUT } from 'db://assets/scripts/config/ApiConfig import { HttpUtil } from 'db://assets/scripts/utils/HttpUtil'; import { ApiEnvelope, CompletedLevel } from 'db://assets/scripts/types/ApiTypes'; import { applyRoundedCorner } from 'db://assets/scripts/utils/roundedMaterial.utils'; +import { AudioManager } from 'db://assets/scripts/utils/AudioManager'; const { ccclass, property } = _decorator; /** @@ -69,9 +70,6 @@ export class PageWriteLevels extends BaseView { @property({ tooltip: '关卡封面圆角半径比例(相对于短边,0-0.5)' }) coverCornerRadius: number = 0.1; - @property({ type: AudioClip, tooltip: '关卡项选中/取消选中音效' }) - itemToggleAudio: AudioClip | null = null; - private _selectedIndices: Set = new Set(); private _levels: CompletedLevel[] = []; private _levelCount: number = 0; @@ -429,7 +427,7 @@ export class PageWriteLevels extends BaseView { this._selectedIndices.delete(index); } - this._playSound(this.itemToggleAudio); + AudioManager.instance.playButtonClick(); console.log('[PageWriteLevels] item切换选中:', index, selected, '当前已选:', this._selectedIndices.size); @@ -492,11 +490,13 @@ export class PageWriteLevels extends BaseView { } private _onBackClick(): void { + AudioManager.instance.playButtonClick(); console.log('[PageWriteLevels] 返回按钮点击'); ViewManager.instance.back(); } private _onDataClick(): void { + AudioManager.instance.playButtonClick(); ViewManager.instance.open('PagePKData'); } @@ -513,16 +513,8 @@ export class PageWriteLevels extends BaseView { return true; } - private _playSound(clip: AudioClip | null): void { - if (!clip) { - return; - } - - const audioSource = this.node.getComponent(AudioSource) ?? this.node.addComponent(AudioSource); - audioSource?.playOneShot(clip); - } - private _onPreviewClick(): void { + AudioManager.instance.playButtonClick(); if (!this._validateSelection()) return; const shareTitle = this.shareTitleEditBox?.getComponent(EditBox)?.string?.trim() || ''; ViewManager.instance.open('PagePreviewLevels', { @@ -534,6 +526,7 @@ export class PageWriteLevels extends BaseView { } private async _onCompleteClick(): Promise { + AudioManager.instance.playButtonClick(); if (!this._validateSelection()) return; const shareTitle = this.shareTitleEditBox?.getComponent(EditBox)?.string?.trim() || ''; diff --git a/assets/prefabs/PassModal.prefab b/assets/prefabs/PassModal.prefab index 9546a76..c144097 100644 --- a/assets/prefabs/PassModal.prefab +++ b/assets/prefabs/PassModal.prefab @@ -4019,6 +4019,9 @@ "nextLevelButton": { "__id__": 107 }, + "settingButton": { + "__id__": 154 + }, "shareButton": { "__id__": 127 }, diff --git a/assets/prefabs/PassModal.ts b/assets/prefabs/PassModal.ts index e6e3e06..c2e77f7 100644 --- a/assets/prefabs/PassModal.ts +++ b/assets/prefabs/PassModal.ts @@ -1,6 +1,7 @@ import { _decorator, Node, Label, AudioClip, AudioSource, view, UITransform, Size, ProgressBar, tween, Tween } from 'cc'; import { BaseModal } from 'db://assets/scripts/core/BaseModal'; import { WxSDK } from 'db://assets/scripts/utils/WxSDK'; +import { AudioManager } from 'db://assets/scripts/utils/AudioManager'; const { ccclass, property } = _decorator; /** @@ -11,6 +12,8 @@ export interface PassModalCallbacks { onNextLevel?: () => void; /** 点击分享回调 */ onShare?: () => void; + /** 点击返回主页回调 */ + onHome?: () => void; } export interface PassModalTitleInfo { @@ -45,6 +48,10 @@ export class PassModal extends BaseModal { @property(Node) nextLevelButton: Node | null = null; + /** 返回主页按钮 */ + @property(Node) + settingButton: Node | null = null; + /** 分享按钮 */ @property(Node) shareButton: Node | null = null; @@ -142,6 +149,7 @@ export class PassModal extends BaseModal { */ onViewLoad(): void { console.log('[PassModal] onViewLoad'); + this._resolveNodes(); this._resolveProgressAnchor(); this._cacheProgressAnchorStartX(); this._bindButtonEvents(); @@ -198,6 +206,9 @@ export class PassModal extends BaseModal { if (this.nextLevelButton) { this.nextLevelButton.on(Node.EventType.TOUCH_END, this._onNextLevelClick, this); } + if (this.settingButton) { + this.settingButton.on(Node.EventType.TOUCH_END, this._onHomeClick, this); + } if (this.shareButton) { this.shareButton.on(Node.EventType.TOUCH_END, this._onShareClick, this); } @@ -211,11 +222,20 @@ export class PassModal extends BaseModal { if (this.nextLevelButton && this.nextLevelButton.isValid) { this.nextLevelButton.off(Node.EventType.TOUCH_END, this._onNextLevelClick, this); } + if (this.settingButton && this.settingButton.isValid) { + this.settingButton.off(Node.EventType.TOUCH_END, this._onHomeClick, this); + } if (this.shareButton && this.shareButton.isValid) { this.shareButton.off(Node.EventType.TOUCH_END, this._onShareClick, this); } } + private _resolveNodes(): void { + this.nextLevelButton = this.nextLevelButton ?? this.node.getChildByName('Button') ?? null; + this.settingButton = this.settingButton ?? this.node.getChildByName('SettingButton') ?? null; + this.shareButton = this.shareButton ?? this.node.getChildByName('Share') ?? null; + } + /** * 播放通关音效 */ @@ -481,4 +501,13 @@ export class PassModal extends BaseModal { this._callbacks.onShare?.(); } + + /** + * 返回主页按钮点击 + */ + private _onHomeClick(): void { + console.log('[PassModal] 点击返回主页'); + AudioManager.instance.playButtonClick(); + this._callbacks.onHome?.(); + } } diff --git a/assets/scripts/utils/AudioManager.ts b/assets/scripts/utils/AudioManager.ts new file mode 100644 index 0000000..2ff8e7b --- /dev/null +++ b/assets/scripts/utils/AudioManager.ts @@ -0,0 +1,55 @@ +import { AudioClip, AudioSource, Node } from 'cc'; + +/** + * 音效管理器 + * 统一管理全局按钮点击等通用音效,避免页面各自维护 AudioSource。 + */ +export class AudioManager { + private static _instance: AudioManager | null = null; + + private _clickAudio: AudioClip | null = null; + private _hostNode: Node | null = null; + private _audioSource: AudioSource | null = null; + + static get instance(): AudioManager { + if (!this._instance) { + this._instance = new AudioManager(); + } + return this._instance; + } + + private constructor() {} + + init(clickAudio: AudioClip | null, hostNode: Node | null): void { + this._clickAudio = clickAudio; + this._hostNode = hostNode; + this._audioSource = null; + } + + playButtonClick(): void { + this._playOneShot(this._clickAudio); + } + + private _playOneShot(clip: AudioClip | null): void { + if (!clip) { + return; + } + + const audioSource = this._ensureAudioSource(); + audioSource?.playOneShot(clip); + } + + private _ensureAudioSource(): AudioSource | null { + if (this._audioSource?.isValid) { + return this._audioSource; + } + + if (!this._hostNode?.isValid) { + console.warn('[AudioManager] 未初始化宿主节点,无法播放音效'); + return null; + } + + this._audioSource = this._hostNode.getComponent(AudioSource) ?? this._hostNode.addComponent(AudioSource); + return this._audioSource; + } +} diff --git a/assets/scripts/utils/AudioManager.ts.meta b/assets/scripts/utils/AudioManager.ts.meta new file mode 100644 index 0000000..f56da05 --- /dev/null +++ b/assets/scripts/utils/AudioManager.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "2dc839e1-6bb2-4d35-a2c4-bf90f85f40e8", + "files": [], + "subMetas": {}, + "userData": {} +}