|
|
@@ -195,6 +195,11 @@ const CONFIG = {
|
|
|
ITEMS_APPEAR: 300, // 物品出现延迟时间
|
|
|
ITEMS_FLIGHT_ANIMATION_DELAY: 800, // 飞行动画延迟时间
|
|
|
PALY_MP3_TIMES: 1000, // 播放MP3文件的时间间隔
|
|
|
+ VIDEO_TIMEOUT: 5000, // 视频播放超时处理时间
|
|
|
+ VIDEO_FADE_DURATION: 500, // 视频淡入淡出持续时间
|
|
|
+ PLAYER_FADE_DURATION: 500, // 玩家淡入淡出持续时间
|
|
|
+ TEMP_ITEM_FADE_DURATION: 500, // 临时物品淡入淡出持续时间
|
|
|
+ COMPLETION_DISPLAY: 1000, // 任务完成后显示延迟时间
|
|
|
},
|
|
|
// 游戏配置
|
|
|
GAME: {
|
|
|
@@ -394,6 +399,7 @@ onMounted(async () => {
|
|
|
await fetchGameData();
|
|
|
// 初始化可行走点集合
|
|
|
initWalkablePointsSet();
|
|
|
+
|
|
|
// 注册自定义积木
|
|
|
registerCustomBlocks(props.blocklySpecialBlocks);
|
|
|
// 注册JavaScript生成器
|
|
|
@@ -429,11 +435,13 @@ function generateToolboxXml() {
|
|
|
<block type="use_item"></block>
|
|
|
`;
|
|
|
|
|
|
+ // 确保blocklySpecialBlocks是数组
|
|
|
+ const specialBlocks = Array.isArray(props.blocklySpecialBlocks) ? props.blocklySpecialBlocks : [];
|
|
|
// 根据允许的特殊积木动态添加
|
|
|
- if (props.blocklySpecialBlocks.includes(BLOCKLY_CUSTOMIZE_DICT.PAUSE)) {
|
|
|
+ if (specialBlocks.includes(BLOCKLY_CUSTOMIZE_DICT.PAUSE)) {
|
|
|
toolboxXml += '<block type="pause"></block>';
|
|
|
}
|
|
|
- if (props.blocklySpecialBlocks.includes(BLOCKLY_CUSTOMIZE_DICT.PLAY_SOUND)) {
|
|
|
+ if (specialBlocks.includes(BLOCKLY_CUSTOMIZE_DICT.PLAY_SOUND)) {
|
|
|
toolboxXml += '<block type="play_sound"></block>';
|
|
|
}
|
|
|
|
|
|
@@ -697,6 +705,24 @@ const resetPlayer = async () => {
|
|
|
currentExecutionPromise = null;
|
|
|
}
|
|
|
|
|
|
+ // 清除所有视频组件
|
|
|
+ const mapBackground = document.querySelector('.map-background');
|
|
|
+ if (mapBackground) {
|
|
|
+ const videoElements = mapBackground.querySelectorAll('video');
|
|
|
+ videoElements.forEach(video => {
|
|
|
+ if (mapBackground.contains(video)) {
|
|
|
+ mapBackground.removeChild(video);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保小智显形(不隐形)
|
|
|
+ const playerElement = document.querySelector('.player');
|
|
|
+ if (playerElement) {
|
|
|
+ playerElement.style.opacity = '1';
|
|
|
+ playerElement.style.transition = 'none'; // 取消过渡效果,立即显示
|
|
|
+ }
|
|
|
+
|
|
|
// 重置携带的物品回地图
|
|
|
if (gameState.mapData.originalWalkablePoints.length > 0) {
|
|
|
gameState.mapData.walkablePoints = JSON.parse(JSON.stringify(gameState.mapData.originalWalkablePoints));
|
|
|
@@ -1020,26 +1046,10 @@ window.useItem = async function() {
|
|
|
|
|
|
// 获取要使用的物品
|
|
|
const itemToUse = gameState.player.carriedItems[itemIndex];
|
|
|
- // 执行物品使用动画
|
|
|
- await animateItemUse(itemToUse, itemIndex);
|
|
|
// 从携带物品中移除已使用的物品
|
|
|
gameState.player.carriedItems.splice(itemIndex, 1);
|
|
|
-
|
|
|
- // 从地图上移除图标
|
|
|
- const pointIndex = gameState.mapData.walkablePoints.findIndex(
|
|
|
- p => p.x === x && p.y === y
|
|
|
- );
|
|
|
- if (pointIndex !== -1) {
|
|
|
-
|
|
|
- // 保留点但移除img属性并设置完成状态图标
|
|
|
- const updatedPoint = { ...gameState.mapData.walkablePoints[pointIndex] };
|
|
|
- updatedPoint.img = updatedPoint.endImg; // 设置完成状态图标
|
|
|
- updatedPoint.status = true;
|
|
|
- gameState.mapData.walkablePoints.splice(pointIndex, 1, updatedPoint);
|
|
|
-
|
|
|
- // 更新映射
|
|
|
- walkablePointsMap.set(`${x},${y}`, updatedPoint);
|
|
|
- }
|
|
|
+ // 执行物品使用动画,传递finishAnimation
|
|
|
+ await animateItemUse(itemToUse, itemIndex, tileMap.finishAnimation);
|
|
|
|
|
|
// 使用物品成功
|
|
|
showGameMessage(tileMap.finishedTip || CONFIG.TIPS.USE_ITEM_SUCCESS, 'success');
|
|
|
@@ -1101,21 +1111,21 @@ window.pause = async function(seconds) {
|
|
|
await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
|
|
|
}
|
|
|
|
|
|
-// 函数
|
|
|
+// 声音函数
|
|
|
window.playSound = async function() {
|
|
|
if (shouldStopExecution || isColliding.value || isSliding.value) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if(processingSpecialTasksDisappearing()){
|
|
|
- //增加延迟,确保声音播放完成
|
|
|
+ //延迟,确保声音播放完成
|
|
|
await playMp3(passMp3);
|
|
|
await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.PALY_MP3_TIMES));
|
|
|
return
|
|
|
}
|
|
|
|
|
|
showGameMessage(CONFIG.TIPS.ERROR_ERROR, 'info');
|
|
|
- //增加延迟,确保声音播放完成
|
|
|
+ //延迟,确保声音播放完成
|
|
|
await playMp3(failureMp3);
|
|
|
await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.PALY_MP3_TIMES));
|
|
|
};
|
|
|
@@ -1406,7 +1416,7 @@ async function animateItemPickup(item, itemX, itemY) {
|
|
|
}
|
|
|
|
|
|
// 物品使用动画函数
|
|
|
-async function animateItemUse(item, itemIndex) {
|
|
|
+async function animateItemUse(item, itemIndex, finishAnimation) {
|
|
|
// 创建临时动画元素
|
|
|
const tempItem = document.createElement('div');
|
|
|
const finalSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO * 0.5;
|
|
|
@@ -1430,28 +1440,13 @@ async function animateItemUse(item, itemIndex) {
|
|
|
// 获取玩家元素
|
|
|
const playerElement = document.querySelector('.player');
|
|
|
const originalPlayerZIndex = playerElement ? playerElement.style.zIndex : '';
|
|
|
+ const originalPlayerOpacity = playerElement ? playerElement.style.opacity : '1';
|
|
|
|
|
|
- // 获取当前位置的任务点元素
|
|
|
- let taskPointElement = null;
|
|
|
- const walkablePointsElements = document.querySelectorAll('.walkable-point');
|
|
|
- walkablePointsElements.forEach(el => {
|
|
|
- const rect = el.getBoundingClientRect();
|
|
|
- const targetRect = {
|
|
|
- left: playerLeft,
|
|
|
- top: playerTop,
|
|
|
- width: iconSize,
|
|
|
- height: iconSize
|
|
|
- };
|
|
|
- // 简单判断元素是否在玩家位置附近
|
|
|
- if (Math.abs(rect.left - targetRect.left) < iconSize &&
|
|
|
- Math.abs(rect.top - targetRect.top) < iconSize) {
|
|
|
- taskPointElement = el;
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- // 先将任务图标置顶盖住人物
|
|
|
- if (taskPointElement) {
|
|
|
- taskPointElement.style.zIndex = '150';
|
|
|
+ // 将临时元素添加到地图容器
|
|
|
+ const mapBackground = document.querySelector('.map-background');
|
|
|
+ if (!mapBackground) {
|
|
|
+ console.error('地图容器未找到');
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
// 设置临时元素初始样式
|
|
|
@@ -1470,19 +1465,52 @@ async function animateItemUse(item, itemIndex) {
|
|
|
transform: 'scale(1)',
|
|
|
});
|
|
|
|
|
|
- // 将临时元素添加到地图容器
|
|
|
- const mapBackground = document.querySelector('.map-background');
|
|
|
- if (!mapBackground) {
|
|
|
- console.error('地图容器未找到');
|
|
|
- return;
|
|
|
- }
|
|
|
mapBackground.appendChild(tempItem);
|
|
|
|
|
|
- // 准备漂移动画
|
|
|
+ // 1. 首先让小智逐渐消失
|
|
|
+ if (playerElement) {
|
|
|
+ playerElement.style.transition = 'opacity ' + CONFIG.DELAY.PLAYER_FADE_DURATION + 'ms ease-out';
|
|
|
+ playerElement.style.opacity = '0';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 准备漂移动画
|
|
|
await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ITEMS_APPEAR));
|
|
|
|
|
|
- // 设置漂移动画样式
|
|
|
- tempItem.style.transition = 'all 1s ease-out';
|
|
|
+ // 3. 如果有视频,先创建视频元素但不自动播放
|
|
|
+ let videoElement = null;
|
|
|
+ if (finishAnimation) {
|
|
|
+ // 创建视频元素
|
|
|
+ videoElement = document.createElement('video');
|
|
|
+ videoElement.src = finishAnimation;
|
|
|
+ videoElement.autoplay = false; // 不自动播放
|
|
|
+ // videoElement.muted = true; // 视频默认静音
|
|
|
+ videoElement.playsinline = true;
|
|
|
+ videoElement.style.position = 'absolute';
|
|
|
+ videoElement.style.left = playerLeft + 'px';
|
|
|
+ videoElement.style.top = playerTop + 'px';
|
|
|
+ videoElement.style.width = iconSize + 'px';
|
|
|
+ videoElement.style.height = iconSize + 'px';
|
|
|
+ videoElement.style.zIndex = '2000'; // 提高z-index确保视频可见
|
|
|
+ videoElement.style.objectFit = 'contain';
|
|
|
+
|
|
|
+ // 添加渐变显示效果
|
|
|
+ videoElement.style.opacity = '0';
|
|
|
+ videoElement.style.transition = 'opacity ' + CONFIG.DELAY.VIDEO_FADE_DURATION + 'ms ease-in-out';
|
|
|
+
|
|
|
+ // 添加视频到地图容器
|
|
|
+ mapBackground.appendChild(videoElement);
|
|
|
+
|
|
|
+ // 触发重绘后设置透明度为1,实现渐变效果
|
|
|
+ setTimeout(() => {
|
|
|
+ videoElement.style.opacity = '1';
|
|
|
+ console.log('Video opacity set to 1');
|
|
|
+ }, CONFIG.DELAY.VIDEO_FADE_DURATION);
|
|
|
+ } else {
|
|
|
+ console.log('无视频');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 设置漂移动画样式
|
|
|
+ tempItem.style.transition = 'all ' + CONFIG.DELAY.TEMP_ITEM_FADE_DURATION + 'ms ease-out';
|
|
|
Object.assign(tempItem.style, {
|
|
|
left: playerLeft + 'px',
|
|
|
top: playerTop + 'px',
|
|
|
@@ -1490,30 +1518,91 @@ async function animateItemUse(item, itemIndex) {
|
|
|
height: iconSize + 'px',
|
|
|
});
|
|
|
|
|
|
- // 等待漂移到玩家位置
|
|
|
+ // 5. 等待漂移到玩家位置
|
|
|
await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ITEMS_FLIGHT_ANIMATION_DELAY));
|
|
|
|
|
|
- // 物品使用效果动画
|
|
|
- tempItem.style.transition = 'transform 0.3s ease-in-out';
|
|
|
- tempItem.style.transform = 'scale(1.2)';
|
|
|
-
|
|
|
- await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ITEMS_APPEAR));
|
|
|
-
|
|
|
- // 移除临时元素
|
|
|
+ // 6. 移除临时元素
|
|
|
if (mapBackground.contains(tempItem)) {
|
|
|
mapBackground.removeChild(tempItem);
|
|
|
}
|
|
|
|
|
|
- // 延迟一段时间后将人物图标置顶,露出完整的人物图标
|
|
|
- await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ITEMS_APPEAR));
|
|
|
+ let x = playerPosition.value.x;
|
|
|
+ let y = playerPosition.value.y;
|
|
|
+
|
|
|
+ // 8. 视频播放完成后替换任务图片
|
|
|
+ const pointIndex = gameState.mapData.walkablePoints.findIndex(
|
|
|
+ p => p.x === x && p.y === y
|
|
|
+ );
|
|
|
+ if (pointIndex !== -1) {
|
|
|
+ // 保留点但移除img属性并设置完成状态图标
|
|
|
+ const updatedPoint = { ...gameState.mapData.walkablePoints[pointIndex] };
|
|
|
+ updatedPoint.img = updatedPoint.endImg; // 设置完成状态图标
|
|
|
+ updatedPoint.status = true;
|
|
|
+ gameState.mapData.walkablePoints.splice(pointIndex, 1, updatedPoint);
|
|
|
+
|
|
|
+ // 更新映射
|
|
|
+ walkablePointsMap.set(`${x},${y}`, updatedPoint);
|
|
|
+ }
|
|
|
|
|
|
- // 恢复层级关系
|
|
|
- if (taskPointElement) {
|
|
|
- taskPointElement.style.zIndex = '';
|
|
|
+ // 9. 处理视频播放或直接替换任务图片
|
|
|
+ if (videoElement) {
|
|
|
+ // 开始播放视频
|
|
|
+ try {
|
|
|
+ const playPromise = videoElement.play();
|
|
|
+ if (playPromise !== undefined) {
|
|
|
+ playPromise.then(_ => {
|
|
|
+ console.log('Video started playing successfully');
|
|
|
+ }).catch(error => {
|
|
|
+ console.error('Error playing video:', error);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Exception when playing video:', error);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 等待视频播放完成,添加超时处理
|
|
|
+ await new Promise(resolve => {
|
|
|
+ videoElement.onended = () => {
|
|
|
+ console.log('Video ended');
|
|
|
+ resolve();
|
|
|
+ };
|
|
|
+
|
|
|
+ // 添加超时处理,防止视频卡住
|
|
|
+ setTimeout(() => {
|
|
|
+ resolve();
|
|
|
+ }, CONFIG.DELAY.VIDEO_TIMEOUT);
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ // 10. 移除视频元素 - 添加淡出效果
|
|
|
+ if (mapBackground.contains(videoElement)) {
|
|
|
+ // 设置淡出效果
|
|
|
+ videoElement.style.transition = 'opacity ' + CONFIG.DELAY.VIDEO_FADE_DURATION + 'ms ease-in-out';
|
|
|
+ videoElement.style.opacity = '0';
|
|
|
+
|
|
|
+ // 等待淡出动画完成后再移除元素
|
|
|
+ setTimeout(() => {
|
|
|
+ if (mapBackground.contains(videoElement)) {
|
|
|
+ mapBackground.removeChild(videoElement);
|
|
|
+ }
|
|
|
+ }, CONFIG.DELAY.VIDEO_FADE_DURATION); // 与动画时长一致
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 10.无视频
|
|
|
}
|
|
|
|
|
|
+ // 11. 视频消失后稍作停留,让用户看到完成后的图片
|
|
|
+ await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.COMPLETION_DISPLAY));
|
|
|
+
|
|
|
+ // 逐步显示玩家并将其置顶
|
|
|
if (playerElement) {
|
|
|
- playerElement.style.zIndex = originalPlayerZIndex || '';
|
|
|
+ playerElement.style.transition = 'opacity ' + CONFIG.DELAY.PLAYER_FADE_DURATION + 'ms ease-in';
|
|
|
+ playerElement.style.opacity = originalPlayerOpacity;
|
|
|
+ playerElement.style.zIndex = originalPlayerZIndex || '100';
|
|
|
+ // 额外设置一个较高的z-index确保盖住其他人物图片
|
|
|
+ setTimeout(() => {
|
|
|
+ playerElement.style.zIndex = '150';
|
|
|
+ }, CONFIG.DELAY.PLAYER_FADE_DURATION);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -2029,27 +2118,27 @@ onUnmounted(() => {
|
|
|
}
|
|
|
|
|
|
/* 优化Blockly积木样式 */
|
|
|
-/* 增加积木高度 */
|
|
|
+/* 积木高度 */
|
|
|
.blocklyBlockCanvas .blocklyBlock {
|
|
|
- height: 45px; /* 增加默认高度 */
|
|
|
+ height: 45px; /* 默认高度 */
|
|
|
min-height: 45px;
|
|
|
}
|
|
|
|
|
|
-/* 增加积木内部元素的行高和间距 */
|
|
|
+/* 积木内部元素的行高和间距 */
|
|
|
.blocklyText {
|
|
|
font-size: 16px;
|
|
|
line-height: 20px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
-/* 增加输入字段的高度 */
|
|
|
+/* 输入字段的高度 */
|
|
|
.blocklyHtmlInput {
|
|
|
height: 30px;
|
|
|
font-size: 14px;
|
|
|
padding: 5px;
|
|
|
}
|
|
|
|
|
|
-/* 增加下拉菜单的高度 */
|
|
|
+/* 下拉菜单的高度 */
|
|
|
.blocklyDropdownMenu {
|
|
|
line-height: 28px;
|
|
|
font-size: 14px;
|
|
|
@@ -2061,13 +2150,13 @@ onUnmounted(() => {
|
|
|
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
|
|
|
}
|
|
|
|
|
|
-/* 增加积木之间的连接点间距 */
|
|
|
+/* 积木之间的连接点间距 */
|
|
|
.blocklyConnection {
|
|
|
height: 20px;
|
|
|
width: 20px;
|
|
|
}
|
|
|
|
|
|
-/* 增加工具箱中积木的高度 */
|
|
|
+/* 工具箱中积木的高度 */
|
|
|
.blocklyTreeRow {
|
|
|
height: 40px;
|
|
|
line-height: 40px;
|