|
|
@@ -51,6 +51,14 @@
|
|
|
:class="{ 'collision': isColliding, 'success': hasReachedEnd }"
|
|
|
>
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- 灯光效果 -->
|
|
|
+ <div
|
|
|
+ v-if="gameState.player.isLightOn"
|
|
|
+ class="light-effect"
|
|
|
+ :style="lightEffectStyle"
|
|
|
+ >
|
|
|
+ </div>
|
|
|
|
|
|
<!-- 怀表倒计时容器 -->
|
|
|
<div v-if="showCountdown" class="watch-container" :style="countdownStyle">
|
|
|
@@ -153,7 +161,7 @@ import passRouteMp3 from '@/assets/music/blockly/pass_route.MP3';
|
|
|
import errorMp3 from '@/assets/music/blockly/error.MP3'
|
|
|
|
|
|
// 游戏接口数据
|
|
|
-import { registerCustomBlocks, registerJavaScriptGenerators, registerPythonGenerators, initBlockly, BLOCKLY_MAP_TYPE_DICT, BLOCKLY_MAP_SPECIAL_DICT } from '@/api/blockly/blockly.js';
|
|
|
+import { registerCustomBlocks, registerJavaScriptGenerators, registerPythonGenerators, initBlockly, BLOCKLY_MAP_TYPE_DICT, BLOCKLY_MAP_SPECIAL_DICT, BLOCKLY_LIGHT_COLOR_DICT } from '@/api/blockly/blockly.js';
|
|
|
import {playMp3} from "@/api/blockly/music.js";
|
|
|
|
|
|
import cupbg from '@/assets/blockly/cupbg.png' // 通关后奖杯底图
|
|
|
@@ -258,6 +266,8 @@ const CONFIG = {
|
|
|
PLAYER_FADE_DURATION: 500, // 玩家淡入淡出持续时间
|
|
|
TEMP_ITEM_FADE_DURATION: 500, // 临时物品淡入淡出持续时间
|
|
|
COMPLETION_DISPLAY: 1000, // 任务完成后显示延迟时间
|
|
|
+ LIGHT_DURATION: 2000, // 灯光效果持续时间
|
|
|
+ BLINK_INTERVAL: 500, // 灯光闪烁间隔时间
|
|
|
},
|
|
|
// 游戏配置
|
|
|
GAME: {
|
|
|
@@ -379,6 +389,10 @@ const gameState = reactive({
|
|
|
isSliding: false,
|
|
|
// 携带的物品数组
|
|
|
carriedItems: [],
|
|
|
+ // 灯光是否开启
|
|
|
+ isLightOn: false,
|
|
|
+ // 灯光颜色
|
|
|
+ lightColor: 'white',
|
|
|
},
|
|
|
|
|
|
// 游戏状态信息
|
|
|
@@ -453,7 +467,52 @@ const playerStyle = computed(() => ({
|
|
|
width: (tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO) + 'px',
|
|
|
height: (tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO) + 'px',
|
|
|
margin: (tileSize.value * CONFIG.STYLES.PLAYER_SIZE_MARGIN) + 'px',
|
|
|
+ ...(gameState.player.isLightOn ? playerGlowStyle.value : {})
|
|
|
}));
|
|
|
+
|
|
|
+// 计算灯光效果样式
|
|
|
+const lightEffectStyle = computed(() => {
|
|
|
+ // 根据颜色名称获取RGB值
|
|
|
+ const getColorRGB = (colorName) => {
|
|
|
+ const colorItem = BLOCKLY_LIGHT_COLOR_DICT.find(item => item.label === colorName);
|
|
|
+ return colorItem ? colorItem.color : null;
|
|
|
+ };
|
|
|
+
|
|
|
+ const [r, g, b] = getColorRGB(gameState.player.lightColor);
|
|
|
+
|
|
|
+ return {
|
|
|
+ position: 'absolute',
|
|
|
+ left: (playerPosition.value.x * tileSize.value - tileSize.value-3) + 'px',
|
|
|
+ top: (playerPosition.value.y * tileSize.value - tileSize.value-3) + 'px',
|
|
|
+ width: tileSize.value + 'px',
|
|
|
+ height: tileSize.value + 'px',
|
|
|
+ border: `3px solid rgba(${r}, ${g}, ${b}, 0.9)`,
|
|
|
+ borderRadius: '8px',
|
|
|
+ boxShadow: `0 0 20px 10px rgba(${r}, ${g}, ${b}, 0.8), 0 0 40px 20px rgba(${r}, ${g}, ${b}, 0.4), 0 0 60px 30px rgba(${r}, ${g}, ${b}, 0.2)`,
|
|
|
+ animation: 'v-lightPulse 0.5s infinite',
|
|
|
+ zIndex: 85,
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
+// 计算玩家发光效果样式
|
|
|
+const playerGlowStyle = computed(() => {
|
|
|
+ if (gameState.player.isLightOn) {
|
|
|
+ // 根据颜色名称获取RGB值
|
|
|
+ const getColorRGB = (colorName) => {
|
|
|
+ const colorItem = BLOCKLY_LIGHT_COLOR_DICT.find(item => item.label === colorName);
|
|
|
+ return colorItem ? colorItem.color : null;
|
|
|
+ };
|
|
|
+
|
|
|
+ const [r, g, b] = getColorRGB(gameState.player.lightColor);
|
|
|
+
|
|
|
+ return {
|
|
|
+ filter: `drop-shadow(0 0 10px rgba(${r}, ${g}, ${b}, 0.8)) drop-shadow(0 0 20px rgba(${r}, ${g}, ${b}, 0.5))`,
|
|
|
+ animation: 'v-lightPulse 0.5s infinite',
|
|
|
+ transition: 'filter 0.3s ease-in-out'
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return {};
|
|
|
+});
|
|
|
// 暂停倒计时样式计算
|
|
|
const countdownStyle = computed(() => {
|
|
|
return {
|
|
|
@@ -531,6 +590,7 @@ function generateToolboxXml() {
|
|
|
<block type="pickup_item"></block>
|
|
|
<block type="use_item"></block>
|
|
|
<block type="when_passed"></block>
|
|
|
+ <block type="light"></block>
|
|
|
`;
|
|
|
|
|
|
// 确保blocklySpecialBlocks是数组
|
|
|
@@ -544,17 +604,6 @@ function generateToolboxXml() {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
- // 根据允许的特殊积木动态添加
|
|
|
- // if (specialBlocks.includes(BLOCKLY_MAP_SPECIAL_DICT.PAUSE)) {
|
|
|
- // toolboxXml += '<block type="pause"></block>';
|
|
|
- // }
|
|
|
- // if (specialBlocks.includes(BLOCKLY_MAP_SPECIAL_DICT.PLAY_SOUND)) {
|
|
|
- // toolboxXml += '<block type="play_sound"></block>';
|
|
|
- // }
|
|
|
- // if (specialBlocks.includes(BLOCKLY_MAP_SPECIAL_DICT.CONSTRUCT)) {
|
|
|
- // toolboxXml += '<block type="construct"></block>';
|
|
|
- // }
|
|
|
-
|
|
|
toolboxXml += `
|
|
|
</category>
|
|
|
<category name="逻辑" colour="210">
|
|
|
@@ -903,6 +952,8 @@ const resetPlayer = async () => {
|
|
|
gameState.player.isColliding = false; //碰撞标志
|
|
|
gameState.player.hasReachedEnd = false;
|
|
|
gameState.player.isSliding = false; // 重置滑行状态
|
|
|
+ gameState.player.isLightOn = false; // 重置灯光状态为关闭
|
|
|
+ gameState.player.lightColor = 'white'; // 重置灯光颜色为默认白色
|
|
|
showOverlay.value = false; // 隐藏遮罩层
|
|
|
|
|
|
// 处理多路线情况:重置回初始的第一条路线并清空路线过关标识
|
|
|
@@ -1368,6 +1419,52 @@ window.construct = async function() {
|
|
|
await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
|
|
|
};
|
|
|
|
|
|
+// 灯光效果函数
|
|
|
+window.light = async function(color = 'white') {
|
|
|
+ if (shouldStopExecution || isColliding.value || isSliding.value) {
|
|
|
+ gameState.player.isLightOn = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置灯光颜色和初始状态为开启
|
|
|
+ gameState.player.lightColor = color;
|
|
|
+ gameState.player.isLightOn = true;
|
|
|
+
|
|
|
+ // 定义闪烁周期(毫秒)
|
|
|
+ const blinkInterval = CONFIG.DELAY.BLINK_INTERVAL;
|
|
|
+
|
|
|
+ // 定义闪烁次数(每个完整闪烁包括一次亮和一次暗,共需两个周期)
|
|
|
+ const totalBlinks = CONFIG.DELAY.LIGHT_DURATION / blinkInterval;
|
|
|
+
|
|
|
+ // 循环实现闪烁效果
|
|
|
+ for (let i = 1; i < totalBlinks; i++) {
|
|
|
+ // 检查是否需要停止执行,如果需要则立即退出
|
|
|
+ if (shouldStopExecution) {
|
|
|
+ // 确保灯光关闭
|
|
|
+ gameState.player.isLightOn = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 等待指定的闪烁间隔
|
|
|
+ await new Promise(resolve => setTimeout(resolve, blinkInterval));
|
|
|
+
|
|
|
+ // 再次检查是否需要停止执行
|
|
|
+ if (shouldStopExecution) {
|
|
|
+ // 确保灯光关闭
|
|
|
+ gameState.player.isLightOn = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换灯光状态
|
|
|
+ gameState.player.isLightOn = !gameState.player.isLightOn;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保最终关闭灯光效果
|
|
|
+ gameState.player.isLightOn = false;
|
|
|
+
|
|
|
+ await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
|
|
|
+};
|
|
|
+
|
|
|
// 当经过指定形状时执行的函数(返回布尔值,可作为参数使用)
|
|
|
window.whenPassed = function(shape) {
|
|
|
if (shouldStopExecution || isColliding.value || isSliding.value) {
|
|
|
@@ -2246,6 +2343,24 @@ onUnmounted(() => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/* 灯光闪烁动画 - 注意:由于CSS动画无法直接使用动态颜色,这里保持白色基础动画,动态颜色通过内联样式实现 */
|
|
|
+@keyframes v-lightPulse {
|
|
|
+ 0% {
|
|
|
+ opacity: 1;
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ opacity: 0.0;
|
|
|
+ transform: scale(0.9);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ opacity: 1;
|
|
|
+ box-shadow: 0 0 25px 12px rgba(255, 255, 255, 0.9), 0 0 50px 25px rgba(255, 255, 255, 0.5), 0 0 75px 35px rgba(255, 255, 255, 0.25);
|
|
|
+ border-color: rgba(255, 255, 255, 1);
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/* 彩带容器样式 */
|
|
|
.confetti-container {
|
|
|
position: absolute;
|