|
|
@@ -1,4 +1,43 @@
|
|
|
<template>
|
|
|
+ <!-- 全屏遮罩盒子 通关后弹框得奖杯 -->
|
|
|
+ <div class="fullscreen-overlay" v-if="showOverlay">
|
|
|
+ <div
|
|
|
+ class="centered-box"
|
|
|
+ :style="{
|
|
|
+ backgroundImage: `url(${cupbg})`,
|
|
|
+ backgroundSize: '110%',
|
|
|
+ backgroundPosition: 'center',
|
|
|
+ backgroundRepeat: 'no-repeat'
|
|
|
+ }">
|
|
|
+ <!-- 彩带飘落效果 -->
|
|
|
+ <div class="confetti-container">
|
|
|
+ <div v-for="n in 50" :key="n" class="confetti" :class="`confetti-${n % 12 + 1}`"></div>
|
|
|
+ </div>
|
|
|
+ <!-- 关闭按钮 -->
|
|
|
+ <div class="close-button" @click="closeOverlay">×</div>
|
|
|
+ <!-- 题目 -->
|
|
|
+ <div class="top-box">
|
|
|
+ <div class="title-text">YOU WIN!</div>
|
|
|
+ </div>
|
|
|
+ <!-- 奖杯 -->
|
|
|
+ <div class="middle-box">
|
|
|
+ <img :src="goldcup" alt="奖杯" class="gold-cup-image" />
|
|
|
+ </div>
|
|
|
+ <!-- 星星 -->
|
|
|
+ <div class="bottom-box">
|
|
|
+ <img
|
|
|
+ v-for="index in starCount"
|
|
|
+ :key="index"
|
|
|
+ :src="star02"
|
|
|
+ alt="星星"
|
|
|
+ class="gold-star-image"
|
|
|
+ :class="index % 2 === 0 ? 'star-bottom' : 'star-top'"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div class="title-box">
|
|
|
<!-- 左侧标题部分 -->
|
|
|
<div class="left-container">
|
|
|
@@ -81,7 +120,7 @@
|
|
|
<div class="workspace-section">
|
|
|
<div class="controls">
|
|
|
<button id="runCode" @click="runCode" :disabled="isRunning">运行代码</button>
|
|
|
-<!-- <button @click="generatePythonCode">生成Python代码</button>-->
|
|
|
+ <!-- <button @click="generatePythonCode">生成Python代码</button>-->
|
|
|
<button @click="resetPlayer" >重置玩家</button>
|
|
|
<button @click="clearWorkspace">清空工作区</button>
|
|
|
</div>
|
|
|
@@ -90,6 +129,7 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
@@ -107,6 +147,21 @@ import errorMp3 from '@/assets/music/blockly/error.MP3'
|
|
|
import { registerCustomBlocks, registerJavaScriptGenerators, registerPythonGenerators, initBlockly } from '@/api/blockly/blockly.js';
|
|
|
import {playMp3} from "@/api/blockly/music.js";
|
|
|
|
|
|
+
|
|
|
+import cupbg from '@/assets/blockly/cupbg.png' // 通关后奖杯底图
|
|
|
+import nocupbg from '@/assets/blockly/nocupbg.png' // 通关失败后奖杯底图
|
|
|
+import goldcup from '@/assets/blockly/goldcup.png' // 金杯
|
|
|
+import silvercup from '@/assets/blockly/silvercup.png' // 银杯
|
|
|
+import coppercup from '@/assets/blockly/coppercup.png' // 铜杯
|
|
|
+import nocup from '@/assets/blockly/nocup.png' // 无杯状态
|
|
|
+import star02 from '@/assets/blockly/star02.png' // 星星
|
|
|
+import star01 from '@/assets/blockly/star01.png' // 金星
|
|
|
+
|
|
|
+// 星星数量,可根据需要调整
|
|
|
+const starCount = ref(3);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
// 定义emits
|
|
|
const emits = defineEmits(['saveProgress'])
|
|
|
// 定义组件属性
|
|
|
@@ -178,6 +233,12 @@ const props = defineProps({
|
|
|
}
|
|
|
});
|
|
|
|
|
|
+// 关闭遮罩层
|
|
|
+const closeOverlay = () => {
|
|
|
+ gameState.player.hasReachedEnd = false;
|
|
|
+ showOverlay.value = false;
|
|
|
+};
|
|
|
+
|
|
|
// 配置常量
|
|
|
const CONFIG = {
|
|
|
// 动画时长配置(毫秒)
|
|
|
@@ -310,6 +371,9 @@ const gameState = reactive({
|
|
|
originalWalkablePoints: [],
|
|
|
}
|
|
|
});
|
|
|
+
|
|
|
+// 控制遮罩层显示的状态
|
|
|
+const showOverlay = ref(false);
|
|
|
// 地图元素类型字典
|
|
|
const BLOCKLY_MAP_TYPE_DICT = {
|
|
|
ICE: 'ice',//冰块
|
|
|
@@ -754,6 +818,7 @@ const resetPlayer = async () => {
|
|
|
gameState.player.isColliding = false; //碰撞标志
|
|
|
gameState.player.hasReachedEnd = false;
|
|
|
gameState.player.isSliding = false; // 重置滑行状态
|
|
|
+ showOverlay.value = false; // 隐藏遮罩层
|
|
|
};
|
|
|
|
|
|
// 清空工作区
|
|
|
@@ -1186,32 +1251,30 @@ window.isFinish = async function() {
|
|
|
if (isColliding.value) {
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
if (gameState.player.position.x === endPoint.value.x && gameState.player.position.y === endPoint.value.y) {
|
|
|
-
|
|
|
// 统计所有类型为TASK的任务点总数||必须完成拾取物品的人物总数
|
|
|
const totalTasks = gameState.mapData.walkablePoints.filter(
|
|
|
p => p.type === BLOCKLY_MAP_TYPE_DICT.TASK ||
|
|
|
p.type === BLOCKLY_MAP_TYPE_DICT.ITEM && p.must === true
|
|
|
).length;
|
|
|
-
|
|
|
// 统计其中status为true的完成任务数||完成拾取物品的人物总数
|
|
|
const completedTasks = gameState.mapData.walkablePoints.filter(
|
|
|
p => p.type === BLOCKLY_MAP_TYPE_DICT.TASK && p.status === true
|
|
|
|| p.type === BLOCKLY_MAP_TYPE_DICT.ITEM && p.must === true && p.status === true
|
|
|
).length;
|
|
|
-
|
|
|
//blockly总星星数量
|
|
|
const starCount = 3;
|
|
|
-
|
|
|
//无任务情况下直接完成
|
|
|
if (totalTasks === 0 || completedTasks === totalTasks) {
|
|
|
gameState.player.hasReachedEnd = true;
|
|
|
+ // 延迟显示遮罩层,玩家动画完成后显示
|
|
|
+ setTimeout(() => {
|
|
|
+ showOverlay.value = true;
|
|
|
+ }, 1500);
|
|
|
emits('saveProgress', 'blockly', starCount * 100)
|
|
|
showGameMessage(CONFIG.TIPS.FINISH, 'success' );
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
//任务失败
|
|
|
// 计算完成百分比
|
|
|
const completionPercentage = totalTasks > 0 ? Math.round(completedTasks / totalTasks * starCount) : starCount;
|
|
|
@@ -1702,6 +1765,189 @@ onUnmounted(() => {
|
|
|
@return math.div($px, 750) * 100vw;
|
|
|
}
|
|
|
|
|
|
+/* 全屏遮罩盒子样式 */
|
|
|
+.fullscreen-overlay {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background-color: rgba(0, 0, 0, 0.5); /* 半透明黑色 遮罩 */
|
|
|
+ z-index: 9999; /* 确保在最上层 */
|
|
|
+ // pointer-events: none; /* 允许点击穿透 */
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+/* 垂直居中盒子样式 */
|
|
|
+.centered-box {
|
|
|
+ border-radius: rpx(10);
|
|
|
+ z-index: 10000;
|
|
|
+ width: rpx(220);
|
|
|
+ height: rpx(255);
|
|
|
+ overflow: auto;
|
|
|
+ pointer-events: auto;
|
|
|
+ // display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ position: relative;
|
|
|
+ /* 初始状态:缩小、透明 */
|
|
|
+ transform: scale(0.7);
|
|
|
+ opacity: 0;
|
|
|
+ /* 动画效果 */
|
|
|
+ animation: popUp 0.5s ease-out forwards;
|
|
|
+}
|
|
|
+
|
|
|
+/* 弹出动画定义 */
|
|
|
+@keyframes popUp {
|
|
|
+ 0% {
|
|
|
+ transform: scale(0.7);
|
|
|
+ opacity: 0;
|
|
|
+ }
|
|
|
+ 70% {
|
|
|
+ transform: scale(1.05);
|
|
|
+ opacity: 0.9;
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ transform: scale(1);
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 彩带容器样式 */
|
|
|
+.confetti-container {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+ z-index: 9999;
|
|
|
+ pointer-events: none;
|
|
|
+}
|
|
|
+
|
|
|
+/* 彩带基本样式 */
|
|
|
+.confetti {
|
|
|
+ position: absolute;
|
|
|
+ width: rpx(5);
|
|
|
+ height: rpx(15);
|
|
|
+ opacity: 0;
|
|
|
+ animation-timing-function: ease-in-out;
|
|
|
+ animation-iteration-count: infinite;
|
|
|
+ animation-fill-mode: forwards;
|
|
|
+}
|
|
|
+
|
|
|
+/* 彩带颜色和动画 */
|
|
|
+.confetti-1 { background-color: #FF5252; animation: confetti-fall 2s 0.2s linear infinite; left: 5%; }
|
|
|
+.confetti-2 { background-color: #536DFE; animation: confetti-fall 2.7s 0.9s linear infinite; left: 15%; }
|
|
|
+.confetti-3 { background-color: #FFC107; animation: confetti-fall 2.3s 1.8s linear infinite; left: 25%; }
|
|
|
+.confetti-4 { background-color: #4CAF50; animation: confetti-fall 2.1s 1.4s linear infinite; left: 35%; }
|
|
|
+.confetti-5 { background-color: #9C27B0; animation: confetti-fall 2.5s 1.8s linear infinite; left: 45%; }
|
|
|
+.confetti-6 { background-color: #2196F3; animation: confetti-fall 2.2s 0.2s linear infinite; left: 55%; }
|
|
|
+.confetti-7 { background-color: #FF9800; animation: confetti-fall 2.4s 1.8s linear infinite; left: 65%; }
|
|
|
+.confetti-8 { background-color: #795548; animation: confetti-fall 2s 1.4s linear infinite; left: 75%; }
|
|
|
+.confetti-9 { background-color: #607D8B; animation: confetti-fall 2.6s 3.4s linear infinite; left: 85%; }
|
|
|
+.confetti-10 { background-color: #E91E63; animation: confetti-fall 2.3s 1.8s linear infinite; left: 95%; }
|
|
|
+
|
|
|
+/* 彩带飘落动画 */
|
|
|
+@keyframes confetti-fall {
|
|
|
+ 0% {
|
|
|
+ top: -10%;
|
|
|
+ opacity: 0;
|
|
|
+ transform: rotate(0deg);
|
|
|
+ }
|
|
|
+ 10% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ 90% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ top: 100%;
|
|
|
+ opacity: 0;
|
|
|
+ transform: rotate(360deg);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 关闭按钮样式 */
|
|
|
+.close-button {
|
|
|
+ position: absolute;
|
|
|
+ top: rpx(0);
|
|
|
+ right: rpx(2);
|
|
|
+ font-size: rpx(20);
|
|
|
+ color: #2b8fdd;
|
|
|
+ font-weight: bold;
|
|
|
+ cursor: pointer;
|
|
|
+ width: rpx(25);
|
|
|
+ height: rpx(25);
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ z-index: 10001;
|
|
|
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
|
|
+ // -webkit-text-stroke: 1px white; // 描
|
|
|
+}
|
|
|
+.close-button:hover {
|
|
|
+ transform: scale(1.1);
|
|
|
+}
|
|
|
+
|
|
|
+.top-box {
|
|
|
+ width: 100%;
|
|
|
+ height: rpx(60);
|
|
|
+ border-radius: 5px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.title-text {
|
|
|
+ font-size: rpx(20);
|
|
|
+ font-weight: bold;
|
|
|
+ color: white;
|
|
|
+ font-family: 'SourceHanSansCN-Bold_0';
|
|
|
+ text-align: center;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
|
|
+}
|
|
|
+
|
|
|
+.middle-box {
|
|
|
+ width: 100%;
|
|
|
+ height: rpx(130);
|
|
|
+ border-radius: 5px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.gold-cup-image{
|
|
|
+ height: 100%;
|
|
|
+ width: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.bottom-box {
|
|
|
+ width: 100%;
|
|
|
+ height: rpx(40);
|
|
|
+ border-radius: 5px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: rpx(12);
|
|
|
+ margin-top: -rpx(10); /* 向上调整位置 */
|
|
|
+}
|
|
|
+
|
|
|
+.gold-star-image {
|
|
|
+ width: rpx(35);
|
|
|
+ height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+.star-top {
|
|
|
+ object-position: top;
|
|
|
+}
|
|
|
+.star-bottom {
|
|
|
+ object-position: bottom;
|
|
|
+}
|
|
|
+
|
|
|
//将tileSize属性绑定到CSS变量上
|
|
|
:root {
|
|
|
--tile-size: v-bind('tileSize + "px"');
|