Commit ae0b0947 authored by 董政锦's avatar 董政锦

h5-game 3和4游戏中声音问题;

parent 96495239
...@@ -38,6 +38,25 @@ function getEffectTemplate(music: string) { ...@@ -38,6 +38,25 @@ function getEffectTemplate(music: string) {
template.volume = 1 template.volume = 1
template.load() template.load()
effectTemplates.set(music, template) effectTemplates.set(music, template)
// 注册用户交互监听器,解决浏览器自动播放策略
const resumeAudio = async () => {
// 尝试播放一个静音的音频来激活 AudioContext
try {
const silentAudio = new Audio()
silentAudio.src = 'data:audio/mp3;base64,SUQzBAAAAAAAIgRTU0UAAAAPAAADTGF2ZjU3LjkyLjEzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//uQZAAAA'
await silentAudio.play()
silentAudio.pause()
console.log('[Music] 音频已通过用户交互激活')
} catch (e) {
console.log('[Music] 音频激活失败:', e)
}
window.removeEventListener('touchstart', resumeAudio)
window.removeEventListener('click', resumeAudio)
}
window.addEventListener('touchstart', resumeAudio, { once: true })
window.addEventListener('click', resumeAudio, { once: true })
} }
return template return template
......
...@@ -8,7 +8,7 @@ import { ...@@ -8,7 +8,7 @@ import {
computed, computed,
} from "vue"; } from "vue";
import { assetUrl } from "@/commons/assets.ts"; import { assetUrl } from "@/commons/assets.ts";
// import { playGame6Music } from '@/commons/music' import { playGame3Music } from '@/commons/music'
const props = defineProps<{ const props = defineProps<{
imageUrls: Record<string, string>; imageUrls: Record<string, string>;
...@@ -300,6 +300,13 @@ const playCollectEffect = (item: RoadItem, x: number, y: number) => { ...@@ -300,6 +300,13 @@ const playCollectEffect = (item: RoadItem, x: number, y: number) => {
const isCoin = item.kind === "coin"; const isCoin = item.kind === "coin";
const delta = isCoin ? 5 : -3; const delta = isCoin ? 5 : -3;
// 播放音效:金币用 game3_1.mp3,炸弹用 game3_2.mp3
if (isCoin) {
playGame3Music(1); // 金币音效
} else {
playGame3Music(2); // 炸弹音效
}
if (isCoin) { if (isCoin) {
emit("scoreChange", 5); emit("scoreChange", 5);
} else { } else {
......
...@@ -12,11 +12,15 @@ import { ...@@ -12,11 +12,15 @@ import {
import LoadingView from "./views/LoadingView.vue"; import LoadingView from "./views/LoadingView.vue";
import PlayingView from "./views/PlayingView.vue"; import PlayingView from "./views/PlayingView.vue";
import ScoreView from "./views/ScoreView.vue"; import ScoreView from "./views/ScoreView.vue";
import { $getWechat, $toast } from "@/commons/utils.ts"; import { $getWechat, $toast, $is_run_local } from "@/commons/utils.ts";
const gameId = "game4"; const gameId = "game4";
type GameView = "loading" | "playing" | "score"; type GameView = "loading" | "playing" | "score";
// 本地模式检测
const isLocalMode = $is_run_local();
console.log('[Game4 H5] 本地模式:', isLocalMode);
const imageUrls = { const imageUrls = {
bg: cssAssetUrl("game4/h5-game4-bg.webp"), bg: cssAssetUrl("game4/h5-game4-bg.webp"),
bg2: cssAssetUrl("game1/bg2.png"), bg2: cssAssetUrl("game1/bg2.png"),
...@@ -194,13 +198,26 @@ const showGameRuleHandler = () => { ...@@ -194,13 +198,26 @@ const showGameRuleHandler = () => {
showGameRule.value = !showGameRule.value; showGameRule.value = !showGameRule.value;
}; };
if (wechat) { if (wechat || isLocalMode) {
if (isLocalMode) {
// 本地模式:模拟用户数据
console.log('[Game4 H5] 本地模式启动,模拟用户数据');
token.value = 'local-token';
nickname.value = '本地测试玩家';
avatar.value = 'https://api.dicebear.com/9.x/fun-emoji/svg?seed=local';
userJoinStatus.value = true;
}
const { offSocketMessage } = useGameSocket({ const { offSocketMessage } = useGameSocket({
gameId, gameId,
auth: { auth: {
userid: wechat.token, userid: wechat?.token || 'local-token',
}, },
onConnect: () => { onConnect: () => {
if (isLocalMode) {
console.log('[Game4 H5] 本地模式:跳过 WebSocket 连接');
return;
}
window.setTimeout(() => { window.setTimeout(() => {
const joined = joinSharedRoom( const joined = joinSharedRoom(
nickname.value, nickname.value,
...@@ -253,6 +270,14 @@ onMounted(() => { ...@@ -253,6 +270,14 @@ onMounted(() => {
if (token.value) { if (token.value) {
window.addEventListener("beforeunload", confirmRefresh); window.addEventListener("beforeunload", confirmRefresh);
} }
// 本地模式:自动开始游戏
if (isLocalMode) {
console.log('[Game4 H5] 本地模式:2秒后自动开始游戏');
setTimeout(() => {
startGameView();
}, 2000);
}
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
...@@ -265,7 +290,7 @@ onBeforeUnmount(() => { ...@@ -265,7 +290,7 @@ onBeforeUnmount(() => {
</script> </script>
<template> <template>
<MobileStage v-if="token" :showGameRule="showGameRule" ref="mobileStageRef" :background="stageBackground"> <MobileStage v-if="token || isLocalMode" :showGameRule="showGameRule" ref="mobileStageRef" :background="stageBackground">
<template #gameRule> <template #gameRule>
<div class="rule-container"> <div class="rule-container">
<div class="rule-txt-container"> <div class="rule-txt-container">
......
...@@ -74,19 +74,56 @@ const requestMotionPermission = async (): Promise<boolean> => { ...@@ -74,19 +74,56 @@ const requestMotionPermission = async (): Promise<boolean> => {
const initAudio = async () => { const initAudio = async () => {
try { try {
audioContext = new (window.AudioContext || (window as any).webkitAudioContext)() audioContext = new (window.AudioContext || (window as any).webkitAudioContext)()
// 如果 AudioContext 处于 suspended 状态,注册一次性交互监听器来激活
if (audioContext.state === 'suspended') {
console.log('[摇一摇] AudioContext 处于 suspended 状态,注册交互监听器')
const resumeAudio = async () => {
if (audioContext && audioContext.state === 'suspended') {
await audioContext.resume()
console.log('[摇一摇] AudioContext 已通过用户交互激活')
}
// 移除监听器
window.removeEventListener('touchstart', resumeAudio)
window.removeEventListener('click', resumeAudio)
}
window.addEventListener('touchstart', resumeAudio, { once: true })
window.addEventListener('click', resumeAudio, { once: true })
}
const response = await fetch(shakeSound) const response = await fetch(shakeSound)
const arrayBuffer = await response.arrayBuffer() const arrayBuffer = await response.arrayBuffer()
shakeAudioBuffer = await audioContext.decodeAudioData(arrayBuffer) shakeAudioBuffer = await audioContext.decodeAudioData(arrayBuffer)
console.log('[摇一摇] 音频初始化成功,AudioContext state:', audioContext.state)
} catch (error) { } catch (error) {
console.error('[摇一摇] 音频初始化失败:', error) console.error('[摇一摇] 音频初始化失败:', error)
} }
} }
const playShakeSound = () => { const playShakeSound = () => {
if (!audioContext || !shakeAudioBuffer) return if (!audioContext || !shakeAudioBuffer) {
console.warn('[摇一摇] 音频未初始化,无法播放')
return
}
// 确保 AudioContext 处于运行状态
if (audioContext.state === 'suspended') { if (audioContext.state === 'suspended') {
audioContext.resume() audioContext.resume().then(() => {
console.log('[摇一摇] AudioContext 已恢复')
playShakeSoundInternal()
}).catch(err => {
console.error('[摇一摇] AudioContext 恢复失败:', err)
})
} else {
playShakeSoundInternal()
} }
}
const playShakeSoundInternal = () => {
if (!audioContext || !shakeAudioBuffer) return
const source = audioContext.createBufferSource() const source = audioContext.createBufferSource()
source.buffer = shakeAudioBuffer source.buffer = shakeAudioBuffer
source.connect(audioContext.destination) source.connect(audioContext.destination)
...@@ -181,6 +218,12 @@ onBeforeUnmount(() => { ...@@ -181,6 +218,12 @@ onBeforeUnmount(() => {
// 点击马触发:iOS 先请求权限(不触发得分) // 点击马触发:iOS 先请求权限(不触发得分)
const onTapHorse = async () => { const onTapHorse = async () => {
// 激活 AudioContext(解决浏览器自动播放策略)
if (audioContext && audioContext.state === 'suspended') {
await audioContext.resume()
console.log('[摇一摇] 通过点击激活 AudioContext')
}
if (!motionPermissionGranted) { if (!motionPermissionGranted) {
await requestMotionPermission() await requestMotionPermission()
showIOSHint.value = false showIOSHint.value = false
......
...@@ -116,6 +116,23 @@ let audioContext: AudioContext | null = null ...@@ -116,6 +116,23 @@ let audioContext: AudioContext | null = null
function getAudioContext(): AudioContext { function getAudioContext(): AudioContext {
if (!audioContext) { if (!audioContext) {
audioContext = new AudioContext() audioContext = new AudioContext()
// 如果 AudioContext 处于 suspended 状态,注册一次性交互监听器来激活
if (audioContext.state === 'suspended') {
console.log('[Audio] AudioContext 处于 suspended 状态,注册交互监听器')
const resumeAudio = async () => {
if (audioContext && audioContext.state === 'suspended') {
await audioContext.resume()
console.log('[Audio] AudioContext 已通过用户交互激活')
}
window.removeEventListener('touchstart', resumeAudio)
window.removeEventListener('click', resumeAudio)
}
window.addEventListener('touchstart', resumeAudio, { once: true })
window.addEventListener('click', resumeAudio, { once: true })
}
} }
if (audioContext.state === 'suspended') { if (audioContext.state === 'suspended') {
audioContext.resume() audioContext.resume()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment