diff --git a/src/audioManager.js b/src/audioManager.js index b002632..c0d5beb 100644 --- a/src/audioManager.js +++ b/src/audioManager.js @@ -109,6 +109,9 @@ class AudioManager { this.sequenceIndex = 0; this.isSequenceRunning = false; + // 静音开关 + this.isMuted = false; + // 网络状态相关 this.networkOnline = true; this.pendingPlayKey = null; @@ -176,6 +179,15 @@ class AudioManager { audio.autoplay = false; audio.src = src; + // 初始化音量(支持的平台用 volume,H5 也可用 muted 作为兜底) + try { + if (typeof audio.volume === "number") { + audio.volume = this.isMuted ? 0 : 1; + } else if (typeof audio.muted !== "undefined") { + audio.muted = this.isMuted; + } + } catch (_) {} + // 初始化为不允许播放,只有显式 play() 才允许 this.allowPlayMap.set(key, false); @@ -340,6 +352,14 @@ class AudioManager { const audio = this.audioMap.get(key); if (audio) { + // 播放前确保遵循当前静音状态 + try { + if (typeof audio.volume === "number") { + audio.volume = this.isMuted ? 0 : 1; + } else if (typeof audio.muted !== "undefined") { + audio.muted = this.isMuted; + } + } catch (_) {} // 同一音频:避免 stop() 触发 onStop 清除授权,使用 pause()+seek(0) try { audio.pause(); @@ -458,6 +478,21 @@ class AudioManager { this.retryLoadAudio(key); } } + + // 设置静音开关:true 静音,false 取消静音 + setMuted(muted) { + this.isMuted = !!muted; + for (const audio of this.audioMap.values()) { + try { + if (typeof audio.volume === "number") { + audio.volume = this.isMuted ? 0 : 1; + } else if (typeof audio.muted !== "undefined") { + audio.muted = this.isMuted; + } + } catch (_) {} + } + debugLog(`静音状态已设置为: ${this.isMuted}`); + } } // 导出单例 diff --git a/src/components/BowTarget.vue b/src/components/BowTarget.vue index 80d7884..330f8ca 100644 --- a/src/components/BowTarget.vue +++ b/src/components/BowTarget.vue @@ -1,6 +1,7 @@ @@ -122,6 +157,9 @@ onBeforeUnmount(() => { }} + + + 中场休息 { - + @@ -339,4 +377,24 @@ onBeforeUnmount(() => { z-index: 99; font-weight: bold; } +@keyframes spring-in { + 0% { + transform: translateY(-20px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +.arrow-dir { + position: absolute; + width: 36%; + left: 50%; + bottom: 50%; +} +.arrow-dir > image { + animation: spring-in 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; + width: 100%; +} diff --git a/src/components/HeaderProgress.vue b/src/components/HeaderProgress.vue index 9ab2254..11bc382 100644 --- a/src/components/HeaderProgress.vue +++ b/src/components/HeaderProgress.vue @@ -2,6 +2,8 @@ import { ref, watch, onMounted, onBeforeUnmount } from "vue"; import audioManager from "@/audioManager"; import { MESSAGETYPES } from "@/constants"; +import { getDirectionText } from "@/util"; + import useStore from "@/store"; import { storeToRefs } from "pinia"; const store = useStore(); @@ -11,7 +13,6 @@ const tips = ref(""); const melee = ref(false); const timer = ref(null); const sound = ref(true); -const currentSound = ref(""); const currentRound = ref(0); const currentRoundEnded = ref(false); const ended = ref(false); @@ -22,7 +23,6 @@ const totalShot = ref(0); watch( () => tips.value, (newVal) => { - if (!sound.value) return; let key = []; if (newVal.includes("重回")) return; if (currentRoundEnded.value) { @@ -44,11 +44,11 @@ watch( const updateSound = () => { sound.value = !sound.value; - if (!sound.value) audioManager.stop(currentSound.value); + audioManager.setMuted(!sound.value); }; async function onReceiveMessage(messages = []) { - if (!sound.value || ended.value) return; + if (ended.value) return; messages.forEach((msg) => { if (msg.constructor === MESSAGETYPES.ShootResult) { if (melee.value && msg.userId !== user.value.id) return; @@ -72,10 +72,11 @@ async function onReceiveMessage(messages = []) { } catch (_) {} } if (!halfTime.value && msg.target) { - currentSound.value = msg.target.ring - ? `${msg.target.ring}环` - : "未上靶"; - audioManager.play(currentSound.value); + let key = []; + key.push(msg.target.ring ? `${msg.target.ring}环` : "未上靶"); + if (!msg.target.ring) + key.push(`向${getDirectionText(msg.target.angle)}调整`); + audioManager.play(key); } } else if (msg.constructor === MESSAGETYPES.InvalidShot) { if (msg.userId === user.value.id) { @@ -123,7 +124,6 @@ async function onReceiveMessage(messages = []) { } const playSound = (key) => { - currentSound.value = key; audioManager.play(key); }; diff --git a/src/components/ShootProgress.vue b/src/components/ShootProgress.vue index 3044204..635379a 100644 --- a/src/components/ShootProgress.vue +++ b/src/components/ShootProgress.vue @@ -2,10 +2,13 @@ import { ref, watch, onMounted, onBeforeUnmount } from "vue"; import audioManager from "@/audioManager"; import { MESSAGETYPES } from "@/constants"; +import { getDirectionText } from "@/util"; + import useStore from "@/store"; import { storeToRefs } from "pinia"; const store = useStore(); const { user } = storeToRefs(store); + const props = defineProps({ show: { type: Boolean, @@ -41,7 +44,6 @@ const barColor = ref("#fed847"); const remain = ref(props.total); const timer = ref(null); const sound = ref(true); -const currentSound = ref(""); const currentRound = ref(props.currentRound); const currentRoundEnded = ref(false); const ended = ref(false); @@ -53,7 +55,7 @@ watch( let key = ""; if (newVal.includes("红队")) key = "请红方射箭"; if (newVal.includes("蓝队")) key = "请蓝方射箭"; - if (key && sound.value) { + if (key) { if (currentRoundEnded.value) { currentRound.value += 1; currentRoundEnded.value = false; @@ -118,11 +120,11 @@ const updateRemain = (value) => { const updateSound = () => { sound.value = !sound.value; - if (!sound.value) audioManager.stop(currentSound.value); + audioManager.setMuted(!sound.value); }; async function onReceiveMessage(messages = []) { - if (!sound.value || ended.value) return; + if (ended.value) return; messages.forEach((msg) => { if ( (props.battleId && msg.constructor === MESSAGETYPES.ShootResult) || @@ -130,10 +132,11 @@ async function onReceiveMessage(messages = []) { ) { if (props.melee && msg.userId !== user.value.id) return; if (!halfTime.value && msg.target) { - currentSound.value = msg.target.ring - ? `${msg.target.ring}环` - : "未上靶"; - audioManager.play(currentSound.value); + let key = []; + key.push(msg.target.ring ? `${msg.target.ring}环` : "未上靶"); + if (!msg.target.ring) + key.push(`向${getDirectionText(msg.target.angle)}调整`); + audioManager.play(key); } } else if (msg.constructor === MESSAGETYPES.InvalidShot) { if (msg.userId === user.value.id) { @@ -164,7 +167,6 @@ async function onReceiveMessage(messages = []) { } const playSound = (key) => { - currentSound.value = key; audioManager.play(key); }; diff --git a/src/static/arrow-direction.png b/src/static/arrow-direction.png new file mode 100644 index 0000000..670272e Binary files /dev/null and b/src/static/arrow-direction.png differ diff --git a/src/util.js b/src/util.js index f9dbca2..f1743b0 100644 --- a/src/util.js +++ b/src/util.js @@ -676,3 +676,24 @@ export const generateShareCard = (canvasId, date, actual, total) => { }; export const generateShareImage = (canvasId) => {}; + +export const getDirectionText = (angle = 0) => { + if (angle < 0) return ""; + if (angle >= 337.5 || angle < 22.5) { + return "下"; + } else if (angle >= 22.5 && angle < 67.5) { + return "左下"; + } else if (angle >= 67.5 && angle < 112.5) { + return "左"; + } else if (angle >= 112.5 && angle < 157.5) { + return "左上"; + } else if (angle >= 157.5 && angle < 202.5) { + return "上"; + } else if (angle >= 202.5 && angle < 247.5) { + return "右上"; + } else if (angle >= 247.5 && angle < 292.5) { + return "右"; + } else if (angle >= 292.5 && angle < 337.5) { + return "右下"; + } +};