|
|
@@ -22,7 +22,7 @@
|
|
|
<p v-html="currentGameData?.info"></p>
|
|
|
</div>
|
|
|
</div>
|
|
|
-
|
|
|
+
|
|
|
<div class="map-container">
|
|
|
<!-- 地图背景 -->
|
|
|
<div class="map-background">
|
|
|
@@ -121,104 +121,58 @@ import playerImage from '@/assets/images/blockly/user.png';
|
|
|
// 游戏接口数据
|
|
|
import { getMapGameById } from '@/api/blockly/game.js';
|
|
|
|
|
|
+// 配置常量
|
|
|
+const CONFIG = {
|
|
|
+ // 动画时长配置(毫秒)
|
|
|
+ ANIMATION: {
|
|
|
+ MOVE_DURATION: 500, // 移动动画持续时间
|
|
|
+ ROTATE_DURATION: 500, // 旋转动画持续时间(左转/右转)
|
|
|
+ TURN_AROUND_DURATION: 750, // 向后转动画持续时间
|
|
|
+ },
|
|
|
+ // 延迟配置(毫秒)
|
|
|
+ DELAY: {
|
|
|
+ ACTION_DELAY: 200, // 每次动作后的延迟时间
|
|
|
+ COLLISION_DELAY: 300, // 碰撞后的延迟时间
|
|
|
+ RESET_DELAY: 300, // 重置后的延迟时间
|
|
|
+ MESSAGE_DISPLAY: 3000, // 消息显示时间
|
|
|
+ COLLISION_RESET: 1000, // 碰撞状态重置时间
|
|
|
+ LOOP_PREVENTION: 10, // 循环防止UI阻塞的延迟
|
|
|
+ },
|
|
|
+ // 游戏配置
|
|
|
+ GAME: {
|
|
|
+ MAX_LOOP_COUNT: 100, // 最大循环次数
|
|
|
+ DIRECTIONS: {
|
|
|
+ UP: 0,
|
|
|
+ RIGHT: 1,
|
|
|
+ DOWN: 2,
|
|
|
+ LEFT: 3
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 样式配置
|
|
|
+ STYLES: {
|
|
|
+ DEFAULT_TILE_SIZE: 110, // 默认瓦片大小
|
|
|
+ PLAYER_SIZE_RATIO: 0.8 // 玩家大小占瓦片的比例
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
+// 路由和游戏状态
|
|
|
const router = useRouter();
|
|
|
const route = useRoute();
|
|
|
const gameTitle = ref('地图游戏编程'); // 默认标题
|
|
|
-const gameSort = ref('Game1'); // 默认排序
|
|
|
const currentGameData = ref(null);
|
|
|
-//人物初始朝向
|
|
|
-const playerInitialDirection = ref(0);
|
|
|
-
|
|
|
-onMounted(async () => {
|
|
|
- //数据
|
|
|
- await fetchGameData();
|
|
|
-
|
|
|
- // 初始化可行走点集合
|
|
|
- initWalkablePointsSet();
|
|
|
- // 注册自定义积木
|
|
|
- registerCustomBlocks();
|
|
|
- // 注册JavaScript生成器
|
|
|
- registerJavaScriptGenerators();
|
|
|
- // 初始化Blockly工作区
|
|
|
- initBlockly();
|
|
|
- // 重置玩家位置
|
|
|
- resetPlayer();
|
|
|
-
|
|
|
- // 获取路由参数中的gameName
|
|
|
- const nameFromRoute = route.query.gameName;
|
|
|
- if (nameFromRoute) {
|
|
|
- gameTitle.value = nameFromRoute;
|
|
|
- }
|
|
|
-})
|
|
|
-
|
|
|
-// 获取游戏数据
|
|
|
-const fetchGameData = async () => {
|
|
|
- try {
|
|
|
- const gameId = route.query.gameId;
|
|
|
- const nameFromRoute = route.query.gameName;
|
|
|
- const sortFromRoute = route.query.gameSort;
|
|
|
-
|
|
|
- if (nameFromRoute) {
|
|
|
- gameTitle.value = nameFromRoute;
|
|
|
- }
|
|
|
-
|
|
|
- // 优先使用路由参数中的排序信息
|
|
|
- if (sortFromRoute) {
|
|
|
- gameSort.value = sortFromRoute;
|
|
|
- }
|
|
|
-
|
|
|
- let mapGameData = await getMapGameById(gameId);
|
|
|
- if (mapGameData.code === 0) {
|
|
|
- currentGameData.value = mapGameData?.data;
|
|
|
-
|
|
|
- // 使用接口数据
|
|
|
- if (currentGameData.value && currentGameData.value.sort) {
|
|
|
- let sortNum = currentGameData.value.sort;
|
|
|
- gameSort.value = sortNum > 9 ? `Game${sortNum}` : `Game${sortNum}`;
|
|
|
- }
|
|
|
- updateGameStateFromData(currentGameData.value);
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('获取游戏数据失败:', error);
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-// 根据获取到的数据更新游戏状态
|
|
|
-const updateGameStateFromData = (gameData) => {
|
|
|
- try {
|
|
|
- // 更新地图配置
|
|
|
- gameState.mapConfig.background = gameData.mapBackground ? gameData.mapBackground.trim() : mapBackgroundImage;
|
|
|
- gameState.mapConfig.tileSize = gameData.mapTileSize;
|
|
|
-
|
|
|
- // 更新玩家位置和方向
|
|
|
- if (gameData.userDirection) {
|
|
|
- playerInitialDirection.value = gameData.userDirection;
|
|
|
- }
|
|
|
+const playerInitialDirection = ref(0); // 人物初始朝向
|
|
|
+const gameSort = ref('Game1'); // 默认排序
|
|
|
|
|
|
- // 更新地图数据
|
|
|
- // 地图起点
|
|
|
- if (gameData.mapStartPoint) {
|
|
|
- const startPoint = JSON.parse(gameData.mapStartPoint);
|
|
|
- gameState.mapData.startPoint = { x: startPoint.x, y: startPoint.y };
|
|
|
- gameState.player.position = { x: startPoint.x, y: startPoint.y };
|
|
|
- }
|
|
|
- // 地图终点
|
|
|
- if (gameData.mapEndPoint) {
|
|
|
- const endPoint = JSON.parse(gameData.mapEndPoint);
|
|
|
- gameState.mapData.endPoint = { x: endPoint.x, y: endPoint.y };
|
|
|
- }
|
|
|
- if (gameData.mapWalkablePoints) {
|
|
|
- gameState.mapData.walkablePoints = JSON.parse(gameData.mapWalkablePoints);
|
|
|
- }
|
|
|
- // 重新初始化可行走点集合
|
|
|
- initWalkablePointsSet();
|
|
|
- } catch (error) {
|
|
|
- console.error('更新游戏状态失败:', error);
|
|
|
- }
|
|
|
-};
|
|
|
+// 运行控制标志
|
|
|
+let shouldStopExecution = false;
|
|
|
+let currentExecutionPromise = null;
|
|
|
+let executionAbortController = null;
|
|
|
|
|
|
+// Blockly相关状态
|
|
|
+let workspace = null;
|
|
|
|
|
|
+// 使用 Map 存储可行走点及其类型,提高查询效率
|
|
|
+let walkablePointsMap = new Map();
|
|
|
|
|
|
// 创建游戏状态的响应式对象
|
|
|
const gameState = reactive({
|
|
|
@@ -227,7 +181,7 @@ const gameState = reactive({
|
|
|
// 地图背景图片路径
|
|
|
background: mapBackgroundImage,
|
|
|
// 每个瓦片的尺寸(像素)
|
|
|
- tileSize: 110,
|
|
|
+ tileSize: CONFIG.STYLES.DEFAULT_TILE_SIZE,
|
|
|
},
|
|
|
|
|
|
// 玩家相关状态
|
|
|
@@ -263,7 +217,6 @@ const gameState = reactive({
|
|
|
{ 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 },
|
|
|
- { x: 4, y: 3 },
|
|
|
],
|
|
|
}
|
|
|
});
|
|
|
@@ -280,6 +233,7 @@ const isColliding = computed(() => gameState.player.isColliding);
|
|
|
const hasReachedEnd = computed(() => gameState.player.hasReachedEnd);
|
|
|
const gameMessage = computed(() => gameState.status.message);
|
|
|
const messageType = computed(() => gameState.status.messageType);
|
|
|
+const isSliding = computed(() => gameState.player.isSliding);
|
|
|
|
|
|
// 计算玩家图片路径,优先使用接口数据
|
|
|
const playerImageSrc = computed(() => {
|
|
|
@@ -288,14 +242,87 @@ const playerImageSrc = computed(() => {
|
|
|
}
|
|
|
return playerImage;
|
|
|
});
|
|
|
-const isSliding = computed(() => gameState.player.isSliding);
|
|
|
|
|
|
+// 生命周期钩子
|
|
|
+onMounted(async () => {
|
|
|
+ // 获取游戏数据
|
|
|
+ await fetchGameData();
|
|
|
+ // 初始化可行走点集合
|
|
|
+ initWalkablePointsSet();
|
|
|
+ // 注册自定义积木
|
|
|
+ registerCustomBlocks();
|
|
|
+ // 注册JavaScript生成器
|
|
|
+ registerJavaScriptGenerators();
|
|
|
+ // 初始化Blockly工作区
|
|
|
+ initBlockly();
|
|
|
+ // 重置玩家位置
|
|
|
+ resetPlayer();
|
|
|
+});
|
|
|
|
|
|
-// Blockly相关状态
|
|
|
-let workspace = null;
|
|
|
+// 获取游戏数据
|
|
|
+const fetchGameData = async () => {
|
|
|
+ try {
|
|
|
+ const gameId = route.query.gameId;
|
|
|
+ const nameFromRoute = route.query.gameName;
|
|
|
+ const sortFromRoute = route.query.gameSort;
|
|
|
|
|
|
-// 使用 Map 存储可行走点及其类型,提高查询效率
|
|
|
-let walkablePointsMap = new Map();
|
|
|
+ if (nameFromRoute) {
|
|
|
+ gameTitle.value = nameFromRoute;
|
|
|
+ }
|
|
|
+ if (sortFromRoute) {
|
|
|
+ gameSort.value = sortFromRoute;
|
|
|
+ }
|
|
|
+
|
|
|
+ let mapGameData = await getMapGameById(gameId);
|
|
|
+ if (mapGameData.code === 0) {
|
|
|
+ currentGameData.value = mapGameData?.data;
|
|
|
+
|
|
|
+ // 使用接口数据
|
|
|
+ if (currentGameData.value && currentGameData.value.sort) {
|
|
|
+ let sortNum = currentGameData.value.sort;
|
|
|
+ gameSort.value = sortNum > 9 ? `Game${sortNum}` : `Game${sortNum}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ await updateGameStateFromData(currentGameData.value);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取游戏数据失败:', error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 根据获取到的数据更新游戏状态
|
|
|
+const updateGameStateFromData = (gameData) => {
|
|
|
+ try {
|
|
|
+ // 更新地图配置
|
|
|
+ gameState.mapConfig.background = gameData.mapBackground ? gameData.mapBackground.trim() : mapBackgroundImage;
|
|
|
+ gameState.mapConfig.tileSize = gameData.mapTileSize || CONFIG.STYLES.DEFAULT_TILE_SIZE;
|
|
|
+
|
|
|
+ // 更新玩家方向
|
|
|
+ if (gameData.userDirection) {
|
|
|
+ playerInitialDirection.value = gameData.userDirection;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新地图数据
|
|
|
+ // 地图起点
|
|
|
+ if (gameData.mapStartPoint) {
|
|
|
+ const startPoint = JSON.parse(gameData.mapStartPoint);
|
|
|
+ gameState.mapData.startPoint = { x: startPoint.x, y: startPoint.y };
|
|
|
+ gameState.player.position = { x: startPoint.x, y: startPoint.y };
|
|
|
+ }
|
|
|
+ // 地图终点
|
|
|
+ if (gameData.mapEndPoint) {
|
|
|
+ const endPoint = JSON.parse(gameData.mapEndPoint);
|
|
|
+ gameState.mapData.endPoint = { x: endPoint.x, y: endPoint.y };
|
|
|
+ }
|
|
|
+ if (gameData.mapWalkablePoints) {
|
|
|
+ gameState.mapData.walkablePoints = JSON.parse(gameData.mapWalkablePoints);
|
|
|
+ }
|
|
|
+ // 重新初始化可行走点集合
|
|
|
+ initWalkablePointsSet();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('更新游戏状态失败:', error);
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
// 初始化可行走点映射
|
|
|
function initWalkablePointsSet() {
|
|
|
@@ -337,9 +364,9 @@ const playerStyle = computed(() => ({
|
|
|
transform: `rotate(${playerDirection.value * 90}deg)`,
|
|
|
'--player-rotation': `${playerDirection.value * 90}deg`,
|
|
|
'--player-image': `url(${playerImageSrc.value})`,
|
|
|
- width: (tileSize.value * 0.8) + 'px',
|
|
|
- height: (tileSize.value * 0.8) + 'px',
|
|
|
- margin: (tileSize.value * 0.1) + 'px',
|
|
|
+ width: (tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO) + 'px',
|
|
|
+ height: (tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO) + 'px',
|
|
|
+ margin: (tileSize.value * (1 - CONFIG.STYLES.PLAYER_SIZE_RATIO) / 2) + 'px',
|
|
|
}));
|
|
|
|
|
|
// 显示游戏消息
|
|
|
@@ -347,10 +374,10 @@ function showGameMessage(message, type = 'info') {
|
|
|
gameState.status.message = message;
|
|
|
gameState.status.messageType = type;
|
|
|
|
|
|
- // 3秒后自动清除消息
|
|
|
+ // 消息显示时间后自动清除消息
|
|
|
setTimeout(() => {
|
|
|
gameState.status.message = '';
|
|
|
- }, 3000);
|
|
|
+ }, CONFIG.DELAY.MESSAGE_DISPLAY);
|
|
|
}
|
|
|
|
|
|
// 导航返回
|
|
|
@@ -494,11 +521,11 @@ function registerJavaScriptGenerators() {
|
|
|
// 修复变量作用域问题,使用IIFE包装循环
|
|
|
let code = '(async function() {\n';
|
|
|
code += ' let loopCount = 0;\n';
|
|
|
- code += until ? ' while (!((' + condition + ')) && loopCount < 100) {\n' :
|
|
|
- ' while (((' + condition + ')) && loopCount < 100) {\n';
|
|
|
+ code += until ? ' while (!((' + condition + ')) && loopCount < ' + CONFIG.GAME.MAX_LOOP_COUNT + ') {\n' :
|
|
|
+ ' while (((' + condition + ')) && loopCount < ' + CONFIG.GAME.MAX_LOOP_COUNT + ') {\n';
|
|
|
code += javascriptGenerator.prefixLines(branch, javascriptGenerator.INDENT + ' ');
|
|
|
code += ' loopCount++';
|
|
|
- code += ' await new Promise(resolve => setTimeout(resolve, 10));\n'; // 防止UI阻塞
|
|
|
+ code += ' await new Promise(resolve => setTimeout(resolve, ' + CONFIG.DELAY.LOOP_PREVENTION + '));\n'; // 防止UI阻塞
|
|
|
code += ' }\n';
|
|
|
code += '})();\n';
|
|
|
|
|
|
@@ -551,44 +578,47 @@ function initBlockly() {
|
|
|
async function smoothMoveTo(targetX, targetY) {
|
|
|
const startX = playerPosition.value.x;
|
|
|
const startY = playerPosition.value.y;
|
|
|
- const duration = 500; // 移动动画持续时间(毫秒)
|
|
|
+
|
|
|
const startTime = performance.now();
|
|
|
|
|
|
- // 使用 requestAnimationFrame 实现平滑动画
|
|
|
+ // 使用Promise包装动画过程,使其可以await
|
|
|
return new Promise(resolve => {
|
|
|
function animate(currentTime) {
|
|
|
- const elapsedTime = currentTime - startTime;
|
|
|
-
|
|
|
- // 计算进度,确保不会超过1
|
|
|
- const progress = Math.min(elapsedTime / duration, 1);
|
|
|
+ // 检查是否应该停止执行
|
|
|
+ if (shouldStopExecution) {
|
|
|
+ resolve();
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- // 使用缓动函数使动画更自然
|
|
|
- const easedProgress = progress * (2 - progress); // easeOutQuad 缓动
|
|
|
+ const elapsed = currentTime - startTime;
|
|
|
+ const progress = Math.min(elapsed / CONFIG.ANIMATION.MOVE_DURATION, 1); // 计算进度,最大为1
|
|
|
|
|
|
- // 计算当前位置
|
|
|
- const currentX = startX + (targetX - startX) * easedProgress;
|
|
|
- const currentY = startY + (targetY - startY) * easedProgress;
|
|
|
+ // 线性插值计算当前位置
|
|
|
+ const currentX = startX + (targetX - startX) * progress;
|
|
|
+ const currentY = startY + (targetY - startY) * progress;
|
|
|
|
|
|
- // 更新玩家位置
|
|
|
- gameState.player.position = { x: currentX, y: currentY };
|
|
|
+ gameState.player = {
|
|
|
+ ...gameState.player,
|
|
|
+ position: { x: currentX, y: currentY }
|
|
|
+ };
|
|
|
|
|
|
- // 如果动画未完成,继续下一帧
|
|
|
+ // 检查是否到达终点
|
|
|
if (progress < 1) {
|
|
|
+ // 继续动画
|
|
|
requestAnimationFrame(animate);
|
|
|
} else {
|
|
|
- // 动画完成后解析Promise
|
|
|
- resolve();
|
|
|
+ resolve(); // 动画完成
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 开始动画
|
|
|
+ // 启动动画
|
|
|
requestAnimationFrame(animate);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 创建通用的移动函数
|
|
|
async function move(direction) {
|
|
|
- if (isColliding.value || isSliding.value) {
|
|
|
+ if (shouldStopExecution || isColliding.value || isSliding.value) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -599,18 +629,18 @@ async function move(direction) {
|
|
|
if (direction === 1) {
|
|
|
// 向前移动
|
|
|
switch(playerDirection.value) {
|
|
|
- case 0: newY--; break;
|
|
|
- case 1: newX++; break;
|
|
|
- case 2: newY++; break;
|
|
|
- case 3: newX--; break;
|
|
|
+ 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 0: newY++; break;
|
|
|
- case 1: newX--; break;
|
|
|
- case 2: newY--; break;
|
|
|
- case 3: newX++; break;
|
|
|
+ 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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -623,6 +653,8 @@ async function move(direction) {
|
|
|
if (isIce(newX, newY)) {
|
|
|
await handleIceSliding();
|
|
|
}
|
|
|
+
|
|
|
+ await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
|
|
|
} else {
|
|
|
// 发生碰撞
|
|
|
gameState.player.isColliding = true;
|
|
|
@@ -633,13 +665,13 @@ async function move(direction) {
|
|
|
executionAbortController.abort();
|
|
|
}
|
|
|
|
|
|
- // 1秒后取消碰撞状态
|
|
|
+ // 碰撞状态重置时间后取消碰撞状态
|
|
|
setTimeout(() => {
|
|
|
gameState.player.isColliding = false;
|
|
|
- }, 1000);
|
|
|
+ }, CONFIG.DELAY.COLLISION_RESET);
|
|
|
|
|
|
// 添加碰撞延迟
|
|
|
- await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
+ await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.COLLISION_DELAY));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -656,10 +688,10 @@ async function handleIceSliding() {
|
|
|
|
|
|
// 根据当前方向计算下一个位置
|
|
|
switch(playerDirection.value) {
|
|
|
- case 0: nextY--; break;
|
|
|
- case 1: nextX++; break;
|
|
|
- case 2: nextY++; break;
|
|
|
- case 3: nextX--; break;
|
|
|
+ case CONFIG.GAME.DIRECTIONS.UP: nextY--; break;
|
|
|
+ case CONFIG.GAME.DIRECTIONS.RIGHT: newX++; break;
|
|
|
+ case CONFIG.GAME.DIRECTIONS.DOWN: newY++; break;
|
|
|
+ case CONFIG.GAME.DIRECTIONS.LEFT: newX--; break;
|
|
|
}
|
|
|
|
|
|
// 检查下一个位置是否可行走
|
|
|
@@ -672,9 +704,6 @@ async function handleIceSliding() {
|
|
|
// 如果不是冰块,结束滑行
|
|
|
break;
|
|
|
}
|
|
|
-
|
|
|
- // 添加滑行间隔时间
|
|
|
- await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
} else {
|
|
|
// 下一个位置不可行走,检查是否是墙体
|
|
|
gameState.player.isColliding = true;
|
|
|
@@ -685,13 +714,10 @@ async function handleIceSliding() {
|
|
|
executionAbortController.abort();
|
|
|
}
|
|
|
|
|
|
- // 1秒后取消碰撞状态
|
|
|
+ // 碰撞状态重置时间后取消碰撞状态
|
|
|
setTimeout(() => {
|
|
|
gameState.player.isColliding = false;
|
|
|
- }, 1000);
|
|
|
-
|
|
|
- // 添加碰撞延迟
|
|
|
- await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
+ }, CONFIG.DELAY.COLLISION_RESET);
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
@@ -716,48 +742,156 @@ window.moveBackward = async function() {
|
|
|
//向左转(逆时针旋转90度)
|
|
|
window.turnLeft = async function() {
|
|
|
// 如果已经发生过碰撞,不再执行任何旋转
|
|
|
- if (isColliding.value) {
|
|
|
+ if (shouldStopExecution || isColliding.value) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 向左转(逆时针旋转90度)
|
|
|
- gameState.player.direction = (playerDirection.value - 1 + 4) % 4;
|
|
|
+ // 记录起始方向和目标方向
|
|
|
+ const startDirection = playerDirection.value;
|
|
|
+ const targetDirection = (playerDirection.value - 1 + 4) % 4;
|
|
|
+
|
|
|
+ // 实现平滑旋转
|
|
|
+ const startTime = performance.now();
|
|
|
+
|
|
|
+ // 使用 requestAnimationFrame 实现平滑动画
|
|
|
+ await new Promise(resolve => {
|
|
|
+ function animate(currentTime) {
|
|
|
+ // 检查是否应该停止执行
|
|
|
+ if (shouldStopExecution) {
|
|
|
+ resolve();
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- // 添加旋转延迟
|
|
|
- await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
+ const elapsedTime = currentTime - startTime;
|
|
|
+ const progress = Math.min(elapsedTime / CONFIG.ANIMATION.ROTATE_DURATION, 1);
|
|
|
+
|
|
|
+ // 在动画过程中更新方向
|
|
|
+ gameState.player.direction = startDirection - progress;
|
|
|
+
|
|
|
+ // 如果动画未完成,继续下一帧
|
|
|
+ if (progress < 1) {
|
|
|
+ requestAnimationFrame(animate);
|
|
|
+ } else {
|
|
|
+ // 动画完成后设置最终方向
|
|
|
+ gameState.player.direction = targetDirection;
|
|
|
+ resolve();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 开始动画
|
|
|
+ requestAnimationFrame(animate);
|
|
|
+ });
|
|
|
+
|
|
|
+ await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
|
|
|
};
|
|
|
|
|
|
//向右转(顺时针旋转90度)
|
|
|
window.turnRight = async function() {
|
|
|
// 如果已经发生过碰撞,不再执行任何旋转
|
|
|
- if (isColliding.value) {
|
|
|
+ if (shouldStopExecution || isColliding.value) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 向右转(顺时针旋转90度)
|
|
|
- gameState.player.direction = (playerDirection.value + 1) % 4;
|
|
|
+ // 记录起始方向和目标方向
|
|
|
+ const startDirection = playerDirection.value;
|
|
|
+ const targetDirection = (playerDirection.value + 1) % 4;
|
|
|
+
|
|
|
+ // 实现平滑旋转
|
|
|
+ const startTime = performance.now();
|
|
|
+
|
|
|
+ // 使用 requestAnimationFrame 实现平滑动画
|
|
|
+ await new Promise(resolve => {
|
|
|
+ function animate(currentTime) {
|
|
|
+ // 检查是否应该停止执行
|
|
|
+ if (shouldStopExecution) {
|
|
|
+ resolve();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const elapsedTime = currentTime - startTime;
|
|
|
+ const progress = Math.min(elapsedTime / CONFIG.ANIMATION.ROTATE_DURATION, 1);
|
|
|
+
|
|
|
+ // 处理从3到0的边界情况,确保顺时针旋转
|
|
|
+ let currentDirection;
|
|
|
+ if (startDirection === 3 && targetDirection === 0) {
|
|
|
+ // 对于从3到0的顺时针旋转,我们需要模拟+1的效果而不是-3
|
|
|
+ currentDirection = startDirection + progress;
|
|
|
+ // 当超过3.99时,设置为0(避免显示4)
|
|
|
+ if (currentDirection > 3.99) {
|
|
|
+ currentDirection = 0;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 正常情况下的线性插值
|
|
|
+ currentDirection = startDirection + (targetDirection - startDirection) * progress;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 在动画过程中更新方向
|
|
|
+ gameState.player.direction = currentDirection;
|
|
|
+
|
|
|
+ // 如果动画未完成,继续下一帧
|
|
|
+ if (progress < 1) {
|
|
|
+ requestAnimationFrame(animate);
|
|
|
+ } else {
|
|
|
+ // 动画完成后设置最终方向
|
|
|
+ gameState.player.direction = targetDirection;
|
|
|
+ resolve();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 开始动画
|
|
|
+ requestAnimationFrame(animate);
|
|
|
+ });
|
|
|
|
|
|
- // 添加旋转延迟
|
|
|
- await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
+ await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
|
|
|
};
|
|
|
|
|
|
// 向后转(旋转180度)
|
|
|
window.turnAround = async function() {
|
|
|
// 如果已经发生过碰撞,不再执行任何旋转
|
|
|
- if (isColliding.value) {
|
|
|
+ if (shouldStopExecution || isColliding.value) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 向后转(旋转180度)
|
|
|
- gameState.player.direction = (playerDirection.value + 2) % 4;
|
|
|
+ // 记录起始方向和目标方向
|
|
|
+ const startDirection = playerDirection.value;
|
|
|
+ const targetDirection = (playerDirection.value + 2) % 4;
|
|
|
|
|
|
- // 添加旋转延迟
|
|
|
- await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
+ // 实现平滑旋转
|
|
|
+ const startTime = performance.now();
|
|
|
+
|
|
|
+ // 使用 requestAnimationFrame 实现平滑动画
|
|
|
+ await new Promise(resolve => {
|
|
|
+ function animate(currentTime) {
|
|
|
+ // 检查是否应该停止执行
|
|
|
+ if (shouldStopExecution) {
|
|
|
+ resolve();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const elapsedTime = currentTime - startTime;
|
|
|
+ const progress = Math.min(elapsedTime / CONFIG.ANIMATION.TURN_AROUND_DURATION, 1);
|
|
|
+
|
|
|
+ // 在动画过程中更新方向
|
|
|
+ gameState.player.direction = startDirection + 2 * progress;
|
|
|
+
|
|
|
+ // 如果动画未完成,继续下一帧
|
|
|
+ if (progress < 1) {
|
|
|
+ requestAnimationFrame(animate);
|
|
|
+ } else {
|
|
|
+ // 动画完成后设置最终方向
|
|
|
+ gameState.player.direction = targetDirection;
|
|
|
+ resolve();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 开始动画
|
|
|
+ requestAnimationFrame(animate);
|
|
|
+ });
|
|
|
+
|
|
|
+ await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
|
|
|
};
|
|
|
|
|
|
//校验是否到达终点
|
|
|
window.isFinish = async function() {
|
|
|
- // 如果已经发生过碰撞,不再执行任何旋转
|
|
|
+ // 如果已经发生过碰撞,不再执行任何检查
|
|
|
if (isColliding.value) {
|
|
|
return;
|
|
|
}
|
|
|
@@ -768,14 +902,13 @@ window.isFinish = async function() {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-// 添加一个变量来跟踪当前执行的代码
|
|
|
-let currentExecutionPromise = null;
|
|
|
-let executionAbortController = null;
|
|
|
// 运行代码
|
|
|
const runCode = async () => {
|
|
|
try {
|
|
|
await resetPlayer();
|
|
|
- await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
+ await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.RESET_DELAY));
|
|
|
+ // 重置执行标志,允许新的执行
|
|
|
+ shouldStopExecution = false;
|
|
|
|
|
|
// 创建新的AbortController用于取消执行
|
|
|
executionAbortController = new AbortController();
|
|
|
@@ -851,6 +984,9 @@ const clearWorkspace = () => {
|
|
|
|
|
|
// 重置玩家位置和状态
|
|
|
const resetPlayer = () => {
|
|
|
+ // 设置标志强制停止所有执行
|
|
|
+ shouldStopExecution = true;
|
|
|
+
|
|
|
// 取消任何正在执行的代码
|
|
|
if (executionAbortController) {
|
|
|
executionAbortController.abort();
|
|
|
@@ -862,7 +998,7 @@ const resetPlayer = () => {
|
|
|
}
|
|
|
|
|
|
gameState.player.position = { ...startPoint.value };
|
|
|
- gameState.player.direction = playerInitialDirection.value; // 重置为向上方向
|
|
|
+ gameState.player.direction = playerInitialDirection.value; // 重置为初始方向
|
|
|
gameState.player.isColliding = false; //碰撞标志
|
|
|
gameState.player.hasReachedEnd = false;
|
|
|
gameState.player.isSliding = false; // 重置滑行状态
|
|
|
@@ -878,10 +1014,46 @@ onUnmounted(() => {
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
@use "sass:math";
|
|
|
+
|
|
|
@function rpx($px) {
|
|
|
@return math.div($px, 750) * 100vw;
|
|
|
}
|
|
|
-/* 信息提示框样式 */
|
|
|
+
|
|
|
+//将tileSize属性绑定到CSS变量上
|
|
|
+:root {
|
|
|
+ --tile-size: v-bind('tileSize + "px"');
|
|
|
+}
|
|
|
+
|
|
|
+.map-game-container {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: transparent;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+/* 自定义滚动条样式 */
|
|
|
+.map-game-container::-webkit-scrollbar {
|
|
|
+ width: rpx(2); /* 滚动条宽度 */
|
|
|
+}
|
|
|
+
|
|
|
+.map-game-container::-webkit-scrollbar-track {
|
|
|
+ background: #f1effd; /* 滚动条轨道背景色 */
|
|
|
+ border-radius: rpx(4);
|
|
|
+}
|
|
|
+
|
|
|
+.map-game-container::-webkit-scrollbar-thumb {
|
|
|
+ background: #e2ddfc; /* 滚动条滑块颜色 */
|
|
|
+ border-radius: rpx(4);
|
|
|
+}
|
|
|
+
|
|
|
+.map-game-container::-webkit-scrollbar-thumb:hover {
|
|
|
+ background: #e2ddfc; /* 滚动条滑块 hover 状态颜色 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 游戏简介样式 */
|
|
|
.info-message-container {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
@@ -927,46 +1099,10 @@ onUnmounted(() => {
|
|
|
margin-bottom: 0;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-//将tileSize属性绑定到CSS变量上
|
|
|
-:root {
|
|
|
- --tile-size: v-bind('tileSize + "px"');
|
|
|
-}
|
|
|
-
|
|
|
-.map-game-container {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- background: transparent;
|
|
|
- overflow-y: auto;
|
|
|
-}
|
|
|
-
|
|
|
-/* 自定义滚动条样式 */
|
|
|
-.map-game-container::-webkit-scrollbar {
|
|
|
- width: rpx(2); /* 滚动条宽度 */
|
|
|
-}
|
|
|
-
|
|
|
-.map-game-container::-webkit-scrollbar-track {
|
|
|
- background: #f1effd; /* 滚动条轨道背景色 */
|
|
|
- border-radius: rpx(4);
|
|
|
-}
|
|
|
-
|
|
|
-.map-game-container::-webkit-scrollbar-thumb {
|
|
|
- background: #e2ddfc; /* 滚动条滑块颜色 */
|
|
|
- border-radius: rpx(4);
|
|
|
-}
|
|
|
-
|
|
|
-.map-game-container::-webkit-scrollbar-thumb:hover {
|
|
|
- background: #e2ddfc; /* 滚动条滑块 hover 状态颜色 */
|
|
|
-}
|
|
|
-
|
|
|
.title-box {
|
|
|
position: relative;
|
|
|
top: rpx(5);
|
|
|
padding-left: 15px;
|
|
|
- // margin-bottom: 20px;
|
|
|
z-index: 10;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
@@ -1028,20 +1164,10 @@ onUnmounted(() => {
|
|
|
border-radius: 15px;
|
|
|
}
|
|
|
|
|
|
-.map-section h2 {
|
|
|
- margin-bottom: 15px;
|
|
|
- color: #2c3e50;
|
|
|
- border-bottom: 2px solid #3498db;
|
|
|
- padding-bottom: 8px;
|
|
|
-}
|
|
|
-
|
|
|
.map-container {
|
|
|
- //position: relative;
|
|
|
- //width: 100%;
|
|
|
- //height: 100% ;
|
|
|
- //display: flex;
|
|
|
- //justify-content: center;
|
|
|
- //align-items: center;
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
}
|
|
|
|
|
|
.map-background {
|
|
|
@@ -1051,12 +1177,6 @@ onUnmounted(() => {
|
|
|
background-size: cover;
|
|
|
background-position: center;
|
|
|
background-repeat: no-repeat;
|
|
|
- //margin: 0 auto;
|
|
|
- //border: 2px solid #ddd;
|
|
|
- //border-radius: 8px;
|
|
|
- //overflow: hidden;
|
|
|
- //background-color: #f0f0f0;
|
|
|
-
|
|
|
}
|
|
|
|
|
|
.map-image {
|
|
|
@@ -1083,7 +1203,6 @@ onUnmounted(() => {
|
|
|
background-repeat: no-repeat;
|
|
|
background-position: center;
|
|
|
border-radius: 5px;
|
|
|
- transition: all 0.3s ease;
|
|
|
z-index: 10;
|
|
|
}
|
|
|
|
|
|
@@ -1168,16 +1287,15 @@ onUnmounted(() => {
|
|
|
gap: 20px;
|
|
|
}
|
|
|
|
|
|
-.toolbox-section {
|
|
|
+// 合并重复的区块样式
|
|
|
+.map-section, .toolbox-section, .workspace-section {
|
|
|
background: rgba(248, 249, 250, 0.82);
|
|
|
padding: 15px;
|
|
|
border-radius: 15px;
|
|
|
+ height: 100%;
|
|
|
}
|
|
|
|
|
|
-// 合并重复的区块标题样式
|
|
|
-.map-section h2,
|
|
|
-.toolbox-section h2,
|
|
|
-.workspace-section h2 {
|
|
|
+.map-section h2, .toolbox-section h2, .workspace-section h2 {
|
|
|
margin-bottom: 15px;
|
|
|
color: #2c3e50;
|
|
|
border-bottom: 2px solid #3498db;
|
|
|
@@ -1191,14 +1309,6 @@ onUnmounted(() => {
|
|
|
background: rgba(248, 249, 250, 0.82);
|
|
|
padding: 15px;
|
|
|
border-radius: 15px;
|
|
|
- height: 100%;
|
|
|
-}
|
|
|
-
|
|
|
-.workspace-section h2 {
|
|
|
- margin-bottom: 15px;
|
|
|
- color: #2c3e50;
|
|
|
- border-bottom: 2px solid #3498db;
|
|
|
- padding-bottom: 8px;
|
|
|
}
|
|
|
|
|
|
#blocklyDiv {
|