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

fix: 修复大屏马的生成数据和动画;

parent 2a8c14a9
...@@ -55,36 +55,48 @@ const renderPlayers = (data: any) => { ...@@ -55,36 +55,48 @@ const renderPlayers = (data: any) => {
} }
const updateRankPlayers = (data: any) => { const updateRankPlayers = (data: any) => {
if (!Array.isArray(data)) return // 兼容多种数据格式:纯数组 / {list:[]} / {players:[]} / {data:[]}
let arr: any[] | null = null;
if (Array.isArray(data)) {
arr = data;
} else if (Array.isArray(data?.list)) {
arr = data.list;
} else if (Array.isArray(data?.players)) {
arr = data.players;
} else if (Array.isArray(data?.data)) {
arr = data.data;
}
rankPlayers.value = Array.from( if (!arr || arr.length === 0) {
{ length: RANK_LIMIT }, console.warn('[game3] updateRankPlayers: 无法解析排行数据', data);
(_, index) => data[index] ?? emptyRankPlayer(index), return;
) }
}
const playerToRankPlayer = (player: any, index: number): Player => ({ // 打印第一条原始数据的所有字段名,用于排查字段不匹配问题
telephone: player?.telephone ?? '', console.log('[game3] updateRankPlayers: 收到排行数据', arr.length, '条, 第1条字段:', Object.keys(arr[0] || {}), ', 第1条值:', JSON.parse(JSON.stringify(arr[0])));
userid: player?.userid ?? '',
wechat_id: player?.wechat_id || `empty-${index}`, // 将 WS 数据标准化为 Player 格式(同时保留 userid / wechat_id)
nickname: player?.nickname || '虚位以待', // ⚠️ 当 WS 返回的数据没有 userid/openid/wechat_id 等ID字段时,用 avatar 作为唯一标识
avatar: player?.avatar || '', const normalized = arr.map((item: any, i: number) => {
score: player?.score ?? '-', const hasId = item.userid || item.openid || item.user_id || item.wechat_id;
}) const avatar = item.avatar || item.headimgurl || '';
const nickname = item.nickname || item.name || '虚位以待';
const isRealPlayer = nickname && nickname !== '-' && nickname !== '虚位以待';
const wechat_id = hasId || (isRealPlayer && avatar ? `avt:${avatar}` : `empty-${i}`);
return {
...item,
userid: hasId || (isRealPlayer && avatar ? `avt:${avatar}` : ''),
wechat_id,
nickname,
avatar,
score: item.score ?? 0,
};
});
const seedRankPlayersFromLoading = () => {
rankPlayers.value = Array.from( rankPlayers.value = Array.from(
{ length: RANK_LIMIT }, { length: RANK_LIMIT },
(_, index) => { (_, index) => normalized[index] ?? emptyRankPlayer(index),
const player = loadingPlayers.value[index] );
if (!player?.userid && !player?.wechat_id) {
return emptyRankPlayer(index)
}
return playerToRankPlayer(player, index)
},
)
} }
const resetRoundState = () => { const resetRoundState = () => {
...@@ -107,7 +119,7 @@ const gotoRank1WithIntro = async () => { ...@@ -107,7 +119,7 @@ const gotoRank1WithIntro = async () => {
if (isPlayingStartAnimation) return if (isPlayingStartAnimation) return
isPlayingStartAnimation = true isPlayingStartAnimation = true
seedRankPlayersFromLoading() // ⚠️ 不要用 seedRankPlayersFromLoading,由 WS room_rank_result 驱动马匹数据
screen.value = 'rank1' screen.value = 'rank1'
await nextTick() await nextTick()
await rank1Ref.value?.startIntro() await rank1Ref.value?.startIntro()
...@@ -129,8 +141,16 @@ function handleRoomState(data: any) { ...@@ -129,8 +141,16 @@ function handleRoomState(data: any) {
gotoLoading() gotoLoading()
break break
case 2: case 2:
seedRankPlayersFromLoading() // 重连/中途进房时游戏已在运行,直接进入赛马画面
if (isPlayingStartAnimation) break;
isPlayingStartAnimation = true;
screen.value = 'rank1' screen.value = 'rank1'
console.log('[game3] handleRoomState: status=2, 直接启动赛马动画');
nextTick(() => {
rank1Ref.value?.startIntro().finally(() => {
isPlayingStartAnimation = false;
});
});
break break
case 3: case 3:
screen.value = 'rank2' screen.value = 'rank2'
...@@ -143,24 +163,58 @@ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({ ...@@ -143,24 +163,58 @@ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
onAny: (evt, payload) => { onAny: (evt, payload) => {
// 兜底处理所有非标准事件,解析玩家实时分数(对齐 game4) // 兜底处理所有非标准事件,解析玩家实时分数(对齐 game4)
const data = payload?.data ?? payload; const data = payload?.data ?? payload;
if (!Array.isArray(data)) return;
// 尝试从 data 中提取数组
const boosts: Record<string, number> = {}; let arr: any[] | null = null;
let hasBoost = false; if (Array.isArray(data)) {
arr = data;
data.forEach((item: any) => { } else if (Array.isArray(data?.list)) {
const id = item.userid || item.wechat_id || item.avatar || ""; arr = data.list;
if (!id) return; } else if (Array.isArray(data?.data)) {
const raw = item.score ?? item.boost ?? item.acceleration ?? item.speed ?? item.progress; arr = data.data;
if (raw === undefined || raw === null) return; }
const val = Number(raw);
if (isNaN(val)) return; if (arr && arr.length > 0) {
boosts[id] = Math.max(0, val); const boosts: Record<string, number> = {};
hasBoost = true; const normalized = arr.map((item: any, i: number) => {
}); const hasId = item.userid || item.openid || item.user_id || item.wechat_id;
const avatar = item.avatar || item.headimgurl || '';
if (hasBoost) { const nickname = item.nickname || item.name || '虚位以待';
horseBoosts.value = { ...boosts }; const isRealPlayer = nickname && nickname !== '-' && nickname !== '虚位以待';
const wechat_id = hasId || (isRealPlayer && avatar ? `avt:${avatar}` : `empty-${i}`);
return {
...item,
userid: hasId || (isRealPlayer && avatar ? `avt:${avatar}` : ''),
wechat_id,
};
});
const hasAnyPlayer = normalized.some((item: any) => {
const id = item.userid || item.wechat_id || '';
return !!id && !id.startsWith('empty-');
});
if (hasAnyPlayer) {
// 同时更新 rankPlayers(确保马匹列表有真实玩家)
rankPlayers.value = Array.from(
{ length: RANK_LIMIT },
(_, index) => normalized[index] ?? emptyRankPlayer(index),
);
}
normalized.forEach((item: any) => {
const id = item.userid || item.wechat_id || '';
if (!id || id.startsWith('empty-')) return;
const raw = item.score ?? item.boost ?? item.acceleration ?? item.speed ?? item.progress;
if (raw === undefined || raw === null) return;
const val = Number(raw);
if (isNaN(val)) return;
boosts[id] = Math.max(0, val);
});
if (Object.keys(boosts).length > 0) {
horseBoosts.value = { ...horseBoosts.value, ...boosts };
}
} }
}, },
onRoomState: handleRoomState, onRoomState: handleRoomState,
...@@ -232,5 +286,7 @@ watch(screen, (value) => { ...@@ -232,5 +286,7 @@ watch(screen, (value) => {
<template> <template>
<Loading v-if="screen === 'loading'" :players="loadingPlayers" :player-count="playerCount" <Loading v-if="screen === 'loading'" :players="loadingPlayers" :player-count="playerCount"
@start="startGameWithMusic" @back="backHandler" /> @start="startGameWithMusic" @back="backHandler" />
<Rank1 v-else ref="rank1Ref" :rank-list="rankPlayers" :horse-boosts="horseBoosts" @start="startGameWithMusic" @next="nextRoundWithMusic" @back="backHandler" /> <Rank1 v-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" :horse-boosts="horseBoosts"
@start="startGameWithMusic" @next="nextRoundWithMusic" @back="backHandler" />
<Rank2 v-if="screen === 'rank2'" :rank-list="rankPlayers" @next="nextRoundWithMusic" @back="backHandler" />
</template> </template>
...@@ -44,6 +44,7 @@ const imageUrls = { ...@@ -44,6 +44,7 @@ const imageUrls = {
const HORSE_SIZE = 280; const HORSE_SIZE = 280;
const HORSE_START_X = -HORSE_SIZE; const HORSE_START_X = -HORSE_SIZE;
const HORSE_END_X = 1920; const HORSE_END_X = 1920;
const HORSE_Y_OFFSET = 80; // 马匹整体向下偏移,补偿帧图片中马匹偏上的空白区域
const horseFrameConfigs = [ const horseFrameConfigs = [
{ type: 1, dir: 'game3/ma1', prefix: '1ma_', startIndex: 24, count: 12 }, { type: 1, dir: 'game3/ma1', prefix: '1ma_', startIndex: 24, count: 12 },
...@@ -68,11 +69,13 @@ interface Horse { ...@@ -68,11 +69,13 @@ interface Horse {
lane: number; lane: number;
frameIndex: number; frameIndex: number;
frameTimer: number; frameTimer: number;
x: number; x: number; // 像素位置(用于渲染 translateX)
targetX: number; xPercent: number; // 进度百分比 0~100(对齐 game4 时间驱动模型)
boost: number; // 瞬时加速值,每帧衰减
nickname: string; nickname: string;
avatar: string; avatar: string;
wechat_id: string; // 用于匹配 WebSocket 实时排名数据 /** 稳定标识,优先 wechat_id 再 userid,用于匹配 WebSocket 数据 */
playerKey: string;
} }
const horses = ref<Horse[]>([]); const horses = ref<Horse[]>([]);
...@@ -87,21 +90,63 @@ const emptyPlayer = (index: number): Player => ({ ...@@ -87,21 +90,63 @@ const emptyPlayer = (index: number): Player => ({
}); });
const list = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyPlayer(index))); const list = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyPlayer(index)));
const playerKey = (player: Player, index = 0) => player.userid || player.wechat_id || `empty-${index}`;
const updateRankList = (rankList: Player[]) => { const updateRankList = (rankList: Player[]) => {
const next = Array.from({ length: RANK_LIMIT }, (_, index) => rankList[index] ?? emptyPlayer(index)); const next = Array.from({ length: RANK_LIMIT }, (_, index) => rankList[index] ?? emptyPlayer(index));
list.value = next; list.value = next;
}; };
// 玩家实时分数(合并 rankList + horseBoosts 双通道,对齐 game4 模式) // ========== 瞬时加速脉冲(对齐 game4 detectScoreChanges) ==========
const liveScores = ref<Record<string, number>>({}); const transientBoosts = ref<Record<string, number>>({});
const prevScores: Record<string, number> = {};
/** 获取玩家的稳定标识(优先 wechat_id,其次 userid,最后用 avatar 回退) */
const getPlayerKey = (player: Player, index = 0): string => {
const key = player?.wechat_id || player?.userid || '';
if (key && !key.startsWith('empty-')) return key;
// 如果 WS 数据没有ID字段,用 avatar URL 作为唯一标识(每个用户头像各不相同)
if (player?.nickname && player.nickname !== '-' && player.nickname !== '虚位以待' && player?.avatar) {
return `avt:${player.avatar}`;
}
return `empty-${index}`;
};
/** 检测 rankList 中哪些玩家 score 增长了 → 触发瞬时加速脉冲 */
const detectScoreChanges = (players: Player[]) => {
if (!players || players.length === 0) return;
let hasNewBoost = false;
const updated = { ...transientBoosts.value };
players.forEach((p, index) => {
const key = getPlayerKey(p, index);
if (key.startsWith('empty-')) return;
const newScore = Number(p.score ?? 0);
if (isNaN(newScore)) return;
const prev = prevScores[key] ?? 0;
if (newScore > prev) {
const current = updated[key] || 0;
updated[key] = Math.min(current + BOOST_PULSE, MAX_BOOST);
hasNewBoost = true;
console.log(`[game3 Rank1View] 🚀 ${p.nickname} score ${prev}${newScore}, boost: ${updated[key].toFixed(1)}`);
}
prevScores[key] = newScore;
});
if (hasNewBoost) {
transientBoosts.value = updated;
}
};
watch( watch(
() => props.rankList, () => props.rankList,
(rankList) => { (rankList) => {
console.log('[game3 Rank1View] websocket rankList 数据:', JSON.parse(JSON.stringify(rankList))); console.log('[game3 Rank1View] websocket rankList 数据:', JSON.parse(JSON.stringify(rankList?.slice(0, 3))));
updateRankList(rankList); updateRankList(rankList);
// 检测积分增长 → 触发瞬时加速脉冲(对齐 game4)
detectScoreChanges(rankList);
// 游戏进行中,实时更新马匹玩家列表(处理加入/离开) // 游戏进行中,实时更新马匹玩家列表(处理加入/离开)
if (isGameRunning.value) { if (isGameRunning.value) {
updateHorses(rankList); updateHorses(rankList);
...@@ -110,32 +155,63 @@ watch( ...@@ -110,32 +155,63 @@ watch(
{ immediate: true, deep: true }, { immediate: true, deep: true },
); );
// 直接监听 list,确保游戏运行时任何 list 变化都触发马匹刷新
watch(
list,
(newList) => {
const validCount = newList.filter(p => getPlayerKey(p).length > 0 && !getPlayerKey(p).startsWith('empty-')).length;
console.log('[game3 Rank1View] list 变化, 有效玩家数:', validCount, 'isGameRunning:', isGameRunning.value);
if (isGameRunning.value && validCount > 0) {
updateHorses(newList);
}
},
{ deep: true },
);
watch( watch(
() => props.horseBoosts, () => props.horseBoosts,
(boosts) => { (boosts) => {
if (!boosts || Object.keys(boosts).length === 0) return; if (!boosts || Object.keys(boosts).length === 0) return;
const merged = { ...liveScores.value };
for (const [id, score] of Object.entries(boosts)) { let hasNew = false;
// 取较大的值,因为不同通道可能收到不同时间点的数据 const updated = { ...transientBoosts.value };
if (score > (merged[id] ?? 0)) { Object.entries(boosts).forEach(([id, val]) => {
merged[id] = score; const score = Number(val ?? 0);
if (score > 0) {
const current = updated[id] || 0;
updated[id] = Math.min(current + BOOST_PULSE, MAX_BOOST);
hasNew = true;
} }
});
if (hasNew) {
transientBoosts.value = updated;
console.log('[game3 Rank1View] horseBoosts 触发加速:', JSON.parse(JSON.stringify(transientBoosts.value)));
} }
liveScores.value = merged;
}, },
{ deep: true }, { deep: true },
); );
// ========== 马匹动画循环 (RAF 驱动帧切换 + 分数驱动位移) ========== // ========== 速度基准(对齐 game4 时间驱动模型) ==========
// 基础速度大幅下调,确保马匹在 60s 游戏时间内不会过早跑出屏幕
const TOTAL_DISTANCE_PX = HORSE_END_X - HORSE_START_X; // 2200px
const BASE_DURATION_SEC = 150; // 基准时长 150s,即无加速时跑完全程需 150s
const BASE_SPEED_PX_PER_SEC = TOTAL_DISTANCE_PX / BASE_DURATION_SEC; // ≈ 14.7px/s
// 瞬时加速脉冲机制(对齐 game4)
const BOOST_PULSE = 0.3; // 每次 score 增长对应的额外速度倍率
const MAX_BOOST = 3.0; // 最大加速倍率
const BOOST_DECAY = 0.992; // 每帧衰减系数(60fps 约 3.6s 到 60%)
// ========== 马匹动画循环 (RAF 驱动:时间驱动位移 + 帧动画) ==========
let horseRafId: number | undefined; let horseRafId: number | undefined;
let horseLastTs = 0; let horseLastTs = 0;
const HORSE_FRAME_TICK_MS = 48; const HORSE_FRAME_TICK_MS = 48;
// 慢速 lerp,分数变化时马匹平滑过渡(约 2.5s 拉到位)
const HORSE_LERP_SPEED = 2.5;
const startHorseLoop = () => { const startHorseLoop = () => {
stopHorseLoop(); stopHorseLoop();
horseLastTs = 0; horseLastTs = 0;
let logCounter = 0;
const tick = (ts: number) => { const tick = (ts: number) => {
if (!isGameRunning.value) { if (!isGameRunning.value) {
...@@ -147,36 +223,29 @@ const startHorseLoop = () => { ...@@ -147,36 +223,29 @@ const startHorseLoop = () => {
horseLastTs = ts; horseLastTs = ts;
if (horses.value.length === 0) { if (horses.value.length === 0) {
if (logCounter++ % 60 === 0) {
console.log('[game3 Rank1View] RAF: horses 为空, gameStarted:', gameStarted.value, 'isGameRunning:', isGameRunning.value);
}
horseRafId = requestAnimationFrame(tick); horseRafId = requestAnimationFrame(tick);
return; return;
} }
// 优先用 liveScores(onAny 实时数据),回退到 list 分数 const newBoosts: Record<string, number> = {};
const getScore = (wechatId: string): number => {
const liveScore = liveScores.value[wechatId];
if (typeof liveScore === 'number' && liveScore > 0) return liveScore;
const player = list.value.find(p => p.wechat_id === wechatId);
return typeof player?.score === 'number' ? player.score : 0;
};
// 计算当前最高分
let maxScore = 0;
for (const horse of horses.value) { for (const horse of horses.value) {
const s = getScore(horse.wechat_id); if (horse.xPercent >= 100) continue;
if (s > maxScore) maxScore = s;
}
if (maxScore <= 0) maxScore = 1;
for (const horse of horses.value) { // 从 transientBoosts 读取实时加速值
const score = getScore(horse.wechat_id); const boost = transientBoosts.value[horse.playerKey] || 0;
horse.boost = boost;
// 进度 = 最小 5% + 根据分数占比最大 95% // 基础速度 + 加速 → 推进百分比
const progress = 0.05 + (score / maxScore) * 0.95; const effectiveSpeed = BASE_SPEED_PX_PER_SEC * (1 + boost);
horse.targetX = HORSE_START_X + (HORSE_END_X - HORSE_START_X) * progress; const deltaPx = effectiveSpeed * (dt / 1000);
horse.xPercent = Math.min(100, horse.xPercent + (deltaPx / TOTAL_DISTANCE_PX) * 100);
// 平滑 lerp,分数变化不跳变 // 像素位置 = 起始位置 + 百分比 * 总距离
const lerpFactor = 1 - Math.pow(0.001, dt / (1000 * HORSE_LERP_SPEED)); horse.x = HORSE_START_X + (horse.xPercent / 100) * TOTAL_DISTANCE_PX;
horse.x += (horse.targetX - horse.x) * lerpFactor;
// 帧动画 // 帧动画
const fc = horseFrameCount[horse.horseType] ?? 12; const fc = horseFrameCount[horse.horseType] ?? 12;
...@@ -185,8 +254,15 @@ const startHorseLoop = () => { ...@@ -185,8 +254,15 @@ const startHorseLoop = () => {
horse.frameTimer -= HORSE_FRAME_TICK_MS; horse.frameTimer -= HORSE_FRAME_TICK_MS;
horse.frameIndex = (horse.frameIndex + 1) % fc; horse.frameIndex = (horse.frameIndex + 1) % fc;
} }
// 加速衰减
const decayed = boost * Math.pow(BOOST_DECAY, dt / (1000 / 60));
if (decayed > 0.001) {
newBoosts[horse.playerKey] = decayed;
}
} }
transientBoosts.value = newBoosts;
horseRafId = requestAnimationFrame(tick); horseRafId = requestAnimationFrame(tick);
}; };
horseRafId = requestAnimationFrame(tick); horseRafId = requestAnimationFrame(tick);
...@@ -204,23 +280,27 @@ const LANE_TOP_POSITIONS = [480, 640, 800]; ...@@ -204,23 +280,27 @@ const LANE_TOP_POSITIONS = [480, 640, 800];
const updateHorses = (players: Player[]) => { const updateHorses = (players: Player[]) => {
const validPlayers = players.filter(p => { const validPlayers = players.filter(p => {
if (!p || !p.wechat_id) return false; const key = getPlayerKey(p);
if (p.wechat_id.startsWith('empty-')) return false; if (!key || key.startsWith('empty-')) return false;
if (p.nickname === '-' || p.nickname === '虚位以待') return false; if (p.nickname === '-' || p.nickname === '虚位以待') return false;
return true; return true;
}); });
console.log('[game3 Rank1View] updateHorses: 输入', players.length, '条, 有效', validPlayers.length, '条',
validPlayers.slice(0, 3).map(p => ({ key: getPlayerKey(p), name: p.nickname, score: p.score })));
if (validPlayers.length === 0) { if (validPlayers.length === 0) {
console.warn('[game3 Rank1View] updateHorses: 无有效玩家,horses 清空');
horses.value = []; horses.value = [];
return; return;
} }
// 保留已有马匹的 x 位置,防止重新生成时位置跳变 // 保留已有马匹的 x/xPercent/boost,防止重新生成时位置跳变
const existingMap = new Map(horses.value.map(h => [h.wechat_id, h])); const existingMap = new Map(horses.value.map(h => [h.playerKey, h]));
horses.value = validPlayers.map((player, index) => { horses.value = validPlayers.map((player, index) => {
const wid = player.wechat_id || ''; const key = getPlayerKey(player, index);
const existing = existingMap.get(wid); const existing = existingMap.get(key);
return { return {
id: existing?.id || `horse-${++horseIdCounter}`, id: existing?.id || `horse-${++horseIdCounter}`,
horseType: HORSE_TYPES[index % HORSE_TYPES.length]!, horseType: HORSE_TYPES[index % HORSE_TYPES.length]!,
...@@ -228,12 +308,15 @@ const updateHorses = (players: Player[]) => { ...@@ -228,12 +308,15 @@ const updateHorses = (players: Player[]) => {
frameIndex: existing?.frameIndex ?? Math.floor(Math.random() * (horseFrameCount[HORSE_TYPES[index % HORSE_TYPES.length]!] ?? 12)), frameIndex: existing?.frameIndex ?? Math.floor(Math.random() * (horseFrameCount[HORSE_TYPES[index % HORSE_TYPES.length]!] ?? 12)),
frameTimer: existing?.frameTimer ?? 0, frameTimer: existing?.frameTimer ?? 0,
x: existing?.x ?? HORSE_START_X, x: existing?.x ?? HORSE_START_X,
targetX: existing?.targetX ?? HORSE_START_X, xPercent: existing?.xPercent ?? 0,
boost: existing?.boost ?? 0,
nickname: player.nickname || '', nickname: player.nickname || '',
avatar: player.avatar || '', avatar: player.avatar || '',
wechat_id: wid, playerKey: key,
}; };
}); });
console.log('[game3 Rank1View] updateHorses: 生成', horses.value.length, '匹马');
}; };
// ========== 预加载马匹帧 ========== // ========== 预加载马匹帧 ==========
...@@ -248,17 +331,23 @@ const preloadHorseFrames = () => { ...@@ -248,17 +331,23 @@ const preloadHorseFrames = () => {
// ========== 游戏流程 ========== // ========== 游戏流程 ==========
const startIntro = async () => { const startIntro = async () => {
console.log('[game3 Rank1View] startIntro 开始');
showResult.value = false; showResult.value = false;
gameStarted.value = false; gameStarted.value = false;
isGameRunning.value = false; isGameRunning.value = false;
horses.value = []; horses.value = [];
horseIdCounter = 0; horseIdCounter = 0;
// 重置加速状态
transientBoosts.value = {};
Object.keys(prevScores).forEach(k => delete prevScores[k]);
stopApplauseMusic(); stopApplauseMusic();
preloadHorseFrames(); preloadHorseFrames();
const countdownPromise = designStage.value?.startCountdown(3, 60); const countdownPromise = designStage.value?.startCountdown(3, 60);
await new Promise((r) => setTimeout(r, 3500)); await new Promise((r) => setTimeout(r, 3500));
const validCount = list.value.filter(p => getPlayerKey(p).length > 0 && !getPlayerKey(p).startsWith('empty-')).length;
console.log('[game3 Rank1View] startIntro: 3.5s 后, list 有效玩家数:', validCount);
gameStarted.value = true; gameStarted.value = true;
isGameRunning.value = true; isGameRunning.value = true;
updateHorses(list.value); updateHorses(list.value);
...@@ -300,27 +389,27 @@ onMounted(() => { ...@@ -300,27 +389,27 @@ onMounted(() => {
if ($is_run_local()) { if ($is_run_local()) {
// 本地模式:用模拟玩家填充 list,驱动马匹渲染 // 本地模式:用模拟玩家填充 list,驱动马匹渲染
list.value = [ list.value = [
{ wechat_id: 'local-1', nickname: '骑手一', avatar: '', score: 45 }, { userid: 'local-1', nickname: '骑手一', avatar: '', score: 45 },
{ wechat_id: 'local-2', nickname: '骑手二', avatar: '', score: 38 }, { userid: 'local-2', nickname: '骑手二', avatar: '', score: 38 },
{ wechat_id: 'local-3', nickname: '骑手三', avatar: '', score: 30 }, { userid: 'local-3', nickname: '骑手三', avatar: '', score: 30 },
{ wechat_id: 'local-4', nickname: '骑手四', avatar: '', score: 22 }, { userid: 'local-4', nickname: '骑手四', avatar: '', score: 22 },
{ wechat_id: 'local-5', nickname: '骑手五', avatar: '', score: 15 }, { userid: 'local-5', nickname: '骑手五', avatar: '', score: 15 },
]; ];
gameStarted.value = true; gameStarted.value = true;
isGameRunning.value = true; isGameRunning.value = true;
updateHorses(list.value); updateHorses(list.value);
startHorseLoop(); startHorseLoop();
// 模拟分数变化 // 模拟分数变化 → 驱动 detectScoreChanges
let simTick = 0; let simTick = 0;
const simInterval = setInterval(() => { const simInterval = setInterval(() => {
if (!isGameRunning.value) { clearInterval(simInterval); return; } if (!isGameRunning.value) { clearInterval(simInterval); return; }
simTick++; simTick++;
// 随机涨分
for (const p of list.value) { for (const p of list.value) {
if (typeof p.score === 'number') { if (typeof p.score === 'number') {
p.score += Math.floor(Math.random() * 3); p.score += Math.floor(Math.random() * 3);
} }
} }
detectScoreChanges(list.value);
}, 1500); }, 1500);
} }
}); });
...@@ -351,7 +440,7 @@ onUnmounted(() => { ...@@ -351,7 +440,7 @@ onUnmounted(() => {
:key="horse.id" :key="horse.id"
class="horse-wrapper" class="horse-wrapper"
:style="{ :style="{
top: `${LANE_TOP_POSITIONS[horse.lane]}px`, top: `${(LANE_TOP_POSITIONS[horse.lane] ?? 560) + HORSE_Y_OFFSET}px`,
transform: `translateX(${horse.x}px)`, transform: `translateX(${horse.x}px)`,
}" }"
> >
...@@ -556,7 +645,7 @@ onUnmounted(() => { ...@@ -556,7 +645,7 @@ onUnmounted(() => {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
padding-left: 4px; padding-left: 6px;
box-sizing: border-box; box-sizing: border-box;
} }
......
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