123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- <template>
- <div>
- <Transition :enter-active-class="proxy?.animate.mask.enter">
- <div class="mask" v-if="actionState"></div>
- </Transition>
- <Transition :enter-active-class="proxy?.animate.rankingWindow.enter"
- :leave-active-class="proxy?.animate.rankingWindow.leave">
- <div class="confirmDiaBg" v-if="actionState">
- <div class="confirmDiaWindow">
- <div class="prompt"><i></i> {{ actionText }}</div>
- <div class="confirmDiaWindow-con">
- <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 class="time" v-if="timerNum>0"><div>{{timerNum}}</div></div>
- </div>
- <div class="gamePlayer">
- <div class="item" v-for="item in areaStateList" :key="item.area">
- <div class="player">
- <template v-if="currentGame == 'bodyposecontroller'">
- <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_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 class="name">
- <template v-if="currentGame == 'game_fruit'">玩法:挥动双手切开水果,失败三次结束游戏</template>
- <template v-if="currentGame == 'game_basketball'">玩法:身体左右移动对准篮筐,双手投篮</template>
- <template v-if="currentGame == 'game_football'">玩法:身体左右移动躲避雪糕筒,踢腿射门</template>
- </div>
- </div>
- </div>
- <div @click="getExit" class="close"></div>
- </div>
- </Transition>
- </div>
- </template>
- <script setup lang="ts">
- import { initSpeech, speckText, playMusic, controlMusic, speckCancel, chineseNumber } from '@/utils/speech';
- const { proxy } = getCurrentInstance() as any;
- const emit = defineEmits(['confirmExit', 'confirmStart']);
- const data = reactive<any>({
- currentGame: "",
- currentGameArea: [],
- actionState: false,//窗口状态
- areaStateList: [],
- lock: false,
- actionText: "",
- timer: null,
- timerNum: null,
- });
- const { currentGame, currentGameArea, actionState, areaStateList, lock, actionText, timer, timerNum } = toRefs(data);
- onBeforeMount(() => {
- //初始化语音
- initSpeech();
- })
- onMounted(() => {
- })
- /**
- * 初始化
- */
- const getInit = (e: any) => {
- let area = e.ctrl_name.replace('bodyposecontroller_', '');
- let arr = e.data.result.boxes;
- let myArr = e.data.result.keypoints;
- let result = [];
- for (let i = 0; i < myArr.length; i += 3) {
- result.push(myArr.slice(i, i + 2));
- }
- //console.log("result", result)
- if (currentGame.value == 'bodyposecontroller') {
- 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;
- }
- }
- if (currentGame.value == 'game_basketball') {
- 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;
- }
- }
- 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') {
- 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) => {
- lock.value = false;
- currentGame.value = game;
- currentGameArea.value = area;
- let list = area.map((item: any) => {
- let obj = {
- area: item,
- state: false
- }
- return obj;
- }) || [];
- areaStateList.value = list;
- actionState.value = true;
- let txt = ""
- if (currentGame.value == 'bodyposecontroller') {
- txt = "举手开始游戏";
- }
- if (currentGame.value == 'game_fruit') {
- txt = "举手开始游戏";
- }
- if (currentGame.value == 'game_basketball') {
- txt = "请做投篮动作开始游戏";
- }
- if (currentGame.value == 'game_football') {
- txt = "请踢腿开始游戏";
- }
- actionText.value = txt;
- speckText(actionText.value);
- };
- /**
- * 关闭
- */
- const getExit = () => {
- clearInterval(timer.value);
- timer.value = null;
- speckCancel(); //停止播报
- actionState.value = false;
- 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;
- timerNum.value = 3;
- timer.value = setInterval(() => {
- if (timerNum.value == 1) {
- actionState.value = false;
- clearInterval(timer.value);
- timer.value = null;
- emit('confirmStart', areaStateList.value);
- }
- speckText(timerNum.value);
- timerNum.value--;
- }, 1000)
- }
- },
- { deep: true }
- );
- //暴露给父组件用
- defineExpose({
- getOpen,
- getInit
- })
- </script>
- <style lang="scss" scoped>
- .mask {
- position: fixed;
- height: 100vh;
- width: 100vw;
- top: 0;
- left: 0;
- background: rgba(0, 0, 0, 0.6);
- z-index: 998;
- }
- .confirmDiaBg {
- width: 70rem;
- position: fixed;
- left: 50%;
- top: 50%;
- margin-left: calc(70rem / -2);
- margin-top: calc(((70rem / 2) + 12rem) / -2);
- display: flex;
- flex-direction: column;
- z-index: 999;
- .confirmDiaWindow {
- border-radius: 1.6rem;
- opacity: 1;
- text-align: center;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-direction: column;
- margin-bottom: 20px;
- 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;
- margin-right: 3px;
- display: block;
- background-image: url('@/assets/images/game/prompt.png');
- background-position: bottom;
- background-repeat: no-repeat;
- background-size: 100%;
- }
- }
- .confirmDiaWindow-con {
- width: 85%;
- padding: 25px;
- display: flex;
- justify-content: center;
- flex-direction: column;
- .picBox {
- 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));
- position: relative;
- display: flex;
- .time{
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.5);
- color: #ffffff;
- font-size: 7rem;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- 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;
- }
- 100% {
- opacity: 1;
- }
- }
- }
- }
- }
- }
- .name {
- width: 100%;
- color: #ffffff;
- font-size: 2.8vh;
- padding-top: 20px;
- }
- }
- }
- .close {
- margin: 0 auto;
- }
- }
- </style>
|