Complete Cocos Creator port with level bundles, themes, and tooling.
Adds level prefabs, theme assets, audio, extensions, and deployment scripts for the Unity WebGL migration. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
123
assets/scripts/audio/GameAudio.ts
Normal file
123
assets/scripts/audio/GameAudio.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { AudioClip, AudioSource, director, Node, resources } from 'cc';
|
||||
|
||||
/** Unity Assets/Art/Audio → resources/audio/ */
|
||||
const CLIPS = {
|
||||
background: 'audio/Backgroud',
|
||||
move: 'audio/Move',
|
||||
jump: 'audio/Jump',
|
||||
vehicleMove: 'audio/FlyingCarpetMove',
|
||||
fail: 'audio/Fail',
|
||||
success: 'audio/Success',
|
||||
coins: 'audio/GetCoins',
|
||||
} as const;
|
||||
|
||||
type SfxKey = Exclude<keyof typeof CLIPS, 'background'>;
|
||||
|
||||
/**
|
||||
* 对齐 Unity:关卡加载时 curLevel 上循环播放 Backgroud.mp3;
|
||||
* 音效路径与 Player.prefab 一致。
|
||||
*/
|
||||
export class GameAudio {
|
||||
private static readonly cache = new Map<string, AudioClip>();
|
||||
private static readonly loading = new Map<string, Promise<AudioClip | null>>();
|
||||
private static sfxHost: Node | null = null;
|
||||
|
||||
static async preload(): Promise<void> {
|
||||
await Promise.all(Object.values(CLIPS).map((p) => GameAudio.loadClip(p)));
|
||||
}
|
||||
|
||||
static loadClip(path: string): Promise<AudioClip | null> {
|
||||
const hit = GameAudio.cache.get(path);
|
||||
if (hit) return Promise.resolve(hit);
|
||||
|
||||
const pending = GameAudio.loading.get(path);
|
||||
if (pending) return pending;
|
||||
|
||||
const task = new Promise<AudioClip | null>((resolve) => {
|
||||
resources.load(path, AudioClip, (err, clip) => {
|
||||
GameAudio.loading.delete(path);
|
||||
if (err || !clip) {
|
||||
console.warn(`[GameAudio] 加载失败: ${path}`, err);
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
GameAudio.cache.set(path, clip);
|
||||
resolve(clip);
|
||||
});
|
||||
});
|
||||
GameAudio.loading.set(path, task);
|
||||
return task;
|
||||
}
|
||||
|
||||
/** 在关卡根节点播放循环背景音乐(对齐 Unity createNewLevel) */
|
||||
static async playBackground(levelRoot: Node): Promise<void> {
|
||||
if (!levelRoot?.isValid) return;
|
||||
const clip = await GameAudio.loadClip(CLIPS.background);
|
||||
if (!clip || !levelRoot.isValid) return;
|
||||
|
||||
let host = levelRoot.getChildByName('_BGM');
|
||||
if (!host) {
|
||||
host = new Node('_BGM');
|
||||
host.parent = levelRoot;
|
||||
}
|
||||
const src = host.getComponent(AudioSource) ?? host.addComponent(AudioSource);
|
||||
src.clip = clip;
|
||||
src.loop = true;
|
||||
src.playOnAwake = false;
|
||||
src.volume = 1;
|
||||
if (!src.playing) {
|
||||
src.play();
|
||||
}
|
||||
}
|
||||
|
||||
static playSfx(key: SfxKey, host?: Node) {
|
||||
void GameAudio.playSfxAsync(key, host);
|
||||
}
|
||||
|
||||
/** 对齐 Unity:同一 AudioSource 播放中则不重复触发移动/跳跃音效 */
|
||||
static async playSfxOnSource(src: AudioSource, key: SfxKey): Promise<boolean> {
|
||||
if (!src?.node?.isValid || src.playing) return false;
|
||||
const clip = await GameAudio.loadClip(CLIPS[key]);
|
||||
if (!clip) return false;
|
||||
src.clip = clip;
|
||||
src.loop = false;
|
||||
src.volume = 1;
|
||||
src.play();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async playSfxAsync(key: SfxKey, host?: Node) {
|
||||
const clip = await GameAudio.loadClip(CLIPS[key]);
|
||||
if (!clip) return;
|
||||
|
||||
const root = host?.isValid ? host : GameAudio.ensureSfxHost();
|
||||
if (!root?.isValid) return;
|
||||
|
||||
const src = root.getComponent(AudioSource) ?? root.addComponent(AudioSource);
|
||||
src.playOneShot(clip, 1);
|
||||
}
|
||||
|
||||
private static ensureSfxHost(): Node | null {
|
||||
if (GameAudio.sfxHost?.isValid) return GameAudio.sfxHost;
|
||||
|
||||
const scene = director.getScene();
|
||||
if (!scene) return null;
|
||||
|
||||
let host = scene.getChildByName('_GameSFX');
|
||||
if (!host) {
|
||||
host = new Node('_GameSFX');
|
||||
host.parent = scene;
|
||||
}
|
||||
GameAudio.sfxHost = host;
|
||||
return host;
|
||||
}
|
||||
|
||||
/** 浏览器需用户交互后才能播放音频,首次点击 HUD 时恢复 */
|
||||
static resumeAll() {
|
||||
const scene = director.getScene();
|
||||
if (!scene) return;
|
||||
for (const src of scene.getComponentsInChildren(AudioSource)) {
|
||||
if (src.clip && !src.playing) src.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user