Bläddra i källkod

1、优化编程课声音组件改成可选音效
2、加入灯光组件参数选择灯光颜色,控制小智和小智所在的方格进行边框闪烁

liyanbo 5 månader sedan
förälder
incheckning
409825919c

+ 70 - 1
src/api/blockly/blockly.js

@@ -13,6 +13,10 @@ import useImage from '@/assets/images/blockly/component/use.png';
 import pauseImage from '@/assets/images/blockly/component/pause.png';
 import play_soundImage from '@/assets/images/blockly/component/sound.png';
 import constructImage from '@/assets/images/blockly/component/construct.png';
+import lightImage from '@/assets/images/blockly/component/light.png';
+import lightRedImage from '@/assets/images/blockly/light/red.png';
+import lightYellowImage from '@/assets/images/blockly/light/yellow.png';
+import lightGreenImage from '@/assets/images/blockly/light/green.png';
 import {DICT_TYPE} from '@/utils/dictUtils.js';
 
 
@@ -51,6 +55,26 @@ const BLOCKLY_MAP_MARK_DICT = blocklyMapMarkDict.reduce((acc, item) => {
   return acc;
 }, {});
 
+// 灯光颜色字典
+const BLOCKLY_LIGHT_COLOR_DICT = [
+  {
+    label: '红色',
+    color: [233, 77, 88],
+    img: lightRedImage,
+  },
+  {
+    label: '黄色',
+    color: [233, 211, 36],
+    img: lightYellowImage,
+  },
+  {
+    label: '绿色',
+    color: [118, 187, 45],
+    img: lightGreenImage,
+  },
+];
+
+
 /**
  * 定义所有可用的自定义积木配置
  */
@@ -367,6 +391,40 @@ const availableBlocks = {
       "helpUrl": ""
     },
     isGeneral: false
+  },
+  light: {
+    jsonConfig: {
+      "type": "light",
+      "message0": "%1 灯光 %2",
+      "args0": [
+        {
+          "type": "field_image",
+          "src": lightImage,
+          "width": 25,
+          "height": 25,
+          "alt": "灯光"
+        },
+        {
+          "type": "field_dropdown",
+          "name": "COLOR",
+          "options": BLOCKLY_LIGHT_COLOR_DICT.map(item => [
+            {
+              "src": item.img,
+              "width": 20,
+              "height": 20,
+              "alt": item.label
+            },
+            item.label
+          ])
+        }
+      ],
+      "previousStatement": null,
+      "nextStatement": null,
+      "colour": 30,
+      "tooltip": "在当前位置产生灯光效果",
+      "helpUrl": ""
+    },
+    isGeneral: true
   }
 };
 
@@ -420,6 +478,11 @@ const availableGenerators = {
   },
   construct: function(block) {
     return 'await construct();\n';
+  },
+  light: function(block) {
+    const color = block.getFieldValue('COLOR');
+    return `await light('${color}');
+`;
   }
 };
 
@@ -473,6 +536,11 @@ const availablePythonGenerators = {
   },
   construct: function(block) {
     return 'construct()\n';
+  },
+  light: function(block) {
+    const color = block.getFieldValue('COLOR');
+    return `light('${color}')
+`;
   }
 };
 
@@ -703,7 +771,8 @@ export function getAllPythonGeneratorsConfig() {
 }
 
 // 导出字典常量
-export {  BLOCKLY_MAP_TYPE_DICT, BLOCKLY_MAP_MARK_DICT, BLOCKLY_MAP_SPECIAL_DICT };
+export {  BLOCKLY_MAP_TYPE_DICT, BLOCKLY_MAP_MARK_DICT, BLOCKLY_MAP_SPECIAL_DICT,
+  BLOCKLY_LIGHT_COLOR_DICT };
 
 export default {
   registerCustomBlocks,

BIN
src/assets/images/blockly/component/light.png


BIN
src/assets/images/blockly/light/green.png


BIN
src/assets/images/blockly/light/red.png


BIN
src/assets/images/blockly/light/yellow.png


+ 127 - 12
src/components/blockly/MapGame.vue

@@ -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;