|
@@ -7,10 +7,56 @@
|
|
:leave-active-class="proxy?.animate.rankingWindow.leave">
|
|
:leave-active-class="proxy?.animate.rankingWindow.leave">
|
|
<div class="confirmDiaBg" v-if="actionState">
|
|
<div class="confirmDiaBg" v-if="actionState">
|
|
<div class="confirmDiaWindow">
|
|
<div class="confirmDiaWindow">
|
|
|
|
+ <div class="prompt"><i></i> <template v-if="currentGame == 'game_fruit'">
|
|
|
|
+ 举手开始游戏
|
|
|
|
+ </template>
|
|
|
|
+ <template v-if="currentGame == 'game_basketball'">
|
|
|
|
+ 请做投篮动作开始游戏
|
|
|
|
+ </template>
|
|
|
|
+ <template v-if="currentGame == 'game_football'">
|
|
|
|
+ 请踢腿开始游戏
|
|
|
|
+ </template>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
<div class="confirmDiaWindow-con">
|
|
<div class="confirmDiaWindow-con">
|
|
- <div class="pic">
|
|
|
|
- <div v-for="item in areaStateList" :key="item.area">
|
|
|
|
- {{ item.state }}
|
|
|
|
|
|
+ <div class="picBox">
|
|
|
|
+ <div class="pic">
|
|
|
|
+ <div class="gamePic">
|
|
|
|
+ <template v-if="currentGame == 'game_fruit'">
|
|
|
|
+ <img src="@/assets/images/game/img2.png" width="200" />
|
|
|
|
+ </template>
|
|
|
|
+ <template v-if="currentGame == 'game_basketball'">
|
|
|
|
+ <img src="@/assets/images/game/img1.png" width="200" />
|
|
|
|
+ </template>
|
|
|
|
+ <template v-if="currentGame == 'game_football'">
|
|
|
|
+ <img src="@/assets/images/game/img3.png" width="200" />
|
|
|
|
+ </template>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="gamePlayer">
|
|
|
|
+ <div class="item" v-for="item in areaStateList" :key="item.area">
|
|
|
|
+ <div class="player">
|
|
|
|
+ <template v-if="currentGame == 'game_fruit'">
|
|
|
|
+ <div><img src="@/assets/images/game/fruit1.png" class="fade-image"
|
|
|
|
+ :class="{ 'fade-image1': !item.state }"></img></div>
|
|
|
|
+ <div><img v-if="!item.state" src="@/assets/images/game/fruit2.png" class="fade-image"
|
|
|
|
+ :class="{ 'fade-image2': !item.state }"></img></div>
|
|
|
|
+ </template>
|
|
|
|
+ <template v-if="currentGame == 'game_basketball'">
|
|
|
|
+ <div><img src="@/assets/images/game/basketball1.png" class="fade-image"
|
|
|
|
+ :class="{ 'fade-image1': !item.state }"></img></div>
|
|
|
|
+ <div><img v-if="!item.state" src="@/assets/images/game/basketball2.png" class="fade-image"
|
|
|
|
+ :class="{ 'fade-image2': !item.state }"></img></div>
|
|
|
|
+ </template>
|
|
|
|
+ <template v-if="currentGame == 'game_football'">
|
|
|
|
+ <div><img src="@/assets/images/game/football1.png" class="fade-image"
|
|
|
|
+ :class="{ 'fade-image1': !item.state }"></img></div>
|
|
|
|
+ <div><img v-if="!item.state" src="@/assets/images/game/football2.png" class="fade-image"
|
|
|
|
+ :class="{ 'fade-image2': !item.state }"></img></div>
|
|
|
|
+ </template>
|
|
|
|
+ <div v-if="item.state" class="ok"><img src="@/assets/images/game/ok.png"></div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="name">
|
|
<div class="name">
|
|
@@ -27,21 +73,25 @@
|
|
|
|
|
|
</template>
|
|
</template>
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
|
+import { initSpeech, speckText, playMusic, controlMusic, speckCancel, chineseNumber } from '@/utils/speech';
|
|
const { proxy } = getCurrentInstance() as any;
|
|
const { proxy } = getCurrentInstance() as any;
|
|
-const emit = defineEmits(['confirmExit']);
|
|
|
|
|
|
+const emit = defineEmits(['confirmExit', 'confirmStart']);
|
|
|
|
|
|
const data = reactive<any>({
|
|
const data = reactive<any>({
|
|
currentGame: "",
|
|
currentGame: "",
|
|
currentGameArea: [],
|
|
currentGameArea: [],
|
|
actionState: false,//窗口状态
|
|
actionState: false,//窗口状态
|
|
areaStateList: [],
|
|
areaStateList: [],
|
|
|
|
+ lock: false,
|
|
});
|
|
});
|
|
|
|
|
|
-const { currentGame, currentGameArea, actionState, areaStateList } = toRefs(data);
|
|
|
|
|
|
+const { currentGame, currentGameArea, actionState, areaStateList, lock } = toRefs(data);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeMount(() => {
|
|
onBeforeMount(() => {
|
|
|
|
+ //初始化语音
|
|
|
|
+ initSpeech();
|
|
})
|
|
})
|
|
|
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
@@ -60,7 +110,12 @@ const getInit = (e: any) => {
|
|
}
|
|
}
|
|
//console.log("result", result)
|
|
//console.log("result", result)
|
|
if (currentGame.value == 'bodyposecontroller') {
|
|
if (currentGame.value == 'bodyposecontroller') {
|
|
-
|
|
|
|
|
|
+ let myIndex = areaStateList.value.findIndex((item: any) => {
|
|
|
|
+ return item.area == area;
|
|
|
|
+ })
|
|
|
|
+ let boxes = [{ x: arr[0], y: arr[3] }, { x: arr[0], y: arr[1] }, { x: arr[2], y: arr[1] }, { x: arr[2], y: arr[3] }];
|
|
|
|
+ areaStateList.value[myIndex].state = true;
|
|
|
|
+ areaStateList.value[myIndex].boxes = boxes;
|
|
}
|
|
}
|
|
if (currentGame.value == 'game_basketball') {
|
|
if (currentGame.value == 'game_basketball') {
|
|
let leftA = result[6][1];//右肩Y
|
|
let leftA = result[6][1];//右肩Y
|
|
@@ -78,18 +133,109 @@ const getInit = (e: any) => {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (currentGame.value == 'game_football') {
|
|
if (currentGame.value == 'game_football') {
|
|
|
|
+ let leftA = { x: result[12][0], y: result[12][1] };//大腿
|
|
|
|
+ let leftB = { x: result[14][0], y: result[14][1] };//膝盖
|
|
|
|
+ let leftC = { x: result[16][0], y: result[16][1] };//脚
|
|
|
|
+
|
|
|
|
+ let rightA = { x: result[11][0], y: result[11][1] };//大腿
|
|
|
|
+ let rightB = { x: result[13][0], y: result[13][1] };//膝盖
|
|
|
|
+ let rightC = { x: result[15][0], y: result[15][1] };//脚
|
|
|
|
|
|
|
|
+ let jiaodu1 = calculateAngleAtB(leftA, leftB, leftC)
|
|
|
|
+ let jiaodu2 = calculateAngleAtB(rightA, rightB, rightC)
|
|
|
|
+ // console.log("jiaodu1",jiaodu1)
|
|
|
|
+ // console.log("jiaodu2",jiaodu2)
|
|
|
|
+ if (jiaodu1 <= 80 && jiaodu2 >= 120 || jiaodu2 <= 80 && jiaodu2 >= 120) {
|
|
|
|
+ let myIndex = areaStateList.value.findIndex((item: any) => {
|
|
|
|
+ return item.area == area;
|
|
|
|
+ })
|
|
|
|
+ let boxes = [{ x: arr[0], y: arr[3] }, { x: arr[0], y: arr[1] }, { x: arr[2], y: arr[1] }, { x: arr[2], y: arr[3] }];
|
|
|
|
+ areaStateList.value[myIndex].state = true;
|
|
|
|
+ areaStateList.value[myIndex].boxes = boxes;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
if (currentGame.value == 'game_fruit') {
|
|
if (currentGame.value == 'game_fruit') {
|
|
-
|
|
|
|
|
|
+ let leftA = result[6][1];//右肩Y
|
|
|
|
+ let rightA = result[5][1];//左肩Y
|
|
|
|
+ let leftB = result[10][1];//右手Y
|
|
|
|
+ let rightB = result[9][1];//左手Y
|
|
|
|
+ let bizi = result[0][1];//鼻子Y
|
|
|
|
+ if (leftB < leftA || rightB < rightA || leftB < bizi || rightB < bizi) {
|
|
|
|
+ let myIndex = areaStateList.value.findIndex((item: any) => {
|
|
|
|
+ return item.area == area;
|
|
|
|
+ })
|
|
|
|
+ let boxes = [{ x: arr[0], y: arr[3] }, { x: arr[0], y: arr[1] }, { x: arr[2], y: arr[1] }, { x: arr[2], y: arr[3] }];
|
|
|
|
+ areaStateList.value[myIndex].state = true;
|
|
|
|
+ areaStateList.value[myIndex].boxes = boxes;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+/**
|
|
|
|
+ * 计算B点的夹角度数(∠ABC)
|
|
|
|
+ * @param {Object} pointA - A点坐标 {x, y, z可选}
|
|
|
|
+ * @param {Object} pointB - B点坐标 {x, y, z可选}
|
|
|
|
+ * @param {Object} pointC - C点坐标 {x, y, z可选}
|
|
|
|
+ * @returns {number} B点的夹角度数(保留两位小数)
|
|
|
|
+ */
|
|
|
|
+const calculateAngleAtB = (pointA, pointB, pointC) => {
|
|
|
|
+ // 计算向量BA和向量BC
|
|
|
|
+ const vectorBA = {
|
|
|
|
+ x: pointA.x - pointB.x,
|
|
|
|
+ y: pointA.y - pointB.y,
|
|
|
|
+ z: (pointA.z || 0) - (pointB.z || 0)
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const vectorBC = {
|
|
|
|
+ x: pointC.x - pointB.x,
|
|
|
|
+ y: pointC.y - pointB.y,
|
|
|
|
+ z: (pointC.z || 0) - (pointB.z || 0)
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 计算点积
|
|
|
|
+ const dotProduct =
|
|
|
|
+ vectorBA.x * vectorBC.x +
|
|
|
|
+ vectorBA.y * vectorBC.y +
|
|
|
|
+ vectorBA.z * vectorBC.z;
|
|
|
|
+
|
|
|
|
+ // 计算向量BA的模长
|
|
|
|
+ const lengthBA = Math.sqrt(
|
|
|
|
+ vectorBA.x ** 2 +
|
|
|
|
+ vectorBA.y ** 2 +
|
|
|
|
+ vectorBA.z ** 2
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ // 计算向量BC的模长
|
|
|
|
+ const lengthBC = Math.sqrt(
|
|
|
|
+ vectorBC.x ** 2 +
|
|
|
|
+ vectorBC.y ** 2 +
|
|
|
|
+ vectorBC.z ** 2
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ // 防止除以零的情况
|
|
|
|
+ if (lengthBA === 0 || lengthBC === 0) {
|
|
|
|
+ throw new Error("点A、B、C不能重合");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 计算余弦值
|
|
|
|
+ const cosine = dotProduct / (lengthBA * lengthBC);
|
|
|
|
+
|
|
|
|
+ // 由于计算误差可能导致cosine略超出[-1, 1]范围,需要修正
|
|
|
|
+ const clampedCosine = Math.max(Math.min(cosine, 1), -1);
|
|
|
|
+
|
|
|
|
+ // 计算弧度并转换为角度
|
|
|
|
+ const angleRadians = Math.acos(clampedCosine);
|
|
|
|
+ const angleDegrees = angleRadians * (180 / Math.PI);
|
|
|
|
+
|
|
|
|
+ // 保留两位小数并返回
|
|
|
|
+ return parseFloat(angleDegrees.toFixed(2));
|
|
|
|
+}
|
|
|
|
|
|
/**
|
|
/**
|
|
* 打开
|
|
* 打开
|
|
*/
|
|
*/
|
|
const getOpen = async (game: any, area: any) => {
|
|
const getOpen = async (game: any, area: any) => {
|
|
|
|
+ lock.value = false;
|
|
currentGame.value = game;
|
|
currentGame.value = game;
|
|
currentGameArea.value = area;
|
|
currentGameArea.value = area;
|
|
let list = area.map((item: any) => {
|
|
let list = area.map((item: any) => {
|
|
@@ -107,10 +253,42 @@ const getOpen = async (game: any, area: any) => {
|
|
* 关闭
|
|
* 关闭
|
|
*/
|
|
*/
|
|
const getExit = () => {
|
|
const getExit = () => {
|
|
|
|
+ speckCancel(); //停止播报
|
|
actionState.value = false;
|
|
actionState.value = false;
|
|
emit('confirmExit', {});
|
|
emit('confirmExit', {});
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+/**
|
|
|
|
+ * 监听全部OK
|
|
|
|
+ */
|
|
|
|
+watch(
|
|
|
|
+ () => areaStateList.value,
|
|
|
|
+ (newData, oldData) => {
|
|
|
|
+ //都准备就绪就开始
|
|
|
|
+ if (lock.value) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ let state = newData.every((item: any) => {
|
|
|
|
+ return item.state;
|
|
|
|
+ })
|
|
|
|
+ if (state) {
|
|
|
|
+ lock.value = true;
|
|
|
|
+ let num = 3;
|
|
|
|
+ let timer: any = setInterval(() => {
|
|
|
|
+ if (num == 1) {
|
|
|
|
+ actionState.value = false;
|
|
|
|
+ clearInterval(timer);
|
|
|
|
+ timer = null;
|
|
|
|
+ emit('confirmStart', areaStateList.value);
|
|
|
|
+ }
|
|
|
|
+ speckText(num);
|
|
|
|
+ num--;
|
|
|
|
+ }, 1000)
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ { deep: true }
|
|
|
|
+);
|
|
|
|
+
|
|
//暴露给父组件用
|
|
//暴露给父组件用
|
|
defineExpose({
|
|
defineExpose({
|
|
getOpen,
|
|
getOpen,
|
|
@@ -134,7 +312,7 @@ defineExpose({
|
|
left: 50%;
|
|
left: 50%;
|
|
top: 50%;
|
|
top: 50%;
|
|
margin-left: calc(70rem / -2);
|
|
margin-left: calc(70rem / -2);
|
|
- margin-top: calc(((70rem / 2) + 3.2rem) / -2);
|
|
|
|
|
|
+ margin-top: calc(((70rem / 2) + 12rem) / -2);
|
|
display: flex;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
z-index: 999;
|
|
z-index: 999;
|
|
@@ -146,18 +324,130 @@ defineExpose({
|
|
display: flex;
|
|
display: flex;
|
|
align-items: center;
|
|
align-items: center;
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
|
+ flex-direction: column;
|
|
margin-bottom: 20px;
|
|
margin-bottom: 20px;
|
|
- background: linear-gradient(62deg, #092941 -85%, #2A484B 96%);
|
|
|
|
|
|
+ background: linear-gradient(59deg, #092941 -85%, #2A484B 96%);
|
|
|
|
+ box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.5577);
|
|
|
|
+
|
|
|
|
+ .prompt {
|
|
|
|
+ color: #ffffff;
|
|
|
|
+ font-size: 2.5vh;
|
|
|
|
+ display: flex;
|
|
|
|
+ line-height: 3vh;
|
|
|
|
+ padding-top: 2vh;
|
|
|
|
+
|
|
|
|
+ i {
|
|
|
|
+ width: 3vh;
|
|
|
|
+ height: 3vh;
|
|
|
|
+ display: block;
|
|
|
|
+ background-image: url('@/assets/images/game/prompt.png');
|
|
|
|
+ background-position: bottom;
|
|
|
|
+ background-repeat: no-repeat;
|
|
|
|
+ background-size: 100%;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
.confirmDiaWindow-con {
|
|
.confirmDiaWindow-con {
|
|
|
|
+ width: 85%;
|
|
padding: 25px;
|
|
padding: 25px;
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: center;
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
|
- .pic {
|
|
|
|
|
|
+ .picBox {
|
|
|
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
|
+ background-image: url('@/assets/images/game/bg.png');
|
|
|
|
+ background-position: bottom;
|
|
|
|
+ background-repeat: no-repeat;
|
|
|
|
+ background-size: 100%;
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: center;
|
|
|
|
+ padding-bottom: 10vh;
|
|
|
|
+
|
|
|
|
+ .pic {
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: center;
|
|
|
|
+ flex-direction: column;
|
|
|
|
+
|
|
|
|
+ .gamePic {
|
|
|
|
+ border: 5px solid #3BDDCE;
|
|
|
|
+ width: 60vh;
|
|
|
|
+ height: calc(60vh * (1080 / 1920));
|
|
|
|
+
|
|
|
|
+ img {
|
|
|
|
+ width: 100%;
|
|
|
|
+ height: 100%;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .gamePlayer {
|
|
|
|
+ display: flex;
|
|
|
|
+ width: 60vh;
|
|
|
|
+ margin-top: -20vh;
|
|
|
|
+
|
|
|
|
+ .item {
|
|
|
|
+ flex: 1;
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: center;
|
|
|
|
+
|
|
|
|
+ .player {
|
|
|
|
+ position: relative;
|
|
|
|
+ width: 22vh;
|
|
|
|
+ height: calc(22vh * (1000 / 518));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .fade-image {
|
|
|
|
+ width: 100%;
|
|
|
|
+ height: 100%;
|
|
|
|
+ position: absolute;
|
|
|
|
+ left: 0;
|
|
|
|
+ top: 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .fade-image1 {
|
|
|
|
+ animation: fadeOpacity1 2s infinite;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .fade-image2 {
|
|
|
|
+ animation: fadeOpacity2 2s infinite;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .ok {
|
|
|
|
+ width: 8vh;
|
|
|
|
+ height: 8vh;
|
|
|
|
+ position: absolute;
|
|
|
|
+ top: 50%;
|
|
|
|
+ left: 50%;
|
|
|
|
+ margin-top: 1vh;
|
|
|
|
+ margin-left: -4vh;
|
|
|
|
+
|
|
|
|
+ img {
|
|
|
|
+ width: 100%;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @keyframes fadeOpacity1 {
|
|
|
|
+ 0% {
|
|
|
|
+ opacity: 1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 100% {
|
|
|
|
+ opacity: 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @keyframes fadeOpacity2 {
|
|
|
|
+ 0% {
|
|
|
|
+ opacity: 0;
|
|
|
|
+ }
|
|
|
|
|
|
- img {
|
|
|
|
- width: 100%;
|
|
|
|
|
|
+ 100% {
|
|
|
|
+ opacity: 1;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -165,7 +455,7 @@ defineExpose({
|
|
width: 100%;
|
|
width: 100%;
|
|
color: #ffffff;
|
|
color: #ffffff;
|
|
font-size: 2.8vh;
|
|
font-size: 2.8vh;
|
|
- margin-bottom: 30px;
|
|
|
|
|
|
+ padding-top: 20px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|