|
|
@@ -41,6 +41,18 @@
|
|
|
:class="{ 'collision': isColliding, 'success': hasReachedEnd }"
|
|
|
>
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- 怀表倒计时容器 -->
|
|
|
+ <div v-if="showCountdown" class="watch-container" :style="countdownStyle">
|
|
|
+ <div class="watch-face">
|
|
|
+ <div class="watch-center"></div>
|
|
|
+ <div class="watch-hands">
|
|
|
+ <div class="watch-hand hour-hand"></div>
|
|
|
+ <div class="watch-hand minute-hand"></div>
|
|
|
+ </div>
|
|
|
+ <div class="watch-countdown-number">{{ countdownValue }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<!-- 携带物品容器 -->
|
|
|
<div class="carried-items-container" v-show="gameState.player.carriedItems.length > 0">
|
|
|
<div
|
|
|
@@ -72,6 +84,7 @@
|
|
|
<block type="turn_around"></block>
|
|
|
<block type="pickup_item"></block>
|
|
|
<block type="use_item"></block>
|
|
|
+ <block type="pause"></block>
|
|
|
</category>
|
|
|
|
|
|
<!-- 逻辑控制积木 -->
|
|
|
@@ -118,6 +131,9 @@ import 'blockly/msg/zh-hans';
|
|
|
import { javascriptGenerator } from "blockly/javascript";
|
|
|
import playerImage from '@/assets/images/blockly/user.png';
|
|
|
|
|
|
+// 游戏接口数据
|
|
|
+import { getMapGameById } from '@/api/blockly/game.js';
|
|
|
+
|
|
|
// 定义组件属性
|
|
|
const props = defineProps({
|
|
|
// 游戏ID
|
|
|
@@ -182,9 +198,6 @@ const props = defineProps({
|
|
|
}
|
|
|
});
|
|
|
|
|
|
-// 游戏接口数据
|
|
|
-import { getMapGameById } from '@/api/blockly/game.js';
|
|
|
-
|
|
|
// 配置常量
|
|
|
const CONFIG = {
|
|
|
// 动画时长配置(毫秒)
|
|
|
@@ -247,6 +260,10 @@ let executionAbortController = null;
|
|
|
// 添加响应式的容器尺寸
|
|
|
const mapContainerDimensions = ref({ width: 0, height: 0 });
|
|
|
|
|
|
+// 暂停模块-倒计时相关状态
|
|
|
+const showCountdown = ref(false);
|
|
|
+const countdownValue = ref(0);
|
|
|
+
|
|
|
// Blockly相关状态
|
|
|
let workspace = null;
|
|
|
|
|
|
@@ -388,10 +405,10 @@ const fetchGameData = async () => {
|
|
|
userImage: props.userImage,
|
|
|
info: props.info
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
// 直接更新游戏状态
|
|
|
await updateGameStateFromData(currentGameData.value);
|
|
|
-
|
|
|
+
|
|
|
// 数据更新后强制刷新容器尺寸(等待DOM更新)
|
|
|
await nextTick();
|
|
|
updateMapContainerDimensions();
|
|
|
@@ -537,6 +554,21 @@ function getCarriedItemStyle(index, item) {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+// 倒计时样式计算
|
|
|
+const countdownStyle = computed(() => {
|
|
|
+ return {
|
|
|
+ position: 'absolute',
|
|
|
+ left: playerPosition.value.x * tileSize.value - tileSize.value - (tileSize.value * 0.2) + 'px',
|
|
|
+ top: playerPosition.value.y * tileSize.value - tileSize.value - (tileSize.value * 0.2) + 'px',
|
|
|
+ width: tileSize.value * 0.5 + 'px',
|
|
|
+ height: tileSize.value * 0.5 + 'px',
|
|
|
+ display: 'flex',
|
|
|
+ justifyContent: 'center',
|
|
|
+ alignItems: 'center',
|
|
|
+ zIndex: 100
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
// 注册自定义积木
|
|
|
function registerCustomBlocks() {
|
|
|
// 向前移动积木
|
|
|
@@ -628,6 +660,31 @@ function registerCustomBlocks() {
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
+
|
|
|
+ // 暂停积木
|
|
|
+ Blockly.Blocks['pause'] = {
|
|
|
+ init: function() {
|
|
|
+ this.jsonInit({
|
|
|
+ "type": "pause",
|
|
|
+ "message0": "暂停 %1 秒",
|
|
|
+ "args0": [
|
|
|
+ {
|
|
|
+ "type": "field_number",
|
|
|
+ "name": "SECONDS",
|
|
|
+ "value": 1,
|
|
|
+ "min": 1,
|
|
|
+ "max": 10,
|
|
|
+ "precision": 1
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "previousStatement": null,
|
|
|
+ "nextStatement": null,
|
|
|
+ "colour": 0,
|
|
|
+ "tooltip": "让角色在原地停留指定的秒数",
|
|
|
+ "helpUrl": ""
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
// 注册JavaScript生成器
|
|
|
@@ -662,6 +719,12 @@ function registerJavaScriptGenerators() {
|
|
|
return 'await useItem();\n';
|
|
|
};
|
|
|
|
|
|
+ // 暂停生成器
|
|
|
+ javascriptGenerator.forBlock['pause'] = function(block) {
|
|
|
+ const seconds = block.getFieldValue('SECONDS');
|
|
|
+ return `await pause(${seconds});\n`;
|
|
|
+ };
|
|
|
+
|
|
|
// 为重复循环块注册自定义生成器,确保支持异步操作
|
|
|
javascriptGenerator.forBlock['controls_repeat_ext'] = function(block) {
|
|
|
const repeats = javascriptGenerator.valueToCode(block, 'TIMES', javascriptGenerator.ORDER_ATOMIC) || '0';
|
|
|
@@ -981,7 +1044,7 @@ async function animateItemPickup(item, itemX, itemY) {
|
|
|
const itemTop = itemY * tileSize.value - tileSize.value + marginSize;
|
|
|
|
|
|
// 平行移动到容器位置,同时调整大小
|
|
|
- const finalSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO * 0.3;
|
|
|
+ const finalSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO * 0.5;
|
|
|
|
|
|
// 获取携带物品容器的位置(左上角)
|
|
|
// 注意:这里不需要考虑当前已携带物品数量,因为物品还没被添加
|
|
|
@@ -1048,7 +1111,7 @@ async function animateItemPickup(item, itemX, itemY) {
|
|
|
async function animateItemUse(item, itemIndex) {
|
|
|
// 创建临时动画元素
|
|
|
const tempItem = document.createElement('div');
|
|
|
- const finalSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO * 0.3;
|
|
|
+ const finalSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO * 0.5;
|
|
|
const iconSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO;
|
|
|
const marginSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_MARGIN;
|
|
|
|
|
|
@@ -1268,6 +1331,28 @@ window.useItem = async function() {
|
|
|
await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
|
|
|
}
|
|
|
|
|
|
+// 暂停函数
|
|
|
+window.pause = async function(seconds) {
|
|
|
+ if (shouldStopExecution || isSliding.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示倒计时
|
|
|
+ showCountdown.value = true;
|
|
|
+ countdownValue.value = seconds;
|
|
|
+
|
|
|
+ // 倒计时循环
|
|
|
+ while (countdownValue.value > 0 && !shouldStopExecution) {
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
+ countdownValue.value--;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 隐藏倒计时
|
|
|
+ showCountdown.value = false;
|
|
|
+
|
|
|
+ await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
|
|
|
+}
|
|
|
+
|
|
|
// 向前移动
|
|
|
window.moveForward = async function() {
|
|
|
if (shouldStopExecution || isColliding.value || isSliding.value) {
|
|
|
@@ -1920,7 +2005,119 @@ onUnmounted(() => {
|
|
|
/* 携带物品样式 */
|
|
|
.carried-item {
|
|
|
animation: bounceIn 0.3s ease-out forwards;
|
|
|
- opacity: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 【暂停】怀表容器样式 */
|
|
|
+.watch-container {
|
|
|
+ animation: fadeInCountdown 0.3s ease-out;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+/* 【暂停】怀表表盘样式 */
|
|
|
+.watch-face {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: linear-gradient(145deg, rgba(240, 240, 240, 0.8), rgba(200, 200, 200, 0.6));
|
|
|
+ border: 4px solid rgba(100, 100, 100, 0.8);
|
|
|
+ box-shadow:
|
|
|
+ 0 0 20px rgba(0, 0, 0, 0.3),
|
|
|
+ inset 0 0 20px rgba(0, 0, 0, 0.1);
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ backdrop-filter: blur(5px);
|
|
|
+}
|
|
|
+
|
|
|
+/* 【暂停】怀表中心圆点 */
|
|
|
+.watch-center {
|
|
|
+ width: 10%;
|
|
|
+ height: 10%;
|
|
|
+ background-color: rgba(100, 50, 20, 0.8);
|
|
|
+ border-radius: 50%;
|
|
|
+ position: absolute;
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+
|
|
|
+/* 【暂停】表针容器 */
|
|
|
+.watch-hands {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+/* 【暂停】表针基础样式 */
|
|
|
+.watch-hand {
|
|
|
+ position: absolute;
|
|
|
+ background-color: rgba(100, 50, 20, 0.8);
|
|
|
+ transform-origin: bottom center;
|
|
|
+ border-radius: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 【暂停】时针样式 */
|
|
|
+.hour-hand {
|
|
|
+ width: 4%;
|
|
|
+ height: 30%;
|
|
|
+ top: 20%;
|
|
|
+ animation: rotateHourHand 12s linear infinite;
|
|
|
+}
|
|
|
+
|
|
|
+/* 【暂停】分针样式 */
|
|
|
+.minute-hand {
|
|
|
+ width: 3%;
|
|
|
+ height: 40%;
|
|
|
+ top: 10%;
|
|
|
+ animation: rotateMinuteHand 1s linear infinite;
|
|
|
+}
|
|
|
+
|
|
|
+/* 【暂停】怀表中心倒计时数字 */
|
|
|
+.watch-countdown-number {
|
|
|
+ font-size: calc(var(--tile-size, 143px) * 0.3);
|
|
|
+ font-weight: bold;
|
|
|
+ color: rgba(100, 50, 20, 0.9);
|
|
|
+ text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8);
|
|
|
+ z-index: 5;
|
|
|
+ animation: pulseCountdown 1s infinite;
|
|
|
+}
|
|
|
+
|
|
|
+/* 【暂停】怀表淡入动画 */
|
|
|
+@keyframes fadeInCountdown {
|
|
|
+ 0% {
|
|
|
+ opacity: 0;
|
|
|
+ transform: scale(0.8) rotate(-30deg);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ opacity: 1;
|
|
|
+ transform: scale(1) rotate(0deg);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 【暂停】倒计时数字脉冲动画 */
|
|
|
+@keyframes pulseCountdown {
|
|
|
+ 0%, 100% {
|
|
|
+ opacity: 0.9;
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ opacity: 0.7;
|
|
|
+ transform: scale(1.05);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 【暂停】时针旋转动画 */
|
|
|
+@keyframes rotateHourHand {
|
|
|
+ from { transform: rotate(0deg); }
|
|
|
+ to { transform: rotate(360deg); }
|
|
|
+}
|
|
|
+
|
|
|
+/* 【暂停】分针旋转动画 */
|
|
|
+@keyframes rotateMinuteHand {
|
|
|
+ from { transform: rotate(0deg); }
|
|
|
+ to { transform: rotate(360deg); }
|
|
|
}
|
|
|
|
|
|
/* 弹入动画 */
|