添加为上靶的方向指示
This commit is contained in:
@@ -109,6 +109,9 @@ class AudioManager {
|
|||||||
this.sequenceIndex = 0;
|
this.sequenceIndex = 0;
|
||||||
this.isSequenceRunning = false;
|
this.isSequenceRunning = false;
|
||||||
|
|
||||||
|
// 静音开关
|
||||||
|
this.isMuted = false;
|
||||||
|
|
||||||
// 网络状态相关
|
// 网络状态相关
|
||||||
this.networkOnline = true;
|
this.networkOnline = true;
|
||||||
this.pendingPlayKey = null;
|
this.pendingPlayKey = null;
|
||||||
@@ -176,6 +179,15 @@ class AudioManager {
|
|||||||
audio.autoplay = false;
|
audio.autoplay = false;
|
||||||
audio.src = src;
|
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() 才允许
|
// 初始化为不允许播放,只有显式 play() 才允许
|
||||||
this.allowPlayMap.set(key, false);
|
this.allowPlayMap.set(key, false);
|
||||||
|
|
||||||
@@ -340,6 +352,14 @@ class AudioManager {
|
|||||||
|
|
||||||
const audio = this.audioMap.get(key);
|
const audio = this.audioMap.get(key);
|
||||||
if (audio) {
|
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)
|
// 同一音频:避免 stop() 触发 onStop 清除授权,使用 pause()+seek(0)
|
||||||
try {
|
try {
|
||||||
audio.pause();
|
audio.pause();
|
||||||
@@ -458,6 +478,21 @@ class AudioManager {
|
|||||||
this.retryLoadAudio(key);
|
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}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出单例
|
// 导出单例
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, onMounted, onBeforeUnmount } from "vue";
|
import { ref, watch, onMounted, onBeforeUnmount, computed } from "vue";
|
||||||
import StartCountdown from "@/components/StartCountdown.vue";
|
import StartCountdown from "@/components/StartCountdown.vue";
|
||||||
|
import { MESSAGETYPES } from "@/constants";
|
||||||
import { simulShootAPI } from "@/apis";
|
import { simulShootAPI } from "@/apis";
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
@@ -42,12 +43,13 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const showsimul = ref(false);
|
|
||||||
const latestOne = ref(null);
|
const latestOne = ref(null);
|
||||||
const bluelatestOne = ref(null);
|
const bluelatestOne = ref(null);
|
||||||
const prevScores = ref([]);
|
const prevScores = ref([]);
|
||||||
const prevBlueScores = ref([]);
|
const prevBlueScores = ref([]);
|
||||||
const timer = ref(null);
|
const timer = ref(null);
|
||||||
|
const dirTimer = ref(null);
|
||||||
|
const angle = ref(null);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.scores,
|
() => props.scores,
|
||||||
@@ -98,10 +100,38 @@ const simulShoot2 = async () => {
|
|||||||
if (device.value.deviceId) await simulShootAPI(device.value.deviceId, 1, 1);
|
if (device.value.deviceId) await simulShootAPI(device.value.deviceId, 1, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
const env = computed(() => {
|
||||||
const accountInfo = uni.getAccountInfoSync();
|
const accountInfo = uni.getAccountInfoSync();
|
||||||
const envVersion = accountInfo.miniProgram.envVersion;
|
return accountInfo.miniProgram.envVersion;
|
||||||
if (envVersion !== "release") showsimul.value = true;
|
});
|
||||||
|
|
||||||
|
const arrowStyle = computed(() => {
|
||||||
|
return {
|
||||||
|
transform: `rotateX(180deg) translate(-50%, -50%) rotate(${
|
||||||
|
360 - angle.value
|
||||||
|
}deg) translateY(100%)`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
async function onReceiveMessage(messages = []) {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
if (
|
||||||
|
msg.constructor === MESSAGETYPES.ShootSyncMeArrowID ||
|
||||||
|
msg.constructor === MESSAGETYPES.ShootResult
|
||||||
|
) {
|
||||||
|
if (msg.userId === user.value.id) {
|
||||||
|
angle.value =
|
||||||
|
!msg.target.ring && msg.target.angle >= 0 ? msg.target.angle : null;
|
||||||
|
dirTimer.value = setTimeout(() => {
|
||||||
|
angle.value = null;
|
||||||
|
}, 1200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
uni.$on("socket-inbox", onReceiveMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
@@ -109,6 +139,11 @@ onBeforeUnmount(() => {
|
|||||||
clearTimeout(timer.value);
|
clearTimeout(timer.value);
|
||||||
timer.value = null;
|
timer.value = null;
|
||||||
}
|
}
|
||||||
|
if (dirTimer.value) {
|
||||||
|
clearTimeout(dirTimer.value);
|
||||||
|
dirTimer.value = null;
|
||||||
|
}
|
||||||
|
uni.$off("socket-inbox", onReceiveMessage);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -122,6 +157,9 @@ onBeforeUnmount(() => {
|
|||||||
}}</text>
|
}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="target">
|
<view class="target">
|
||||||
|
<view v-if="angle !== null" class="arrow-dir" :style="arrowStyle">
|
||||||
|
<image src="../static/arrow-direction.png" mode="widthFix" />
|
||||||
|
</view>
|
||||||
<view v-if="stop" class="stop-sign">中场休息</view>
|
<view v-if="stop" class="stop-sign">中场休息</view>
|
||||||
<view
|
<view
|
||||||
v-if="latestOne && latestOne.ring && user.id === latestOne.playerId"
|
v-if="latestOne && latestOne.ring && user.id === latestOne.playerId"
|
||||||
@@ -206,7 +244,7 @@ onBeforeUnmount(() => {
|
|||||||
<view v-if="avatar" class="footer">
|
<view v-if="avatar" class="footer">
|
||||||
<image :src="avatar" mode="widthFix" />
|
<image :src="avatar" mode="widthFix" />
|
||||||
</view>
|
</view>
|
||||||
<view class="simul" v-if="showsimul">
|
<view class="simul" v-if="env !== 'release'">
|
||||||
<button @click="simulShoot">模拟</button>
|
<button @click="simulShoot">模拟</button>
|
||||||
<button @click="simulShoot2">射箭</button>
|
<button @click="simulShoot2">射箭</button>
|
||||||
</view>
|
</view>
|
||||||
@@ -339,4 +377,24 @@ onBeforeUnmount(() => {
|
|||||||
z-index: 99;
|
z-index: 99;
|
||||||
font-weight: bold;
|
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%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import { ref, watch, onMounted, onBeforeUnmount } from "vue";
|
import { ref, watch, onMounted, onBeforeUnmount } from "vue";
|
||||||
import audioManager from "@/audioManager";
|
import audioManager from "@/audioManager";
|
||||||
import { MESSAGETYPES } from "@/constants";
|
import { MESSAGETYPES } from "@/constants";
|
||||||
|
import { getDirectionText } from "@/util";
|
||||||
|
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
@@ -11,7 +13,6 @@ const tips = ref("");
|
|||||||
const melee = ref(false);
|
const melee = ref(false);
|
||||||
const timer = ref(null);
|
const timer = ref(null);
|
||||||
const sound = ref(true);
|
const sound = ref(true);
|
||||||
const currentSound = ref("");
|
|
||||||
const currentRound = ref(0);
|
const currentRound = ref(0);
|
||||||
const currentRoundEnded = ref(false);
|
const currentRoundEnded = ref(false);
|
||||||
const ended = ref(false);
|
const ended = ref(false);
|
||||||
@@ -22,7 +23,6 @@ const totalShot = ref(0);
|
|||||||
watch(
|
watch(
|
||||||
() => tips.value,
|
() => tips.value,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
if (!sound.value) return;
|
|
||||||
let key = [];
|
let key = [];
|
||||||
if (newVal.includes("重回")) return;
|
if (newVal.includes("重回")) return;
|
||||||
if (currentRoundEnded.value) {
|
if (currentRoundEnded.value) {
|
||||||
@@ -44,11 +44,11 @@ watch(
|
|||||||
|
|
||||||
const updateSound = () => {
|
const updateSound = () => {
|
||||||
sound.value = !sound.value;
|
sound.value = !sound.value;
|
||||||
if (!sound.value) audioManager.stop(currentSound.value);
|
audioManager.setMuted(!sound.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function onReceiveMessage(messages = []) {
|
async function onReceiveMessage(messages = []) {
|
||||||
if (!sound.value || ended.value) return;
|
if (ended.value) return;
|
||||||
messages.forEach((msg) => {
|
messages.forEach((msg) => {
|
||||||
if (msg.constructor === MESSAGETYPES.ShootResult) {
|
if (msg.constructor === MESSAGETYPES.ShootResult) {
|
||||||
if (melee.value && msg.userId !== user.value.id) return;
|
if (melee.value && msg.userId !== user.value.id) return;
|
||||||
@@ -72,10 +72,11 @@ async function onReceiveMessage(messages = []) {
|
|||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
if (!halfTime.value && msg.target) {
|
if (!halfTime.value && msg.target) {
|
||||||
currentSound.value = msg.target.ring
|
let key = [];
|
||||||
? `${msg.target.ring}环`
|
key.push(msg.target.ring ? `${msg.target.ring}环` : "未上靶");
|
||||||
: "未上靶";
|
if (!msg.target.ring)
|
||||||
audioManager.play(currentSound.value);
|
key.push(`向${getDirectionText(msg.target.angle)}调整`);
|
||||||
|
audioManager.play(key);
|
||||||
}
|
}
|
||||||
} else if (msg.constructor === MESSAGETYPES.InvalidShot) {
|
} else if (msg.constructor === MESSAGETYPES.InvalidShot) {
|
||||||
if (msg.userId === user.value.id) {
|
if (msg.userId === user.value.id) {
|
||||||
@@ -123,7 +124,6 @@ async function onReceiveMessage(messages = []) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const playSound = (key) => {
|
const playSound = (key) => {
|
||||||
currentSound.value = key;
|
|
||||||
audioManager.play(key);
|
audioManager.play(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,13 @@
|
|||||||
import { ref, watch, onMounted, onBeforeUnmount } from "vue";
|
import { ref, watch, onMounted, onBeforeUnmount } from "vue";
|
||||||
import audioManager from "@/audioManager";
|
import audioManager from "@/audioManager";
|
||||||
import { MESSAGETYPES } from "@/constants";
|
import { MESSAGETYPES } from "@/constants";
|
||||||
|
import { getDirectionText } from "@/util";
|
||||||
|
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const { user } = storeToRefs(store);
|
const { user } = storeToRefs(store);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: {
|
show: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -41,7 +44,6 @@ const barColor = ref("#fed847");
|
|||||||
const remain = ref(props.total);
|
const remain = ref(props.total);
|
||||||
const timer = ref(null);
|
const timer = ref(null);
|
||||||
const sound = ref(true);
|
const sound = ref(true);
|
||||||
const currentSound = ref("");
|
|
||||||
const currentRound = ref(props.currentRound);
|
const currentRound = ref(props.currentRound);
|
||||||
const currentRoundEnded = ref(false);
|
const currentRoundEnded = ref(false);
|
||||||
const ended = ref(false);
|
const ended = ref(false);
|
||||||
@@ -53,7 +55,7 @@ watch(
|
|||||||
let key = "";
|
let key = "";
|
||||||
if (newVal.includes("红队")) key = "请红方射箭";
|
if (newVal.includes("红队")) key = "请红方射箭";
|
||||||
if (newVal.includes("蓝队")) key = "请蓝方射箭";
|
if (newVal.includes("蓝队")) key = "请蓝方射箭";
|
||||||
if (key && sound.value) {
|
if (key) {
|
||||||
if (currentRoundEnded.value) {
|
if (currentRoundEnded.value) {
|
||||||
currentRound.value += 1;
|
currentRound.value += 1;
|
||||||
currentRoundEnded.value = false;
|
currentRoundEnded.value = false;
|
||||||
@@ -118,11 +120,11 @@ const updateRemain = (value) => {
|
|||||||
|
|
||||||
const updateSound = () => {
|
const updateSound = () => {
|
||||||
sound.value = !sound.value;
|
sound.value = !sound.value;
|
||||||
if (!sound.value) audioManager.stop(currentSound.value);
|
audioManager.setMuted(!sound.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function onReceiveMessage(messages = []) {
|
async function onReceiveMessage(messages = []) {
|
||||||
if (!sound.value || ended.value) return;
|
if (ended.value) return;
|
||||||
messages.forEach((msg) => {
|
messages.forEach((msg) => {
|
||||||
if (
|
if (
|
||||||
(props.battleId && msg.constructor === MESSAGETYPES.ShootResult) ||
|
(props.battleId && msg.constructor === MESSAGETYPES.ShootResult) ||
|
||||||
@@ -130,10 +132,11 @@ async function onReceiveMessage(messages = []) {
|
|||||||
) {
|
) {
|
||||||
if (props.melee && msg.userId !== user.value.id) return;
|
if (props.melee && msg.userId !== user.value.id) return;
|
||||||
if (!halfTime.value && msg.target) {
|
if (!halfTime.value && msg.target) {
|
||||||
currentSound.value = msg.target.ring
|
let key = [];
|
||||||
? `${msg.target.ring}环`
|
key.push(msg.target.ring ? `${msg.target.ring}环` : "未上靶");
|
||||||
: "未上靶";
|
if (!msg.target.ring)
|
||||||
audioManager.play(currentSound.value);
|
key.push(`向${getDirectionText(msg.target.angle)}调整`);
|
||||||
|
audioManager.play(key);
|
||||||
}
|
}
|
||||||
} else if (msg.constructor === MESSAGETYPES.InvalidShot) {
|
} else if (msg.constructor === MESSAGETYPES.InvalidShot) {
|
||||||
if (msg.userId === user.value.id) {
|
if (msg.userId === user.value.id) {
|
||||||
@@ -164,7 +167,6 @@ async function onReceiveMessage(messages = []) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const playSound = (key) => {
|
const playSound = (key) => {
|
||||||
currentSound.value = key;
|
|
||||||
audioManager.play(key);
|
audioManager.play(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
BIN
src/static/arrow-direction.png
Normal file
BIN
src/static/arrow-direction.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
21
src/util.js
21
src/util.js
@@ -676,3 +676,24 @@ export const generateShareCard = (canvasId, date, actual, total) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const generateShareImage = (canvasId) => {};
|
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 "右下";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user