feat: 支持音频管线

This commit is contained in:
richarjiang
2026-05-19 21:40:45 +08:00
parent 165fef318f
commit 43afe6085d
13 changed files with 151 additions and 24 deletions

View File

@@ -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"
},
{

View File

@@ -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);
}
}

View File

@@ -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');
}

View File

@@ -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;
// 不限制单格 maxLengthiOS 拼音 / 日韩 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();
}
});
// 动画消费完一次后清除起点,避免弹窗多次打开时复用

View File

@@ -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 });
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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
}
]
]

View File

@@ -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<number> = 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<void> {
AudioManager.instance.playButtonClick();
if (!this._validateSelection()) return;
const shareTitle = this.shareTitleEditBox?.getComponent(EditBox)?.string?.trim() || '';

View File

@@ -4019,6 +4019,9 @@
"nextLevelButton": {
"__id__": 107
},
"settingButton": {
"__id__": 154
},
"shareButton": {
"__id__": 127
},

View File

@@ -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?.();
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "2dc839e1-6bb2-4d35-a2c4-bf90f85f40e8",
"files": [],
"subMetas": {},
"userData": {}
}