Commit fedc7c9b authored by 董政锦's avatar 董政锦

fix: 测回;

parent dbc63f3d
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, watch, nextTick, computed } from 'vue'; import {
import { assetUrl } from '@/commons/assets.ts' onBeforeUnmount,
import { playGame6Music } from '@/commons/music' onMounted,
ref,
watch,
nextTick,
computed,
} from "vue";
import { assetUrl } from "@/commons/assets.ts";
// import { playGame6Music } from '@/commons/music'
const props = defineProps<{ const props = defineProps<{
imageUrls: Record<string, string> imageUrls: Record<string, string>;
countdownInterval: number countdownInterval: number;
isDivDescVisible: boolean isDivDescVisible: boolean;
tick: number tick: number;
rank: number rank: number;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
touch: [mole: boolean] // touch: [mole: boolean]
scoreChange: [delta: number] scoreChange: [delta: number];
rankClose: [];
}>(); }>();
// ========== 常量 ========== // ========== 常量 ==========
...@@ -25,56 +33,84 @@ const HORSE_BOTTOM_PCT = 19.11; ...@@ -25,56 +33,84 @@ const HORSE_BOTTOM_PCT = 19.11;
const ITEM_SIZE = 80; const ITEM_SIZE = 80;
const GAME_DURATION = 60; const GAME_DURATION = 60;
const LEFT_LANE_X = 170; // 设计基准跑道 X 位置(750px 设计稿下的值,窄屏设备上会自动居中偏移)
const RIGHT_LANE_X = 470; const DESIGN_LEFT_LANE_X = 150;
const DESIGN_RIGHT_LANE_X = 500;
// 运行时根据视口计算的真实 X 位置(适配不同屏幕宽度)
const leftLaneX = ref(DESIGN_LEFT_LANE_X);
const rightLaneX = ref(DESIGN_RIGHT_LANE_X);
/** 按视口宽度计算 750px 设计区域在 content-bg 中的居中偏移 */
const updateLanePositions = () => {
const vw = window.visualViewport?.width ?? window.innerWidth;
const vh = window.visualViewport?.height ?? window.innerHeight;
// MobileStage contain 模式下 scale = min(scaleX, scaleY),手机通常由高度决定
const scaleY = vh / 1624;
if (!scaleY || scaleY <= 0) return;
const stageViewportWidth = vw / scaleY;
const offset = Math.max(0, (stageViewportWidth - 750) / 2);
leftLaneX.value = DESIGN_LEFT_LANE_X + offset;
rightLaneX.value = DESIGN_RIGHT_LANE_X + offset;
};
// ========== 马帧 ========== // ========== 马帧 ==========
const HORSE_FRAME_COUNT = 21; const HORSE_FRAME_COUNT = 21;
const horseFrames = Array.from({ length: HORSE_FRAME_COUNT }, (_, i) => const horseFrames = Array.from({ length: HORSE_FRAME_COUNT }, (_, i) =>
assetUrl(`game3/horse/1-ma_${String(i).padStart(2, '0')}.png`) assetUrl(`game3/horse/1-ma_${String(i).padStart(2, "0")}.png`),
); );
// ========== 金币/炸弹帧 ========== // ========== 金币/炸弹帧 ==========
const JINBI_FRAME_COUNT = 25; const JINBI_FRAME_COUNT = 25;
const jinbiFrames = Array.from({ length: JINBI_FRAME_COUNT }, (_, i) => const jinbiFrames = Array.from({ length: JINBI_FRAME_COUNT }, (_, i) =>
assetUrl(`game3/jinbi/1-yuanbao_${String(i).padStart(2, '0')}.png`) assetUrl(`game3/jinbi/1-yuanbao_${String(i).padStart(2, "0")}.png`),
); );
const BOM_FRAME_COUNT = 28; const BOM_FRAME_COUNT = 28;
const bomFrames = Array.from({ length: BOM_FRAME_COUNT }, (_, i) => const bomFrames = Array.from({ length: BOM_FRAME_COUNT }, (_, i) =>
assetUrl(`game3/bom/1-zha_${String(i).padStart(2, '0')}.png`) assetUrl(`game3/bom/1-zha_${String(i).padStart(2, "0")}.png`),
); );
const fudaiUrl = assetUrl('game3/icon_fudai.webp'); const fudaiUrl = assetUrl("game3/icon_fudai.webp");
const dileiUrl = assetUrl('game3/icon_dilei.webp'); const dileiUrl = assetUrl("game3/icon_dilei.webp");
// ========== 预加载碰撞动画帧(解决 4G 慢网下动画图片未加载的问题) ==========
const preloadFrames = (urls: string[]) => {
for (const url of urls) {
const img = new Image();
img.src = url;
}
};
preloadFrames(jinbiFrames);
preloadFrames(bomFrames);
// ========== 马状态 ========== // ========== 马状态 ==========
const horseLane = ref<'left' | 'right'>('left'); const horseLane = ref<"left" | "right">("left");
const horseFrameIndex = ref(0); const horseFrameIndex = ref(0);
const horseX = ref(LEFT_LANE_X); const horseX = ref(leftLaneX.value);
const horseTargetX = ref(LEFT_LANE_X); const horseTargetX = ref(leftLaneX.value);
const switchToLeft = () => { const switchToLeft = () => {
if (horseLane.value !== 'left') { if (horseLane.value !== "left") {
horseLane.value = 'left'; horseLane.value = "left";
horseTargetX.value = LEFT_LANE_X; horseTargetX.value = leftLaneX.value;
} }
}; };
const switchToRight = () => { const switchToRight = () => {
if (horseLane.value !== 'right') { if (horseLane.value !== "right") {
horseLane.value = 'right'; horseLane.value = "right";
horseTargetX.value = RIGHT_LANE_X; horseTargetX.value = rightLaneX.value;
} }
}; };
// ========== 键盘控制 ========== // ========== 键盘控制 ==========
const handleKeydown = (e: KeyboardEvent) => { const handleKeydown = (e: KeyboardEvent) => {
if (!props.isDivDescVisible) return; if (!props.isDivDescVisible) return;
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') { if (e.key === "ArrowLeft" || e.key === "a" || e.key === "A") {
switchToLeft(); switchToLeft();
} else if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') { } else if (e.key === "ArrowRight" || e.key === "d" || e.key === "D") {
switchToRight(); switchToRight();
} }
}; };
...@@ -83,8 +119,8 @@ const handleKeydown = (e: KeyboardEvent) => { ...@@ -83,8 +119,8 @@ const handleKeydown = (e: KeyboardEvent) => {
const roadOffset = ref(0); const roadOffset = ref(0);
let roadRafId: number | undefined; let roadRafId: number | undefined;
let roadLastTs = 0; let roadLastTs = 0;
const BASE_ROAD_SPEED = 80; const BASE_ROAD_SPEED = 200;
const MAX_ROAD_SPEED = 400; const MAX_ROAD_SPEED = 700;
const gameElapsed = ref(0); const gameElapsed = ref(0);
const currentRoadSpeed = ref(BASE_ROAD_SPEED); const currentRoadSpeed = ref(BASE_ROAD_SPEED);
...@@ -105,9 +141,13 @@ const startRoadScroll = () => { ...@@ -105,9 +141,13 @@ const startRoadScroll = () => {
gameElapsed.value = (ts - startTs) / 1000; gameElapsed.value = (ts - startTs) / 1000;
const progress = Math.min(gameElapsed.value / GAME_DURATION, 1); const progress = Math.min(gameElapsed.value / GAME_DURATION, 1);
currentRoadSpeed.value = BASE_ROAD_SPEED + (MAX_ROAD_SPEED - BASE_ROAD_SPEED) * progress; currentRoadSpeed.value =
BASE_ROAD_SPEED + (MAX_ROAD_SPEED - BASE_ROAD_SPEED) * progress;
roadOffset.value += currentRoadSpeed.value * dt / 1000; roadOffset.value += (currentRoadSpeed.value * dt) / 1000;
if (roadOffset.value >= ROAD_HEIGHT) {
roadOffset.value -= ROAD_HEIGHT;
}
roadRafId = requestAnimationFrame(tick); roadRafId = requestAnimationFrame(tick);
}; };
...@@ -122,121 +162,187 @@ const stopRoadScroll = () => { ...@@ -122,121 +162,187 @@ const stopRoadScroll = () => {
}; };
// ========== 持续道具生成系统 ========== // ========== 持续道具生成系统 ==========
type ItemKind = 'coin' | 'bomb'; type ItemKind = "coin" | "bomb";
interface RoadItem { interface RoadItem {
id: number; id: number;
kind: ItemKind; kind: ItemKind;
lane: 'left' | 'right'; lane: "left" | "right";
y: number; y: number;
collected: boolean; collected: boolean;
colliding: boolean; // 防止同一帧内重复触发碰撞特效
animFrame: number; animFrame: number;
animTimer: ReturnType<typeof setTimeout> | undefined; animTimer: ReturnType<typeof setTimeout> | undefined;
} }
const roadItems = ref<RoadItem[]>([]); const roadItems = ref<RoadItem[]>([]);
const effectAnims = ref<{ id: number; kind: ItemKind; frame: number; x: number; y: number }[]>([]); const effectAnims = ref<
{ id: number; kind: ItemKind; frame: number; x: number; y: number }[]
>([]);
interface ScoreEffect {
id: number;
x: number;
y: number;
score: number; // 正数表示加分,负数表示扣分
}
const scoreEffects = ref<ScoreEffect[]>([]);
let scoreEffectIdSeq = 0;
let itemIdCounter = 0; let itemIdCounter = 0;
let lastItemY = 0; let genTimerId: ReturnType<typeof setTimeout> | undefined;
const MIN_ITEM_SPACING = 250;
const MAX_ITEM_SPACING = 400;
const ITEM_GEN_INTERVAL = 1500;
function seededRandom(seed: number) { function seededRandom(seed: number) {
const x = Math.sin(seed * 12.9898 + 78.233) * 43758.5453; const x = Math.sin(seed * 12.9898 + 78.233) * 43758.5453;
return x - Math.floor(x); return x - Math.floor(x);
} }
function scheduleNextItem() {
if (!props.isDivDescVisible) return;
// 游戏越接近尾声,生成间隔越短(1800ms → 350ms),加上随机抖动
const progress = Math.min(gameElapsed.value / GAME_DURATION, 1);
const base = 1800 - 1450 * progress;
const jitter = (Math.random() - 0.5) * 800; // ±400ms 随机
const interval = Math.max(250, base + jitter);
genTimerId = setTimeout(generateNewItem, interval);
}
function generateNewItem() { function generateNewItem() {
if (!props.isDivDescVisible) return; if (!props.isDivDescVisible) return;
const seed = Date.now() + itemIdCounter; const seed = Date.now() + itemIdCounter;
const kind: ItemKind = seededRandom(seed) < 0.55 ? 'coin' : 'bomb'; const kind: ItemKind = seededRandom(seed) < 0.75 ? "coin" : "bomb";
const lane: 'left' | 'right' = seededRandom(seed + 1000) < 0.5 ? 'left' : 'right';
// 概率分配车道,但避免与前一个同车道道具太近
const spacing = MIN_ITEM_SPACING + seededRandom(seed + 2000) * (MAX_ITEM_SPACING - MIN_ITEM_SPACING); let lane: "left" | "right" =
const startY = -ITEM_SIZE; seededRandom(seed + 1000) < 0.5 ? "left" : "right";
// 找到该车道还在顶部附近(y < 200)的未收集道具
const hasNearbyInLane = (ln: "left" | "right") =>
roadItems.value.some((it) => !it.collected && it.lane === ln && it.y < 200);
if (
hasNearbyInLane(lane) &&
!hasNearbyInLane(lane === "left" ? "right" : "left")
) {
// 首选车道已有最近道具,强制换到另一车道
lane = lane === "left" ? "right" : "left";
}
const newItem: RoadItem = { const newItem: RoadItem = {
id: itemIdCounter++, id: itemIdCounter++,
kind, kind,
lane, lane,
y: startY, y: -ITEM_SIZE,
collected: false, collected: false,
colliding: false,
animFrame: 0, animFrame: 0,
animTimer: undefined, animTimer: undefined,
}; };
roadItems.value.push(newItem); roadItems.value.push(newItem);
setTimeout(generateNewItem, ITEM_GEN_INTERVAL); scheduleNextItem();
} }
function stopItemGen() {
if (genTimerId) {
clearTimeout(genTimerId);
genTimerId = undefined;
}
}
// 道具 y 已是 screen-space(content-bg 坐标系),清理逻辑直接判断
function cleanupOffscreenItems() { function cleanupOffscreenItems() {
const screenBottom = roadOffset.value + ROAD_HEIGHT; roadItems.value = roadItems.value.filter((item) => {
roadItems.value = roadItems.value.filter(item => { // item.y 超出 content-bg 底部即删除,超出顶部也删除
const itemBottom = item.y + ITEM_SIZE; return item.y < ROAD_HEIGHT && item.y > -ITEM_SIZE;
return itemBottom > roadOffset.value - 100 && item.y < screenBottom + 100;
}); });
} }
// ========== 碰撞检测 ========== // ========== 碰撞检测 ==========
const horseBottomY = ROAD_HEIGHT * (HORSE_BOTTOM_PCT / 100); // 马匹在屏幕上的Y坐标(从 content-bg 顶部算起,horse 使用 bottom: 19.11% 定位)
const horseTopY = horseBottomY - HORSE_H; const horseCSSBottom = ROAD_HEIGHT * (1 - HORSE_BOTTOM_PCT / 100);
const horseCSSTop = horseCSSBottom - HORSE_H;
const checkCollisions = () => { const checkCollisions = () => {
const horseCenterX = horseX.value + HORSE_W / 2; const horseCenterX = horseX.value + HORSE_W / 2;
const horseTopCenterY = horseTopY + HORSE_H * 0.15; const horseTopCenterY = horseCSSTop + HORSE_H * 0.15;
const horseRadius = HORSE_W * 0.35; const horseRadius = HORSE_W * 0.35;
for (const item of roadItems.value) { for (const item of roadItems.value) {
if (item.collected) continue; if (item.collected || item.colliding) continue;
const itemX = item.lane === 'left' ? LEFT_LANE_X : RIGHT_LANE_X; const itemX = item.lane === "left" ? leftLaneX.value : rightLaneX.value;
const itemVisualY = item.y; // item.y 已经是屏幕坐标(content-bg 内),无需加 roadOffset
const itemCenterX = itemX + ITEM_SIZE / 2; const itemCenterX = itemX + ITEM_SIZE / 2;
const itemCenterY = itemVisualY + ITEM_SIZE / 2; const itemCenterY = item.y + ITEM_SIZE / 2;
const dist = Math.hypot(horseCenterX - itemCenterX, horseTopCenterY - itemCenterY); const dist = Math.hypot(
horseCenterX - itemCenterX,
horseTopCenterY - itemCenterY,
);
if (dist < horseRadius + ITEM_SIZE * 0.35) { if (dist < horseRadius + ITEM_SIZE * 0.35) {
// 立即标记碰撞中,防止后续帧重复触发
item.colliding = true;
// 先启动碰撞特效动画,延迟隐藏道具让动画和道具短暂重叠过渡
playCollectEffect(item, itemX, item.y);
setTimeout(() => {
item.collected = true; item.collected = true;
playCollectEffect(item, itemX, itemVisualY); }, 100);
} }
} }
}; };
const playCollectEffect = (item: RoadItem, x: number, y: number) => { const playCollectEffect = (item: RoadItem, x: number, y: number) => {
if (item.kind === 'coin') { const isCoin = item.kind === "coin";
emit('scoreChange', 5); const delta = isCoin ? 5 : -3;
playGame6Music(1);
if (isCoin) {
emit("scoreChange", 5);
} else { } else {
emit('scoreChange', -3); emit("scoreChange", -3);
playGame6Music(2);
} }
const animId = Date.now() + Math.random(); const animId = Date.now() + Math.random();
const totalFrames = item.kind === 'coin' ? JINBI_FRAME_COUNT : BOM_FRAME_COUNT; const totalFrames = isCoin ? JINBI_FRAME_COUNT : BOM_FRAME_COUNT;
const frameDuration = 1000 / totalFrames; const frameDuration = 1000 / totalFrames;
effectAnims.value.push({ id: animId, kind: item.kind, frame: 0, x, y }); // 碰撞动画(大幅放大尺寸,x 往右偏移让动画居中于视觉碰撞点)
effectAnims.value.push({
id: animId,
kind: item.kind,
frame: 0,
x: x + 65,
y,
});
let frameIdx = 0; let frameIdx = 0;
const tick = () => { const tick = () => {
frameIdx++; frameIdx++;
if (frameIdx >= totalFrames) { if (frameIdx >= totalFrames) {
effectAnims.value = effectAnims.value.filter(a => a.id !== animId); effectAnims.value = effectAnims.value.filter((a) => a.id !== animId);
return; return;
} }
const anim = effectAnims.value.find(a => a.id === animId); const anim = effectAnims.value.find((a) => a.id === animId);
if (anim) anim.frame = frameIdx; if (anim) anim.frame = frameIdx;
item.animTimer = setTimeout(tick, frameDuration); item.animTimer = setTimeout(tick, frameDuration);
}; };
item.animTimer = setTimeout(tick, frameDuration); item.animTimer = setTimeout(tick, frameDuration);
// 积分飘字效果(参考 game5 套中圈 "+10" 动画)
const effectId = ++scoreEffectIdSeq;
scoreEffects.value.push({
id: effectId,
x: x + ITEM_SIZE / 2 + 25,
y: y,
score: delta,
});
setTimeout(() => {
const idx = scoreEffects.value.findIndex((e) => e.id === effectId);
if (idx >= 0) scoreEffects.value.splice(idx, 1);
}, 800);
}; };
// ========== 主循环 ========== // ========== 主循环 ==========
...@@ -261,31 +367,35 @@ const startMainLoop = () => { ...@@ -261,31 +367,35 @@ const startMainLoop = () => {
mainLastTs = ts; mainLastTs = ts;
horseFrameAccum += dt; horseFrameAccum += dt;
while (horseFrameAccum >= 48) { const horseInterval = (80 * BASE_ROAD_SPEED) / currentRoadSpeed.value;
horseFrameAccum -= 48; while (horseFrameAccum >= horseInterval) {
horseFrameAccum -= horseInterval;
horseFrameIndex.value = (horseFrameIndex.value + 1) % HORSE_FRAME_COUNT; horseFrameIndex.value = (horseFrameIndex.value + 1) % HORSE_FRAME_COUNT;
} }
const lerpFactor = 1 - Math.pow(0.001, dt / 1000); const lerpFactor = 1 - Math.pow(0.001, dt / 1000);
horseX.value += (horseTargetX.value - horseX.value) * lerpFactor; horseX.value += (horseTargetX.value - horseX.value) * lerpFactor;
checkCollisions(); // 先移动所有道具
for (const item of roadItems.value) { for (const item of roadItems.value) {
if (!item.collected) { if (!item.collected) {
item.y += currentRoadSpeed.value * dt / 1000; item.y += (currentRoadSpeed.value * dt) / 1000;
} }
} }
// 清理已离开屏幕的道具,避免 roadOffset 回绕后旧道具闪现
cleanupOffscreenItems(); cleanupOffscreenItems();
// 最后再检测碰撞,确保只检测屏幕内确实可见的道具
checkCollisions();
mainRafId = requestAnimationFrame(tick); mainRafId = requestAnimationFrame(tick);
}; };
mainRafId = requestAnimationFrame(tick); mainRafId = requestAnimationFrame(tick);
perSecondTimer = setInterval(() => { perSecondTimer = setInterval(() => {
if (props.isDivDescVisible) { if (props.isDivDescVisible) {
emit('scoreChange', 2); emit("scoreChange", 2);
} }
}, 1000); }, 1000);
}; };
...@@ -304,58 +414,91 @@ const stopMainLoop = () => { ...@@ -304,58 +414,91 @@ const stopMainLoop = () => {
// ========== 生命周期 ========== // ========== 生命周期 ==========
onMounted(() => { onMounted(() => {
window.addEventListener('keydown', handleKeydown); window.addEventListener("keydown", handleKeydown);
window.addEventListener("resize", updateLanePositions);
window.visualViewport?.addEventListener("resize", updateLanePositions);
updateLanePositions();
// 初始化马匹在左侧跑道(使用计算后的实际 X 位置)
horseX.value = leftLaneX.value;
horseTargetX.value = leftLaneX.value;
nextTick(() => { nextTick(() => {
startRoadScroll(); startRoadScroll();
startMainLoop(); startMainLoop();
setTimeout(generateNewItem, 1000); scheduleNextItem();
}); });
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', handleKeydown); window.removeEventListener("keydown", handleKeydown);
window.removeEventListener("resize", updateLanePositions);
window.visualViewport?.removeEventListener("resize", updateLanePositions);
stopRoadScroll(); stopRoadScroll();
stopMainLoop(); stopMainLoop();
stopItemGen();
for (const item of roadItems.value) { for (const item of roadItems.value) {
if (item.animTimer) clearTimeout(item.animTimer); if (item.animTimer) clearTimeout(item.animTimer);
} }
roadItems.value = []; roadItems.value = [];
effectAnims.value = [];
scoreEffects.value = [];
}); });
watch(() => props.isDivDescVisible, (visible) => { watch(
() => props.isDivDescVisible,
(visible) => {
if (visible) { if (visible) {
nextTick(() => { nextTick(() => {
roadItems.value = []; roadItems.value = [];
effectAnims.value = []; effectAnims.value = [];
scoreEffects.value = [];
roadOffset.value = 0; roadOffset.value = 0;
gameElapsed.value = 0; gameElapsed.value = 0;
updateLanePositions();
horseX.value = leftLaneX.value;
horseTargetX.value = leftLaneX.value;
startRoadScroll(); startRoadScroll();
startMainLoop(); startMainLoop();
setTimeout(generateNewItem, 1000); scheduleNextItem();
}); });
} else { } else {
stopRoadScroll(); stopRoadScroll();
stopMainLoop(); stopMainLoop();
stopItemGen();
for (const item of roadItems.value) { for (const item of roadItems.value) {
if (item.animTimer) clearTimeout(item.animTimer); if (item.animTimer) clearTimeout(item.animTimer);
} }
roadItems.value = []; roadItems.value = [];
effectAnims.value = []; effectAnims.value = [];
scoreEffects.value = [];
} }
}, { immediate: true }); },
{ immediate: true },
);
// 游戏结束:暂停全部动画,显示结果页
// watch(() => props.isGameOver, (over) => {
// if (over) {
// stopRoadScroll();
// stopMainLoop();
// stopItemGen();
// }
// });
</script> </script>
<template> <template>
<div class="h5-page game-stage"> <div class="h5-page game-stage">
<div class="only-bg"></div> <div class="only-bg"></div>
<div class="content-bg"> <div class="content-bg">
<div class="road-track" :style="{ transform: `translateY(${roadOffset % ROAD_HEIGHT}px)` }"> <div
class="road-track"
:style="{ transform: `translateY(${roadOffset}px)` }"
>
<div class="road-inner"></div> <div class="road-inner"></div>
<div class="road-inner"></div> <div class="road-inner"></div>
<div class="road-inner"></div> <div class="road-inner"></div>
</div> </div>
<!-- 道具 --> <!-- 道具(屏幕坐标系,独立于跑道滚动) -->
<template v-for="item in roadItems" :key="item.id"> <template v-for="item in roadItems" :key="item.id">
<img <img
v-if="!item.collected" v-if="!item.collected"
...@@ -363,7 +506,7 @@ watch(() => props.isDivDescVisible, (visible) => { ...@@ -363,7 +506,7 @@ watch(() => props.isDivDescVisible, (visible) => {
class="road-item" class="road-item"
:class="`item-lane-${item.lane}`" :class="`item-lane-${item.lane}`"
:style="{ :style="{
top: `${item.y + roadOffset}px`, top: `${item.y}px`,
}" }"
/> />
</template> </template>
...@@ -377,8 +520,22 @@ watch(() => props.isDivDescVisible, (visible) => { ...@@ -377,8 +520,22 @@ watch(() => props.isDivDescVisible, (visible) => {
/> />
</template> </template>
<!-- 积分飘字 -->
<div
v-for="ef in scoreEffects"
:key="ef.id"
class="score-float"
:class="ef.score > 0 ? 'score-plus' : 'score-minus'"
:style="{ left: ef.x + 'px', top: ef.y + 'px' }"
>
{{ ef.score > 0 ? "+" : "" }}{{ ef.score }}
</div>
<!-- 马 --> <!-- 马 -->
<div class="horse-wrapper" :style="{ left: `${horseX}px`, bottom: `${HORSE_BOTTOM_PCT}%` }"> <div
class="horse-wrapper"
:style="{ left: `${horseX}px`, bottom: `${HORSE_BOTTOM_PCT}%` }"
>
<img <img
v-for="(url, idx) in horseFrames" v-for="(url, idx) in horseFrames"
:key="idx" :key="idx"
...@@ -401,6 +558,43 @@ watch(() => props.isDivDescVisible, (visible) => { ...@@ -401,6 +558,43 @@ watch(() => props.isDivDescVisible, (visible) => {
<div class="img-hammer"></div> <div class="img-hammer"></div>
<div class="img-left" @click="switchToLeft"></div> <div class="img-left" @click="switchToLeft"></div>
<div class="img-right" @click="switchToRight"></div> <div class="img-right" @click="switchToRight"></div>
<!-- 游戏结束结果页覆盖层 -->
<!-- <Transition name="rank-fade">
<div v-if="isGameOver" class="rank-overlay">
<div class="ranklist-container">
<div class="txt-title">排行</div>
<div class="list-container">
<div class="row row-1">
<div class="col-1">排名</div>
<div class="col-2">用户</div>
<div class="col-3">成绩</div>
</div>
<div class="row-2">
<div class="row rank-row" v-for="(item, index) in rankList" :key="`${item.rank}-${index}`">
<div class="col-1">
<div>{{ item.rank }}</div>
</div>
<div class="col-2">
<div class="avatar" :style="item.avatar ? { backgroundImage: `url(${item.avatar})` } : {}"></div>
<div>{{ item.nickname }}</div>
</div>
<div class="col-3">{{ item.score }}</div>
</div>
</div>
<div class="row row-3">
<div class="col-1">{{ userRank > 0 ? userRank : '未上榜' }}</div>
<div class="col-2">
<div class="avatar" :style="{ backgroundImage: `url(${userAvatar})` }"></div>
<div>{{ userNickname }}</div>
</div>
<div class="col-3">{{ userScore }}</div>
</div>
</div>
</div>
<div class="img-rank-close" @click="$emit('rankClose')"></div>
</div>
</Transition> -->
</div> </div>
</template> </template>
...@@ -418,7 +612,7 @@ watch(() => props.isDivDescVisible, (visible) => { ...@@ -418,7 +612,7 @@ watch(() => props.isDivDescVisible, (visible) => {
left: var(--stage-viewport-left, 0); left: var(--stage-viewport-left, 0);
width: var(--stage-viewport-width, 750px); width: var(--stage-viewport-width, 750px);
height: 421.9px; height: 421.9px;
background: v-bind('imageUrls.bg3') center / cover no-repeat; background: v-bind("imageUrls.bg3") center / cover no-repeat;
} }
.content-bg { .content-bg {
...@@ -434,7 +628,7 @@ watch(() => props.isDivDescVisible, (visible) => { ...@@ -434,7 +628,7 @@ watch(() => props.isDivDescVisible, (visible) => {
.road-track { .road-track {
width: 100%; width: 100%;
position: absolute; position: absolute;
top: 0; top: -1202.1px;
left: 0; left: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -443,32 +637,77 @@ watch(() => props.isDivDescVisible, (visible) => { ...@@ -443,32 +637,77 @@ watch(() => props.isDivDescVisible, (visible) => {
.road-inner { .road-inner {
width: 100%; width: 100%;
height: 1202.1px; height: 1202.1px;
background: v-bind('imageUrls.bg4') center / cover no-repeat; background: v-bind("imageUrls.bg4") center / cover no-repeat;
flex-shrink: 0; flex-shrink: 0;
} }
.road-item { .road-item {
position: absolute; position: absolute;
width: 80px; /* width: 80px;
height: 80px; height: 80px; */
width: 77px;
height: 96px;
z-index: 5; z-index: 5;
pointer-events: none; pointer-events: none;
} }
.item-lane-left { .item-lane-left {
left: 170px; /* 当 content-bg 宽于 750px 设计稿时,自动居中 750px 游戏区域 */
left: calc((var(--stage-viewport-width, 750px) - 750px) / 2 + 170px);
} }
.item-lane-right { .item-lane-right {
left: 470px; left: calc((var(--stage-viewport-width, 750px) - 750px) / 2 + 520px);
} }
.effect-anim { .effect-anim {
position: absolute; position: absolute;
width: 120px; width: 250px;
height: 120px; height: 250px;
z-index: 10; z-index: 10;
pointer-events: none; pointer-events: none;
transform: translate(-50%, -50%);
}
/* 积分飘字动画 */
.score-float {
position: absolute;
z-index: 25;
font-size: 56px;
font-weight: 900;
pointer-events: none;
white-space: nowrap;
transform: translate(-50%, -100%);
animation: score-float-up 0.8s ease-out forwards;
}
.score-plus {
color: #ffd700;
text-shadow:
0 3px 8px rgba(0, 0, 0, 0.7),
0 0 20px rgba(255, 215, 0, 0.5);
}
.score-minus {
color: #ff4444;
text-shadow:
0 3px 8px rgba(0, 0, 0, 0.7),
0 0 20px rgba(255, 68, 68, 0.5);
}
@keyframes score-float-up {
0% {
opacity: 1;
transform: translate(-50%, -100%) scale(0.5);
}
25% {
opacity: 1;
transform: translate(-50%, -140%) scale(1.15);
}
100% {
opacity: 0;
transform: translate(-50%, -260%) scale(0.8);
}
} }
.horse-wrapper { .horse-wrapper {
...@@ -499,7 +738,7 @@ watch(() => props.isDivDescVisible, (visible) => { ...@@ -499,7 +738,7 @@ watch(() => props.isDivDescVisible, (visible) => {
top: 174px; top: 174px;
left: 52%; left: 52%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
background: v-bind('imageUrls.hydt') center / cover; background: v-bind("imageUrls.hydt") center / cover;
} }
.img-left { .img-left {
...@@ -509,7 +748,7 @@ watch(() => props.isDivDescVisible, (visible) => { ...@@ -509,7 +748,7 @@ watch(() => props.isDivDescVisible, (visible) => {
left: 144px; left: 144px;
bottom: 5.48%; bottom: 5.48%;
z-index: 20; z-index: 20;
background: v-bind('imageUrls.left') center / cover no-repeat; background: v-bind("imageUrls.left") center / cover no-repeat;
cursor: pointer; cursor: pointer;
} }
...@@ -520,14 +759,14 @@ watch(() => props.isDivDescVisible, (visible) => { ...@@ -520,14 +759,14 @@ watch(() => props.isDivDescVisible, (visible) => {
right: 144px; right: 144px;
bottom: 5.48%; bottom: 5.48%;
z-index: 20; z-index: 20;
background: v-bind('imageUrls.right') center / cover no-repeat; background: v-bind("imageUrls.right") center / cover no-repeat;
cursor: pointer; cursor: pointer;
} }
.img-hammer { .img-hammer {
width: 148px; width: 148px;
height: 148px; height: 148px;
background: v-bind('imageUrls.hammer') center / cover; background: v-bind("imageUrls.hammer") center / cover;
position: absolute; position: absolute;
left: 50%; left: 50%;
bottom: 100px; bottom: 100px;
...@@ -555,7 +794,7 @@ watch(() => props.isDivDescVisible, (visible) => { ...@@ -555,7 +794,7 @@ watch(() => props.isDivDescVisible, (visible) => {
width: 38px; width: 38px;
height: 44px; height: 44px;
border-radius: 50%; border-radius: 50%;
background: v-bind('imageUrls.clock') center / cover; background: v-bind("imageUrls.clock") center / cover;
} }
.desc-time { .desc-time {
...@@ -591,12 +830,125 @@ watch(() => props.isDivDescVisible, (visible) => { ...@@ -591,12 +830,125 @@ watch(() => props.isDivDescVisible, (visible) => {
transform: translateX(0); transform: translateX(0);
} }
/* 游戏结束结果页覆盖层 */
.rank-overlay {
position: absolute;
inset: 0;
z-index: 100;
background: rgba(0, 0, 0, 0.65);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.ranklist-container {
width: 550px;
background: v-bind("imageUrls.ranklist") center / 100% 100% no-repeat;
border-radius: 16px;
padding: 60px 30px 30px;
display: flex;
flex-direction: column;
align-items: center;
.txt-title {
font-size: 36px;
font-weight: bold;
color: #aa0000;
letter-spacing: 2px;
margin-bottom: 16px;
}
.list-container {
width: 100%;
text-align: center;
line-height: 56px;
font-size: 24px;
.row {
display: grid;
grid-template-columns: 100px auto 120px;
align-items: center;
}
.row-1 {
color: #aa0000;
font-weight: bold;
background: linear-gradient(
-84deg,
#ffd396 0%,
#fff3d1 53%,
#ffd59c 100%
);
border-radius: 12px 12px 0 0;
}
.row-2 {
color: #633911;
max-height: 440px;
overflow-y: auto;
.rank-row:nth-child(odd) {
background: #f2e2ae;
}
.rank-row:nth-child(even) {
background: #fceebf;
}
}
.row-3 {
background: linear-gradient(0deg, #f4b64c 0%, #f7c96f 100%);
border-radius: 0 0 12px 12px;
font-weight: bold;
}
.col-2 {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
.avatar {
width: 40px;
height: 40px;
flex: 0 0 auto;
border-radius: 50%;
background-position: center;
background-size: cover;
}
}
}
}
.img-rank-close {
width: 48px;
height: 48px;
margin-top: 24px;
border-radius: 50%;
background: v-bind("imageUrls.close") center / cover no-repeat;
cursor: pointer;
}
.rank-fade-enter-active {
transition: opacity 0.35s ease-out;
}
.rank-fade-leave-active {
transition: opacity 0.25s ease-in;
}
.rank-fade-enter-from,
.rank-fade-leave-to {
opacity: 0;
}
.img-logo { .img-logo {
position: absolute; position: absolute;
top: 46px; top: 46px;
left: 20px; left: 20px;
width: 428px; width: 428px;
height: 77px; height: 77px;
background: v-bind('imageUrls.logo') center / cover; background: v-bind("imageUrls.logo") center / cover;
} }
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import { cssAssetUrl } from '@/commons/assets.ts' import { cssAssetUrl } from "@/commons/assets.ts";
import MobileStage from '@/components/MobileStage.vue' import MobileStage from "@/components/MobileStage.vue";
import { useGameSocket, sendGameMessage, userJoinStatus, joinSharedRoom } from '@/composables/useGameSocket' import {
import LoadingView from './views/LoadingView.vue' useGameSocket,
import PlayingView from './views/PlayingView.vue' sendGameMessage,
import ScoreView from './views/ScoreView.vue' userJoinStatus,
import { $getWechat, $toast } from '@/commons/utils.ts' joinSharedRoom,
} from "@/composables/useGameSocket";
const gameId = 'game4'; import LoadingView from "./views/LoadingView.vue";
type GameView = 'loading' | 'playing' | 'score' import PlayingView from "./views/PlayingView.vue";
import ScoreView from "./views/ScoreView.vue";
import { $getWechat, $toast } from "@/commons/utils.ts";
const gameId = "game4";
type GameView = "loading" | "playing" | "score";
const imageUrls = { const imageUrls = {
bg: cssAssetUrl('game4/h5-game4-bg.webp'), bg: cssAssetUrl("game4/h5-game4-bg.webp"),
bg2: cssAssetUrl('game1/bg2.png'), bg2: cssAssetUrl("game1/bg2.png"),
playBg: cssAssetUrl('game4/h5-game4-bg2.webp'), playBg: cssAssetUrl("game4/h5-game4-bg2.webp"),
logo: cssAssetUrl('game4/logo.webp'), logo: cssAssetUrl("game4/logo.webp"),
time: cssAssetUrl('game4/h5-game4-clock.svg'), time: cssAssetUrl("game4/h5-game4-clock.svg"),
title: cssAssetUrl('game4/h5-game4-title1.webp'), title: cssAssetUrl("game4/h5-game4-title1.webp"),
title2: cssAssetUrl('game4/h5-game4-title2.webp'), title2: cssAssetUrl("game4/h5-game4-title2.webp"),
// avatarBg: cssAssetUrl('game4/h5-game4-join.svg'), // avatarBg: cssAssetUrl('game4/h5-game4-join.svg'),
userBg: cssAssetUrl('game4/user-bg.webpp'), userBg: cssAssetUrl("game4/user-bg.webp"),
startBg: cssAssetUrl('game4/start-bg.webp'), startBg: cssAssetUrl("game4/start-bg.webp"),
gift: cssAssetUrl('game4/h5-game4-gift.webp'), gift: cssAssetUrl("game4/h5-game4-gift.webp"),
avatar: cssAssetUrl('game1/avatar.png'), avatar: cssAssetUrl("game1/avatar.png"),
gu: cssAssetUrl('game4/h5-game4-horse.webp'), gu: cssAssetUrl("game4/h5-game4-horse.webp"),
clock: cssAssetUrl('game1/clock.png'), clock: cssAssetUrl("game1/clock.png"),
horse: cssAssetUrl('game4/h5-game4-horse.webp'), horse: cssAssetUrl("game4/h5-game4-horse.webp"),
yaoyiyao: cssAssetUrl('game4/h5-game4-yaoyiyao.svg'), yaoyiyao: cssAssetUrl("game4/h5-game4-yaoyiyao.svg"),
scoreBg: cssAssetUrl('game1/score-bg.png'), scoreBg: cssAssetUrl("game1/score-bg.png"),
back: cssAssetUrl('game4/back.svg'), back: cssAssetUrl("game4/back.svg"),
replay: cssAssetUrl('game4/replay.svg'), replay: cssAssetUrl("game4/replay.svg"),
close: cssAssetUrl('game1/close.png'), close: cssAssetUrl("game1/close.png"),
rule: cssAssetUrl('game1/rule.png') rule: cssAssetUrl("game1/rule.png"),
} };
const wechat = $getWechat() const wechat = $getWechat();
const currentView = ref<GameView>('loading') const currentView = ref<GameView>("loading");
const stageBackground = computed(() => { const stageBackground = computed(() => {
if (currentView.value === 'playing') { if (currentView.value === "playing") {
return `${imageUrls.playBg} center/cover` return `${imageUrls.playBg} center/cover`;
} }
return `${imageUrls.bg} center/cover` return `${imageUrls.bg} center/cover`;
}) });
const countdownInterval = ref(60) const countdownInterval = ref(60);
const isDivDescVisible = ref(false) const isDivDescVisible = ref(false);
const tick = ref(0) // 点击次数/摇动次数 const tick = ref(0); // 点击次数/摇动次数
const rank = ref(0) // 排名 const rank = ref(0); // 排名
const currentHorse = ref(imageUrls.horse) const currentHorse = ref(imageUrls.horse);
const currentYaoyiyao = ref(imageUrls.yaoyiyao) const currentYaoyiyao = ref(imageUrls.yaoyiyao);
const token = ref(wechat?.token ?? '') const token = ref(wechat?.token ?? "");
const nickname = ref(wechat?.nickname ?? '') const nickname = ref(wechat?.nickname ?? "");
const avatar = ref(wechat?.avatar ?? '') const avatar = ref(wechat?.avatar ?? "");
const showGameRule = ref(true) const showGameRule = ref(true);
const mobileStageRef = ref<InstanceType<typeof MobileStage> | null>(null) const mobileStageRef = ref<InstanceType<typeof MobileStage> | null>(null);
const _offSocketMessage = ref<(() => void) | undefined>() const _offSocketMessage = ref<(() => void) | undefined>();
let gameCountdownTimer: ReturnType<typeof window.setTimeout> | undefined let gameCountdownTimer: ReturnType<typeof window.setTimeout> | undefined;
const confirmRefresh = (event: BeforeUnloadEvent) => { const confirmRefresh = (event: BeforeUnloadEvent) => {
event.preventDefault() event.preventDefault();
event.returnValue = '' event.returnValue = "";
} };
function setDivDescVisible(visible: boolean) { function setDivDescVisible(visible: boolean) {
isDivDescVisible.value = visible isDivDescVisible.value = visible;
} }
function stopGameCountdown() { function stopGameCountdown() {
if (gameCountdownTimer) { if (gameCountdownTimer) {
window.clearTimeout(gameCountdownTimer) window.clearTimeout(gameCountdownTimer);
gameCountdownTimer = undefined gameCountdownTimer = undefined;
} }
} }
...@@ -82,110 +87,106 @@ function submitScore(save: boolean = false) { ...@@ -82,110 +87,106 @@ function submitScore(save: boolean = false) {
if (save) { if (save) {
// item_num 游戏项目(1到6) // item_num 游戏项目(1到6)
// wechat 原始token // wechat 原始token
sendGameMessage('submit_score_save', { sendGameMessage("submit_score_save", {
score: tick.value, score: tick.value,
wechat: wechat.token_origin, wechat: wechat.token_origin,
item_num: 4, item_num: 4,
nickname: wechat.nickname, nickname: wechat.nickname,
avatar: wechat.avatar, avatar: wechat.avatar,
rank: rank.value, rank: rank.value,
}) });
} else { } else {
sendGameMessage('submit_score', { score: tick.value }) sendGameMessage("submit_score", { score: tick.value });
} }
} }
function startGameCountdown(seconds = 60) { function startGameCountdown(seconds = 60) {
stopGameCountdown() stopGameCountdown();
countdownInterval.value = seconds countdownInterval.value = seconds;
setDivDescVisible(true) setDivDescVisible(true);
const tickCountdown = () => { const tickCountdown = () => {
countdownInterval.value -= 1 countdownInterval.value -= 1;
if (countdownInterval.value <= 0) { if (countdownInterval.value <= 0) {
countdownInterval.value = 0 countdownInterval.value = 0;
gameCountdownTimer = undefined gameCountdownTimer = undefined;
setDivDescVisible(false) setDivDescVisible(false);
submitScore(true) submitScore(true);
showScoreView() showScoreView();
return return;
} }
gameCountdownTimer = window.setTimeout(tickCountdown, 1000) gameCountdownTimer = window.setTimeout(tickCountdown, 1000);
} };
gameCountdownTimer = window.setTimeout(tickCountdown, 1000) gameCountdownTimer = window.setTimeout(tickCountdown, 1000);
} }
function syncGameCountdown(seconds: number) { function syncGameCountdown(seconds: number) {
const syncedSeconds = Math.max(0, Math.floor(seconds)) const syncedSeconds = Math.max(0, Math.floor(seconds));
if (countdownInterval.value - syncedSeconds <= 1) return if (countdownInterval.value - syncedSeconds <= 1) return;
if (syncedSeconds === 0) { if (syncedSeconds === 0) {
countdownInterval.value = 0 countdownInterval.value = 0;
return return;
} }
startGameCountdown(syncedSeconds) startGameCountdown(syncedSeconds);
} }
function showLoadingView() { function showLoadingView() {
stopGameCountdown() stopGameCountdown();
currentView.value = 'loading' currentView.value = "loading";
setDivDescVisible(false) setDivDescVisible(false);
// alert('管理员关闭了游戏房间') // alert('管理员关闭了游戏房间')
$toast('管理员关闭了游戏房间') $toast("管理员关闭了游戏房间");
} }
function resetToLoadingView() { function resetToLoadingView() {
stopGameCountdown() stopGameCountdown();
tick.value = 0 tick.value = 0;
rank.value = 0 rank.value = 0;
countdownInterval.value = 60 countdownInterval.value = 60;
currentHorse.value = imageUrls.horse currentHorse.value = imageUrls.horse;
currentYaoyiyao.value = imageUrls.yaoyiyao currentYaoyiyao.value = imageUrls.yaoyiyao;
userJoinStatus.value = false userJoinStatus.value = false;
currentView.value = 'loading' currentView.value = "loading";
setDivDescVisible(false) setDivDescVisible(false);
} }
function startGameView() { function startGameView() {
stopGameCountdown() stopGameCountdown();
tick.value = 0 tick.value = 0;
currentHorse.value = imageUrls.horse currentHorse.value = imageUrls.horse;
currentView.value = 'playing' currentView.value = "playing";
startGameCountdown(60) startGameCountdown(60);
} }
function showScoreView() { function showScoreView() {
stopGameCountdown() stopGameCountdown();
setDivDescVisible(false) setDivDescVisible(false);
currentView.value = 'score' currentView.value = "score";
} }
function replayGame() { function replayGame() {
startGameView() startGameView();
} }
function backToWaiting() { function backToWaiting() {}
}
function handleRoomBack() {
} function handleRoomBack() {}
const touchHandler = () => { const touchHandler = () => {
tick.value += 5 tick.value += 5;
submitScore() submitScore();
} };
const showGameRuleHandler = () => { const showGameRuleHandler = () => {
showGameRule.value = !showGameRule.value showGameRule.value = !showGameRule.value;
} };
if (wechat) { if (wechat) {
const { offSocketMessage } = useGameSocket({ const { offSocketMessage } = useGameSocket({
gameId, gameId,
auth: { auth: {
...@@ -193,59 +194,75 @@ if (wechat) { ...@@ -193,59 +194,75 @@ if (wechat) {
}, },
onConnect: () => { onConnect: () => {
window.setTimeout(() => { window.setTimeout(() => {
const joined = joinSharedRoom(nickname.value, token.value, avatar.value, gameId) const joined = joinSharedRoom(
}, 200) nickname.value,
token.value,
avatar.value,
gameId,
);
}, 200);
}, },
onGameStart: async () => { onGameStart: async () => {
showGameRule.value = false showGameRule.value = false;
await mobileStageRef.value?.startCountdown() await mobileStageRef.value?.startCountdown();
startGameView() startGameView();
}, },
onScoreSubmitted: (is_save, data) => { onScoreSubmitted: (is_save, data) => {
const nextScore = Number(typeof data === 'object' ? data?.score : NaN) const nextScore = Number(typeof data === "object" ? data?.score : NaN);
const nextRank = Number(typeof data === 'object' ? data?.rank : data) const nextRank = Number(typeof data === "object" ? data?.rank : data);
const remainingSeconds = Number(typeof data === 'object' ? data?.remainingSeconds : NaN) const remainingSeconds = Number(
typeof data === "object" ? data?.remainingSeconds : NaN,
);
if (!is_save && Number.isFinite(nextScore) && nextScore >= 0) { if (!is_save && Number.isFinite(nextScore) && nextScore >= 0) {
tick.value = nextScore tick.value = nextScore;
} }
if (!is_save && Number.isFinite(remainingSeconds) && remainingSeconds >= 0) { if (
syncGameCountdown(remainingSeconds) !is_save &&
Number.isFinite(remainingSeconds) &&
remainingSeconds >= 0
) {
syncGameCountdown(remainingSeconds);
} }
if (Number.isFinite(nextRank) && nextRank > 0) { if (Number.isFinite(nextRank) && nextRank > 0) {
rank.value = nextRank rank.value = nextRank;
} }
return { code: 'CMYF', score: tick.value } return { code: "CMYF", score: tick.value };
}, },
onRescoreSubmitted: (_recount) => { onRescoreSubmitted: (_recount) => {
submitScore(true) submitScore(true);
}, },
onRoomClosed: showLoadingView, onRoomClosed: showLoadingView,
onRoomBack: handleRoomBack, onRoomBack: handleRoomBack,
onGameRecover:()=>{ onGameRecover: () => {
debugger debugger;
} },
}) });
_offSocketMessage.value = offSocketMessage _offSocketMessage.value = offSocketMessage;
} }
onMounted(() => { onMounted(() => {
document.title = '互动游戏 - 策马迎福' document.title = "互动游戏 - 策马迎福";
if (token.value) { if (token.value) {
window.addEventListener('beforeunload', confirmRefresh) window.addEventListener("beforeunload", confirmRefresh);
} }
}) });
onBeforeUnmount(() => { onBeforeUnmount(() => {
_offSocketMessage.value?.() _offSocketMessage.value?.();
stopGameCountdown() stopGameCountdown();
if (token.value) { if (token.value) {
window.removeEventListener('beforeunload', confirmRefresh) window.removeEventListener("beforeunload", confirmRefresh);
} }
}) });
</script> </script>
<template> <template>
<MobileStage v-if="token" :showGameRule="showGameRule" ref="mobileStageRef" :background="stageBackground"> <MobileStage
v-if="token"
:showGameRule="showGameRule"
ref="mobileStageRef"
:background="stageBackground"
>
<template #gameRule> <template #gameRule>
<div class="rule-container"> <div class="rule-container">
<div class="rule-txt-container"> <div class="rule-txt-container">
...@@ -263,23 +280,50 @@ onBeforeUnmount(() => { ...@@ -263,23 +280,50 @@ onBeforeUnmount(() => {
</div> </div>
</div> </div>
</template> </template>
<LoadingView v-if="currentView === 'loading'" :image-urls="imageUrls" :user-join-status="userJoinStatus" <LoadingView
@touchGameRule="showGameRuleHandler" /> v-if="currentView === 'loading'"
:image-urls="imageUrls"
:user-join-status="userJoinStatus"
@touchGameRule="showGameRuleHandler"
/>
<!-- <PlayingView v-else :image-urls="imageUrls" :tick="tick" :rank="rank" --> <!-- <PlayingView v-else :image-urls="imageUrls" :tick="tick" :rank="rank" -->
<PlayingView v-else-if="currentView === 'playing'" :image-urls="imageUrls" :tick="tick" :rank="rank" <PlayingView
:currentHorse="currentHorse" :currentYaoyiyao="currentYaoyiyao" :countdown-interval="countdownInterval" v-else-if="currentView === 'playing'"
:is-div-desc-visible="isDivDescVisible" @touch="touchHandler" /> :image-urls="imageUrls"
<ScoreView v-else :image-urls="imageUrls" :tick="tick" :level="rank" @replay="replayGame" :tick="tick"
@back="backToWaiting" /> :rank="rank"
:currentHorse="currentHorse"
:currentYaoyiyao="currentYaoyiyao"
:countdown-interval="countdownInterval"
:is-div-desc-visible="isDivDescVisible"
@touch="touchHandler"
/>
<ScoreView
v-else
:image-urls="imageUrls"
:tick="tick"
:level="rank"
@replay="replayGame"
@back="backToWaiting"
/>
</MobileStage> </MobileStage>
<div v-else style="text-align: center; width: 100vw; height: 100vh; line-height: 30; font-size: 20px;"> <div
v-else
style="
text-align: center;
width: 100vw;
height: 100vh;
line-height: 30;
font-size: 20px;
"
>
请使用微信扫码进入游戏 请使用微信扫码进入游戏
</div> </div>
</template> </template>
<style scoped> <style scoped>
.rule-container { .rule-container {
.rule-txt-container { .rule-txt-container {
background: v-bind('imageUrls.rule') center / cover no-repeat; background: v-bind("imageUrls.rule") center / cover no-repeat;
width: 692px; width: 692px;
height: 797px; height: 797px;
margin-top: -20%; margin-top: -20%;
...@@ -298,14 +342,14 @@ onBeforeUnmount(() => { ...@@ -298,14 +342,14 @@ onBeforeUnmount(() => {
font-size: 26pt; font-size: 26pt;
padding: 40px; padding: 40px;
margin-top: 10px; margin-top: 10px;
color: #AA0000; color: #aa0000;
} }
} }
.rule-close { .rule-close {
width: 57px; width: 57px;
height: 57px; height: 57px;
background: v-bind('imageUrls.close') center / cover no-repeat; background: v-bind("imageUrls.close") center / cover no-repeat;
transform: translateX(-50%) scale(1.5); transform: translateX(-50%) scale(1.5);
position: absolute; position: absolute;
top: -73px; top: -73px;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment