diff --git a/src/assets/game/share_mask.png b/src/assets/game/share_mask.png new file mode 100644 index 0000000..8e52884 Binary files /dev/null and b/src/assets/game/share_mask.png differ diff --git a/src/assets/sounds/index.ts b/src/assets/sounds/index.ts new file mode 100644 index 0000000..a82c3ea --- /dev/null +++ b/src/assets/sounds/index.ts @@ -0,0 +1,19 @@ +import { EasyAudio } from "./utils"; + +import 按钮音效 from "./按钮音效.mp3" +import 弹簧蓄力结束弹射 from "./弹簧蓄力结束弹射有效.mp3" +import 弹簧蓄力下拉音效 from "./弹簧蓄力下拉音效.mp3" +import 风声 from "./风声.mp3" +import 标题出现 from "./首页标题、尾标出现音效.mp3" +import 结算 from "./最终结算音效.mp3" + +const ea = new EasyAudio([ + {name: "按钮音效", audioUrl: 按钮音效, volume: 0.5}, + {name: "弹簧蓄力结束弹射", audioUrl: 弹簧蓄力结束弹射, volume: 0.5}, + {name: "弹簧蓄力下拉音效", audioUrl: 弹簧蓄力下拉音效, volume: 0.5}, + {name: "风声", audioUrl: 风声, volume: 0.5}, + {name: "标题出现", audioUrl: 标题出现, volume: 0.5}, + {name: "结算", audioUrl: 结算, volume: 0.5}, +]); + +export default ea; \ No newline at end of file diff --git a/src/assets/sounds/utils.ts b/src/assets/sounds/utils.ts new file mode 100644 index 0000000..f3475bc --- /dev/null +++ b/src/assets/sounds/utils.ts @@ -0,0 +1,114 @@ +export class EasyAudio { + private static audioCtx: AudioContext | null = null; + private buffers: Map = new Map(); + private sources: Map = new Map(); + private audioOptions: Map< + string, + { audioUrl: string; loop?: boolean; volume?: number } + > = new Map(); + + private get audioCtx(): AudioContext { + if (!EasyAudio.audioCtx) { + EasyAudio.audioCtx = new AudioContext(); + } + return EasyAudio.audioCtx; + } + + constructor( + audios?: + | { name: string; audioUrl: string | URL; loop?: boolean; volume?: number } + | { name: string; audioUrl: string | URL; loop?: boolean; volume?: number }[] + ) { + if (audios) { + this.add(audios); + } + } + + private async createAudioBuffer(audioUrl: string): Promise { + const response = await fetch(audioUrl); + const arrayBuffer = await response.arrayBuffer(); + return await this.audioCtx.decodeAudioData(arrayBuffer); + } + + private async loadAudio(name: string) { + const options = this.audioOptions.get(name); + if (!options) { + throw new Error(`音频 ${name} 未找到`); + } + const buffer = await this.createAudioBuffer(options.audioUrl); + this.buffers.set(name, buffer); + } + + async load(): Promise { + const promises = Array.from(this.audioOptions.keys()).map(name => + this.loadAudio(name) + ); + await Promise.all(promises); + } + + add( + audios: + | { name: string; audioUrl: string | URL; loop?: boolean; volume?: number } + | { name: string; audioUrl: string | URL; loop?: boolean; volume?: number }[] + ): void { + if (Array.isArray(audios)) { + audios.forEach(audio => { + this.audioOptions.set(audio.name, { + audioUrl: typeof (audio.audioUrl) === "string" ? audio.audioUrl : audio.audioUrl.href, + loop: audio.loop, + volume: audio.volume, + }); + }); + } else { + this.audioOptions.set(audios.name, { + audioUrl: typeof (audios.audioUrl) === "string" ? audios.audioUrl : audios.audioUrl.href, + loop: audios.loop, + volume: audios.volume, + }); + } + } + + async play(name: string) { + if (!this.buffers.has(name)) { + await this.loadAudio(name); + } + + const buffer = this.buffers.get(name); + const options = this.audioOptions.get(name); + if (!buffer || !options) { + throw new Error(`音频 ${name} 未找到`); + } + + const source = this.audioCtx.createBufferSource(); + source.buffer = buffer; + source.loop = options.loop || false; + + let gainNode: GainNode | null = null; + if (options.volume !== undefined) { + gainNode = this.audioCtx.createGain(); + gainNode.gain.value = options.volume; + source.connect(gainNode); + gainNode.connect(this.audioCtx.destination); + } else { + source.connect(this.audioCtx.destination); + } + + source.start(); + this.sources.set(name, source); + console.log(`音频 ${name} 播放`); + + } + + stop(name: string) { + const source = this.sources.get(name); + if (source) { + source.stop(); + this.sources.delete(name); + } + } + + stopAll() { + this.sources.forEach(source => source.stop()); + this.sources.clear(); + } +} diff --git a/src/assets/sounds/弹簧蓄力下拉音效.mp3 b/src/assets/sounds/弹簧蓄力下拉音效.mp3 new file mode 100644 index 0000000..bfe0bad Binary files /dev/null and b/src/assets/sounds/弹簧蓄力下拉音效.mp3 differ diff --git a/src/assets/sounds/弹簧蓄力结束弹射有效.mp3 b/src/assets/sounds/弹簧蓄力结束弹射有效.mp3 new file mode 100644 index 0000000..8612123 Binary files /dev/null and b/src/assets/sounds/弹簧蓄力结束弹射有效.mp3 differ diff --git a/src/assets/sounds/按钮音效.mp3 b/src/assets/sounds/按钮音效.mp3 new file mode 100644 index 0000000..716ff1f Binary files /dev/null and b/src/assets/sounds/按钮音效.mp3 differ diff --git a/src/assets/sounds/最终结算音效.mp3 b/src/assets/sounds/最终结算音效.mp3 new file mode 100644 index 0000000..058bfa9 Binary files /dev/null and b/src/assets/sounds/最终结算音效.mp3 differ diff --git a/src/assets/sounds/风声.mp3 b/src/assets/sounds/风声.mp3 new file mode 100644 index 0000000..d4988f4 Binary files /dev/null and b/src/assets/sounds/风声.mp3 differ diff --git a/src/assets/sounds/首页标题、尾标出现音效.mp3 b/src/assets/sounds/首页标题、尾标出现音效.mp3 new file mode 100644 index 0000000..5766f01 Binary files /dev/null and b/src/assets/sounds/首页标题、尾标出现音效.mp3 differ diff --git a/src/pages/Game.vue b/src/pages/Game.vue index 1de4a96..42d42aa 100644 --- a/src/pages/Game.vue +++ b/src/pages/Game.vue @@ -11,6 +11,7 @@ defineExpose({ } }) +import AudioEffects from '../assets/sounds' const props = defineProps<{ userdata: { @@ -32,6 +33,7 @@ const hasP2AnimationPlayed = ref(false); async function shoot() { //sunEle.value?.jumpTo('天上飞') + AudioEffects.play("弹簧蓄力下拉音效") sunEle.value?.jumpTo('蓄力飞') await document.querySelector('.sun-ani-wrapper')?.animate([ @@ -54,6 +56,7 @@ async function shoot() { document.querySelector('.arrow')?.remove(); }); emit('cloudUp'); + AudioEffects.play("弹簧蓄力结束弹射") document.querySelector('.bed')?.animate([ { transform: 'translateY(0)' }, { transform: 'translateY(170%)' }, @@ -300,6 +303,7 @@ function gToggle(pos: 'left' | 'center' | 'right') { if (windEl) { windEl.style.rotate = `${windAni[0]}deg`; } + AudioEffects.play("风声") windEl?.animate([ { left: windAni[1][0] + 'vw', top: windAni[1][1] + 'vw', opacity: 0 }, @@ -558,6 +562,7 @@ async function gameEnd() { easing: 'cubic-bezier(0.4, 0, 1, 0.5)', fill: 'forwards', }); + AudioEffects.play("结算") sunEle.value?.jumpTo('落下'); @@ -716,6 +721,7 @@ async function finishCollect() { document.querySelector('.bar-container').style.display = 'none'; showLastPage.value = true; + AudioEffects.play("标题出现") // 在最后一页显示后设置 Intersection Observer 监听 p2 元素 await wait(100); // 等待 DOM 更新 @@ -726,7 +732,7 @@ async function finishCollect() { hasP2AnimationPlayed.value = true; posterP2Ele.value?.jumpTo('p2'); console.log('Poster P2 animation triggered'); - + // 停止观察,因为只需要触发一次 observer.unobserve(entry.target); } @@ -785,6 +791,7 @@ function posterSwap(e: PointerEvent) { } } } */ +const showShareMask = ref(false);