Ver Fonte

blockly编程游戏
1、移出向后移动
2、新增拾取、使用积木组件
3、加入拾取后图标跟随人物移动
4、优化移动逻辑每次移动处理特殊方块
5、统一调用撞墙方法
6、配置每个特殊方块的特殊提示和使用

liyanbo há 5 meses atrás
pai
commit
cf369e1f92
1 ficheiros alterados com 331 adições e 152 exclusões
  1. 331 152
      src/views/block/MapGame.vue

+ 331 - 152
src/views/block/MapGame.vue

@@ -34,14 +34,23 @@
                 :key="index"
                 class="walkable-point"
                 :style="getPointStyle(point)"
-            ></div>
+            >
+            </div>
 
             <!-- 玩家角色 -->
             <div
                 class="player"
                 :style="playerStyle"
                 :class="{ 'collision': isColliding, 'success': hasReachedEnd }"
-            ></div>
+            >
+              <!-- 携带的物品 - 移动到玩家内部 -->
+              <div
+                  v-for="(item, index) in gameState.player.carriedItems"
+                  :key="index"
+                  class="carried-item"
+                  :style="getCarriedItemStyle(index, item)"
+              ></div>
+            </div>
           </div>
 
           <!-- 游戏状态提示 -->
@@ -59,10 +68,11 @@
             <!-- 移动控制积木 -->
             <category name="移动控制" colour="%{BKY_MOTION_HUE}">
               <block type="move_forward"></block>
-              <block type="move_backward"></block>
               <block type="turn_left"></block>
               <block type="turn_right"></block>
               <block type="turn_around"></block>
+              <block type="pickup_item"></block>
+              <block type="use_item"></block>
             </category>
 
             <!-- 逻辑控制积木 -->
@@ -115,7 +125,6 @@ import { ArrowLeftBold } from '@element-plus/icons-vue';
 import * as Blockly from "blockly";
 import 'blockly/msg/zh-hans';
 import { javascriptGenerator } from "blockly/javascript";
-// import mapBackgroundImage from '@/assets/images/blockly/mapGame.png';
 import playerImage from '@/assets/images/blockly/user.png';
 
 // 游戏接口数据
@@ -195,7 +204,7 @@ const gameState = reactive({
   // 玩家相关状态
   player: {
     // 玩家当前位置坐标
-    position: { x: 1, y: 1 },
+    position: {},
     // 玩家当前朝向:0=上, 1=右, 2=下, 3=左
     direction: 1,
     // 是否正在发生碰撞
@@ -204,6 +213,8 @@ const gameState = reactive({
     hasReachedEnd: false,
     // 是否正在冰块上滑行
     isSliding: false,
+    // 携带的物品数组
+    carriedItems: [],
   },
 
   // 游戏状态信息
@@ -217,19 +228,18 @@ const gameState = reactive({
   // 地图数据信息
   mapData: {
     // 游戏起点位置
-    startPoint: { x: 1, y: 1 },
+    startPoint: {},
     // 游戏终点位置
-    endPoint: { x: 4, y: 3 },
+    endPoint: {},
     // 地图上所有可行走的点坐标集合,添加type属性区分普通点和冰块
-    walkablePoints: [
-      { x: 1, y: 1, type: 'ice' }, { x: 2, y: 1, type: 'ice' }, { x: 3, y: 1 }, { x: 4, y: 1 },
-      { x: 1, y: 2 }, { x: 2, y: 2 }, { x: 3, y: 2, type: 'ice' }, { x: 4, y: 2 },
-      { x: 1, y: 3 }, { x: 2, y: 3 }, { x: 3, y: 3 }, { x: 4, y: 3 },
-    ],
+    walkablePoints: [],
+    // 保存原始的可行走点数据,用于重置
+    originalWalkablePoints: [],
   }
 });
 const BLOCKLY_MAP_TYPE_DICT = {
   ICE: 'ice',//冰块
+  YC: 'yaoshi',//钥匙
 };
 
 // 计算属性 - 提高性能和可读性
@@ -277,7 +287,6 @@ const tileSize = computed(() => {
   return Math.min(tileWidth, tileHeight);
 });
 
-
 // 生命周期钩子
 onMounted(async () => {
   // 获取游戏数据
@@ -372,8 +381,10 @@ const updateGameStateFromData = (gameData) => {
 function initWalkablePointsSet() {
   walkablePointsMap.clear();
   gameState.mapData.walkablePoints.forEach(point => {
-    walkablePointsMap.set(`${point.x},${point.y}`, point.type);
+    walkablePointsMap.set(`${point.x},${point.y}`, point);
   });
+  // 保存原始的可行走点数据,用于重置
+  gameState.mapData.originalWalkablePoints = JSON.parse(JSON.stringify(gameState.mapData.walkablePoints));
 }
 
 // 可行走检查函数
@@ -381,22 +392,34 @@ function isWalkable(x, y) {
   return walkablePointsMap.has(`${x},${y}`);
 }
 
-// 检查是否是冰块点 - 基于walkablePoints中的type属性
-function isIce(x, y) {
-  return walkablePointsMap.get(`${x},${y}`) === BLOCKLY_MAP_TYPE_DICT.ICE;
-}
-
 // 计算点的样式
 function getPointStyle(point) {
-  return {
+  const style = {
     left: point.x * tileSize.value - tileSize.value + 'px',
     top: point.y * tileSize.value - tileSize.value + 'px',
     width: tileSize.value + 'px',
     height: tileSize.value + 'px',
-    backgroundColor: 'rgba(52, 152, 219, 0.2)',
-    border: '1px solid rgba(52, 152, 219, 0.5)',
-    boxShadow: 'none',
   };
+
+  // 如果point有img属性,则添加图标
+  if (point.img) {
+    const iconSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO;
+    const marginSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_MARGIN;
+
+    // 重置可能影响背景图显示的样式
+    style.backgroundColor = 'transparent';
+    style.backgroundImage = `url(${point.img})`;
+    style.backgroundSize = 'contain';
+    style.backgroundPosition = 'center';
+    style.backgroundRepeat = 'no-repeat';
+
+    // 设置与玩家相同的宽高和边距
+    style.width = iconSize + 'px';
+    style.height = iconSize + 'px';
+    style.margin = marginSize + 'px';
+  }
+
+  return style;
 }
 
 // 计算玩家样式
@@ -411,15 +434,26 @@ const playerStyle = computed(() => ({
   margin: (tileSize.value * CONFIG.STYLES.PLAYER_SIZE_MARGIN) + 'px',
 }));
 
-// 显示游戏消息
-function showGameMessage(message, type = 'info') {
-  gameState.status.message = message;
-  gameState.status.messageType = type;
+// 计算携带物品样式
+function getCarriedItemStyle(index, item) {
+  const baseSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO * 0.3;
+  const baseSize2 = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO * 0.7;
+  console.log(baseSize2, baseSize2 * index)
 
-  // 消息显示时间后自动清除消息
-  setTimeout(() => {
-    gameState.status.message = '';
-  }, CONFIG.DELAY.MESSAGE_DISPLAY);
+  return {
+    position: 'absolute',
+    width: baseSize + 'px',
+    height: baseSize + 'px',
+    backgroundSize: 'contain',
+    backgroundPosition: 'center',
+    backgroundRepeat: 'no-repeat',
+    bottom: index * baseSize2 * 0.5 + 'px',
+    left: baseSize2  + 'px', // 根据索引计算位置,从左到右排列
+    transform: 'none',
+    transformOrigin: 'center',
+    zIndex: -1,
+    backgroundImage: `url(${item.img})`
+  };
 }
 
 // 导航返回
@@ -444,21 +478,6 @@ function registerCustomBlocks() {
     }
   };
 
-  // 向后移动积木
-  Blockly.Blocks['move_backward'] = {
-    init: function() {
-      this.jsonInit({
-        "type": "move_backward",
-        "message0": "向后移动",
-        "previousStatement": null,
-        "nextStatement": null,
-        "colour": 230,
-        "tooltip": "控制角色向后移动一格",
-        "helpUrl": ""
-      });
-    }
-  };
-
   // 向左转积木
   Blockly.Blocks['turn_left'] = {
     init: function() {
@@ -503,6 +522,36 @@ function registerCustomBlocks() {
       });
     }
   };
+
+  // 拾取物品积木
+  Blockly.Blocks['pickup_item'] = {
+    init: function() {
+      this.jsonInit({
+        "type": "pickup_item",
+        "message0": "拾取物品",
+        "previousStatement": null,
+        "nextStatement": null,
+        "colour": 30,
+        "tooltip": "尝试拾取当前位置的物品",
+        "helpUrl": ""
+      });
+    }
+  };
+
+  // 使用物品积木
+  Blockly.Blocks['use_item'] = {
+    init: function() {
+      this.jsonInit({
+        "type": "use_item",
+        "message0": "使用物品",
+        "previousStatement": null,
+        "nextStatement": null,
+        "colour": 30,
+        "tooltip": "在当前位置使用物品",
+        "helpUrl": ""
+      });
+    }
+  };
 }
 
 // 注册JavaScript生成器
@@ -512,11 +561,6 @@ function registerJavaScriptGenerators() {
     return 'await moveForward();\n';
   };
 
-  // 向后移动生成器
-  javascriptGenerator.forBlock['move_backward'] = function(block) {
-    return 'await moveBackward();\n';
-  };
-
   // 向左转生成器
   javascriptGenerator.forBlock['turn_left'] = function(block) {
     return 'await turnLeft();\n';
@@ -532,6 +576,16 @@ function registerJavaScriptGenerators() {
     return 'await turnAround();\n';
   };
 
+  // 拾取物品生成器
+  javascriptGenerator.forBlock['pickup_item'] = function(block) {
+    return 'await pickupItem();\n';
+  };
+
+  // 使用物品生成器
+  javascriptGenerator.forBlock['use_item'] = function(block) {
+    return 'await useItem();\n';
+  };
+
   // 为重复循环块注册自定义生成器,确保支持异步操作
   javascriptGenerator.forBlock['controls_repeat_ext'] = function(block) {
     const repeats = javascriptGenerator.valueToCode(block, 'TIMES', javascriptGenerator.ORDER_ATOMIC) || '0';
@@ -593,6 +647,8 @@ function initBlockly() {
     trashcan: true,
     horizontalLayout: false,
     toolboxPosition: 'start',
+    toolboxCollapse: false, // 设置工具箱默认展开
+    toolboxAlwaysExpanded: true, // 确保点击类别后保持展开状态
     css: true,
     media: 'https://unpkg.com/blockly/media/',
     rtl: false,
@@ -641,7 +697,7 @@ async function smoothMoveTo(targetX, targetY) {
 
       gameState.player = {
         ...gameState.player,
-        position: { x: currentX, y: currentY }
+        position: { x: currentX, y: currentY },
       };
 
       // 检查是否到达终点
@@ -658,111 +714,85 @@ async function smoothMoveTo(targetX, targetY) {
   });
 }
 
-// 创建通用的移动函数
-async function move(direction) {
-  if (shouldStopExecution || isColliding.value || isSliding.value) {
-    return;
-  }
-
-  let newX = playerPosition.value.x;
-  let newY = playerPosition.value.y;
+// 处理冰块滑行逻辑(根据人物当前位置判断)
+async function switchMapType(isMapType = null) {
+  //取人物当前位置
+  let x = playerPosition.value.x;
+  let y = playerPosition.value.y;
+  let tileMap = walkablePointsMap.get(`${x},${y}`);
 
-  // 根据当前方向和移动类型计算新位置
-  if (direction === 1) {
-    // 向前移动
-    switch(playerDirection.value) {
-      case CONFIG.GAME.DIRECTIONS.UP: newY--; break;
-      case CONFIG.GAME.DIRECTIONS.RIGHT: newX++; break;
-      case CONFIG.GAME.DIRECTIONS.DOWN: newY++; break;
-      case CONFIG.GAME.DIRECTIONS.LEFT: newX--; break;
-    }
-  } else {
-    // 向后移动
-    switch(playerDirection.value) {
-      case CONFIG.GAME.DIRECTIONS.UP: newY++; break;
-      case CONFIG.GAME.DIRECTIONS.RIGHT: newX--; break;
-      case CONFIG.GAME.DIRECTIONS.DOWN: newY--; break;
-      case CONFIG.GAME.DIRECTIONS.LEFT: newX++; break;
-    }
+  //判断是否是指定地图类型
+  if (isMapType) {
+    return isMapType === tileMap.type;
   }
 
-  // 检查是否可以移动
-  if (isWalkable(newX, newY)) {
-    // 使用平滑移动动画
-    await smoothMoveTo(newX, newY);
-
-    // 检查新位置是否是冰块,如果是则触发滑行
-    if (isIce(newX, newY)) {
-      await handleIceSliding();
-    }
-
-    await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
-  } else {
-    // 发生碰撞
-    gameState.player.isColliding = true;
-    showGameMessage(CONFIG.TIPS.NO_ENTRY, 'error');
-
-    // 立即中止整个代码执行
-    if (executionAbortController) {
-      executionAbortController.abort();
-    }
-
-    // 碰撞状态重置时间后取消碰撞状态
-    setTimeout(() => {
-      gameState.player.isColliding = false;
-    }, CONFIG.DELAY.COLLISION_RESET);
-
-    // 添加碰撞延迟
-    await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.COLLISION_DELAY));
+  //判断方块类型并处理逻辑
+  switch (tileMap.type) {
+    case BLOCKLY_MAP_TYPE_DICT.ICE:
+      do {
+        showGameMessage(tileMap.tip, 'warning',300)
+        await handleIceSliding();
+
+        tileMap = walkablePointsMap.get(`${playerPosition.value.x},${playerPosition.value.y}`);
+      }while (tileMap.type === BLOCKLY_MAP_TYPE_DICT.ICE && !isColliding.value)
+      break;
+    case BLOCKLY_MAP_TYPE_DICT.YC:
+      // showGameMessage(tileMap.tip, 'warning', 1000)
+      // // 处理携带物品逻辑
+      // if (tileMap && tileMap.img) {
+      //   // 将物品添加到玩家携带物品中
+      //   gameState.player.carriedItems.push({
+      //     ...tileMap,
+      //     originalX: x,
+      //     originalY: y
+      //   });
+      //
+      //   // 从地图上移除图标(但保留点的可通行性)
+      //   const pointIndex = gameState.mapData.walkablePoints.findIndex(
+      //       p => p.x === x && p.y === y
+      //   );
+      //   if (pointIndex !== -1) {
+      //     // 保留点但移除img属性
+      //     const updatedPoint = { ...gameState.mapData.walkablePoints[pointIndex] };
+      //     delete updatedPoint.img;
+      //     gameState.mapData.walkablePoints.splice(pointIndex, 1, updatedPoint);
+      //     // 更新映射
+      //     walkablePointsMap.set(`${x},${y}`, updatedPoint);
+      //   }
+      // }
+
+      break;
   }
 }
 
 // 处理冰块滑行逻辑
 async function handleIceSliding() {
+  if (shouldStopExecution || isColliding.value) {
+    return;
+  }
   gameState.player.isSliding = true;
 
   try {
-    // 循环检查是否可以继续滑行
-    while (true) {
-      // 计算下一个位置
-      let nextX = playerPosition.value.x;
-      let nextY = playerPosition.value.y;
-
-      // 根据当前方向计算下一个位置
-      switch(playerDirection.value) {
-        case CONFIG.GAME.DIRECTIONS.UP: nextY--; break;
-        case CONFIG.GAME.DIRECTIONS.RIGHT: nextX++; break;
-        case CONFIG.GAME.DIRECTIONS.DOWN: nextY++; break;
-        case CONFIG.GAME.DIRECTIONS.LEFT: nextX--; break;
-      }
-
-      // 检查下一个位置是否可行走
-      if (isWalkable(nextX, nextY)) {
-        // 执行平滑移动到下一个位置
-        await smoothMoveTo(nextX, nextY);
-
-        // 检查下一个位置是否还是冰块
-        if (!isIce(nextX, nextY)) {
-          // 如果不是冰块,结束滑行
-          break;
-        }
-      } else {
-        // 下一个位置不可行走,检查是否是墙体
-        gameState.player.isColliding = true;
-        showGameMessage(CONFIG.TIPS.NO_ENTRY, 'error');
+    // 计算下一个位置
+    let nextX = playerPosition.value.x;
+    let nextY = playerPosition.value.y;
 
-        // 立即中止整个代码执行
-        if (executionAbortController) {
-          executionAbortController.abort();
-        }
+    // 根据当前方向计算下一个位置
+    switch(playerDirection.value) {
+      case CONFIG.GAME.DIRECTIONS.UP: nextY--; break;
+      case CONFIG.GAME.DIRECTIONS.RIGHT: nextX++; break;
+      case CONFIG.GAME.DIRECTIONS.DOWN: nextY++; break;
+      case CONFIG.GAME.DIRECTIONS.LEFT: nextX--; break;
+    }
 
-        // 碰撞状态重置时间后取消碰撞状态
-        setTimeout(() => {
-          gameState.player.isColliding = false;
-        }, CONFIG.DELAY.COLLISION_RESET);
+    // 检查下一个位置是否可行走
+    if (isWalkable(nextX, nextY)) {
+      // 执行平滑移动到下一个位置
+      await smoothMoveTo(nextX, nextY);
 
-        break;
-      }
+    } else {
+      // 使用统一的碰撞处理方法,但不等待延迟(因为handleIceSliding不需要这个延迟)
+      await handleWallCollision();
     }
   } catch (error) {
     console.error('滑行过程中发生错误:', error);
@@ -772,13 +802,114 @@ async function handleIceSliding() {
   }
 }
 
-// 保留原有的API接口
+// 拾取物品函数
+window.pickupItem = async function()  {
+  //取人物当前位置
+  let x = playerPosition.value.x;
+  let y = playerPosition.value.y;
+  let tileMap = walkablePointsMap.get(`${x},${y}`);
+
+  // 判断是否是要拾取的方块类型
+  if (tileMap && tileMap.type === BLOCKLY_MAP_TYPE_DICT.YC) {
+    showGameMessage(tileMap.tip || '成功拾取物品!', 'warning', 1000)
+
+    // 处理携带物品逻辑
+    if (tileMap && tileMap.img) {
+      // 将物品添加到玩家携带物品中
+      gameState.player.carriedItems.push({
+        ...tileMap,
+        originalX: x,
+        originalY: y
+      });
+
+      // 从地图上移除图标(但保留点的可通行性)
+      const pointIndex = gameState.mapData.walkablePoints.findIndex(
+          p => p.x === x && p.y === y
+      );
+      if (pointIndex !== -1) {
+        // 保留点但移除img属性
+        const updatedPoint = { ...gameState.mapData.walkablePoints[pointIndex] };
+        delete updatedPoint.img;
+        gameState.mapData.walkablePoints.splice(pointIndex, 1, updatedPoint);
+        // 更新映射
+        walkablePointsMap.set(`${x},${y}`, updatedPoint);
+      }
+    }
+  } else {
+    showGameMessage('当前位置没有可拾取的物品', 'info', 1000)
+  }
+}
+
+// 使用物品函数
+window.useItem = async function() {
+  //取人物当前位置
+  let x = playerPosition.value.x;
+  let y = playerPosition.value.y;
+  let tileMap = walkablePointsMap.get(`${x},${y}`);
+
+  // 判断当前位置是否有特殊需求
+  if (tileMap && tileMap.need) {
+    // 检查玩家是否携带了需要的物品
+    const requiredItems = tileMap.need;
+    let hasRequiredItem = false;
+    let itemIndex = -1;
+
+    // 检查玩家携带的物品中是否有满足需求的物品
+    for (let i = 0; i < gameState.player.carriedItems.length; i++) {
+      const item = gameState.player.carriedItems[i];
+      if (requiredItems.includes(item.type)) {
+        hasRequiredItem = true;
+        itemIndex = i;
+        break;
+      }
+    }
+
+    if (hasRequiredItem) {
+      // 使用物品成功
+      showGameMessage(tileMap.tip || '物品使用成功!', 'success', 1500);
+
+      // 从携带物品中移除已使用的物品
+      gameState.player.carriedItems.splice(itemIndex, 1);
+
+    } else {
+      // 提示缺少所需物品
+      showGameMessage(tileMap.tip || `需要特殊物品才能使用`, 'warning', 1500);
+    }
+  } else {
+    showGameMessage('当前位置不需要使用物品', 'info', 1000);
+  }
+}
+
+// 向前移动
 window.moveForward = async function() {
-  await move(1);
-};
+  if (shouldStopExecution || isColliding.value || isSliding.value) {
+    return;
+  }
 
-window.moveBackward = async function() {
-  await move(-1);
+  let newX = playerPosition.value.x;
+  let newY = playerPosition.value.y;
+
+  // 向前移动
+  switch(playerDirection.value) {
+    case CONFIG.GAME.DIRECTIONS.UP: newY--; break;
+    case CONFIG.GAME.DIRECTIONS.RIGHT: newX++; break;
+    case CONFIG.GAME.DIRECTIONS.DOWN: newY++; break;
+    case CONFIG.GAME.DIRECTIONS.LEFT: newX--; break;
+  }
+
+  // 检查是否可以移动
+  if (isWalkable(newX, newY)) {
+    // 使用平滑移动动画
+    await smoothMoveTo(newX, newY);
+
+    // 处理方块类型逻辑
+    await switchMapType();
+
+    await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
+  } else {
+    // 发生碰撞 使用统一的碰撞处理方法
+    await handleWallCollision();
+  }
 };
 
 //向左转(逆时针旋转90度)
@@ -1039,6 +1170,15 @@ const resetPlayer = () => {
     currentExecutionPromise = null;
   }
 
+  // 重置携带的物品回地图
+  if (gameState.mapData.originalWalkablePoints.length > 0) {
+    gameState.mapData.walkablePoints = JSON.parse(JSON.stringify(gameState.mapData.originalWalkablePoints));
+  }
+  // 清空携带物品
+  gameState.player.carriedItems = [];
+  // 重新初始化可行走点集合
+  initWalkablePointsSet();
+
   gameState.player.position = { ...startPoint.value };
   gameState.player.direction = playerInitialDirection.value; // 重置为初始方向
   gameState.player.isColliding = false; //碰撞标志
@@ -1065,6 +1205,39 @@ function updateMapContainerDimensions() {
       };
     }
   }
+};
+
+
+// 显示游戏消息
+function showGameMessage(message, type = 'info', duration = CONFIG.DELAY.MESSAGE_DISPLAY) {
+  gameState.status.message = message;
+  gameState.status.messageType = type;
+
+  // 消息显示时间后自动清除消息
+  setTimeout(() => {
+    gameState.status.message = '';
+  }, duration);
+}
+
+// 统一处理撞到墙时的停止逻辑
+async function handleWallCollision() {
+  // 设置碰撞状态
+  gameState.player.isColliding = true;
+  // 显示错误消息
+  showGameMessage(CONFIG.TIPS.NO_ENTRY, 'error');
+
+  // 立即中止整个代码执行
+  if (executionAbortController) {
+    executionAbortController.abort();
+  }
+
+  // 碰撞状态重置时间后取消碰撞状态
+  setTimeout(() => {
+    gameState.player.isColliding = false;
+  }, CONFIG.DELAY.COLLISION_RESET);
+
+  // 返回一个Promise,允许调用者等待碰撞延迟
+  return new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.COLLISION_DELAY));
 }
 
 // 组件卸载时清理
@@ -1255,11 +1428,11 @@ onUnmounted(() => {
 /* 可行走区域样式 */
 .walkable-point {
   position: absolute;
-  background-color: rgba(52, 152, 219, 0.2);
-  border: 1px solid rgba(52, 152, 219, 0.5);
-  box-sizing: border-box;
+  //background-color: rgba(52, 152, 219, 0.2);
+  //border: 1px solid rgba(52, 152, 219, 0.5);
+  //box-sizing: border-box;
   //是否显示可行路线
-  opacity: 0;
+  opacity: 1;
 }
 
 /* 玩家样式 */
@@ -1345,6 +1518,12 @@ onUnmounted(() => {
   border: 1px solid #bee5eb;
 }
 
+.game-message.warning {
+  background-color: #baeff8;
+  color: #035767;
+  border: 1px solid #9be9f6;
+}
+
 /* Blockly区域样式 */
 .blockly-section {
   flex: 1;