提交 11dc456f authored 作者: lidongxu's avatar lidongxu

修改倒计时架构

上级 16ed8fb7
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* 大屏展示页入口:支持多玩家并排渲染 * 大屏展示页入口:支持多玩家并排渲染
*/ */
import { initScaler } from './scaler.js' import { initScaler } from './scaler.js'
import { getAllPlayerStates, clearGameState, getCurrentRoom, getPlayerTeam } from './stateManager.js' import { getAllPlayerStates, clearGameState, getCurrentRoom, getPlayerTeam, getCountdown } from './stateManager.js'
import { initSocket, getConnectionStatus } from './socket.js' import { initSocket, getConnectionStatus } from './socket.js'
import { drawBackground } from './renderer/background.js' import { drawBackground } from './renderer/background.js'
import { drawBubbleGrid } from './renderer/bubbleGrid.js' import { drawBubbleGrid } from './renderer/bubbleGrid.js'
...@@ -167,6 +167,50 @@ function drawTeamLabel(x, team) { ...@@ -167,6 +167,50 @@ function drawTeamLabel(x, team) {
// ─── 倒计时覆盖层 ─────────────────────────────────────────────────────────────
function drawCountdownOverlay(totalWidth) {
const countdown = getCountdown()
if (!countdown.active) return
const cx = totalWidth / 2
const cy = SCREEN_HEIGHT / 2
ctx.save()
ctx.setTransform(1, 0, 0, 1, 0, 0)
// 半透明遮罩
ctx.fillStyle = 'rgba(0,0,0,0.6)'
ctx.fillRect(0, 0, totalWidth, SCREEN_HEIGHT)
// 脉冲效果
const pulse = 1 + 0.08 * Math.sin(frameCount * 0.15)
const fontSize = Math.min(totalWidth, SCREEN_HEIGHT) * 0.3 * pulse
// 倒计时数字
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.font = `bold ${fontSize}px Arial`
ctx.shadowColor = 'rgba(251,191,36,0.8)'
ctx.shadowBlur = 30
const numGrad = ctx.createLinearGradient(cx - 60, cy - 40, cx + 60, cy + 40)
numGrad.addColorStop(0, '#FDE68A')
numGrad.addColorStop(0.5, '#FCD34D')
numGrad.addColorStop(1, '#F59E0B')
ctx.fillStyle = numGrad
ctx.fillText(String(countdown.value), cx, cy)
ctx.shadowBlur = 0
// "游戏即将开始" 文字
const subFontSize = Math.min(totalWidth, SCREEN_HEIGHT) * 0.06
ctx.font = `bold ${subFontSize}px Arial`
ctx.fillStyle = 'rgba(196,181,253,0.9)'
ctx.fillText('游戏即将开始', cx, cy + SCREEN_HEIGHT * 0.15)
ctx.restore()
}
// ─── 主循环 ─────────────────────────────────────────────────────────────────── // ─── 主循环 ───────────────────────────────────────────────────────────────────
let _lastPlayerCount = 1 let _lastPlayerCount = 1
...@@ -263,6 +307,9 @@ function loop() { ...@@ -263,6 +307,9 @@ function loop() {
drawIdleScreen(ctx, SCREEN_WIDTH, SCREEN_HEIGHT, roomId, connStatus, SCREEN_NAME, frameCount) drawIdleScreen(ctx, SCREEN_WIDTH, SCREEN_HEIGHT, roomId, connStatus, SCREEN_NAME, frameCount)
} }
// ── 倒计时覆盖层(服务端驱动,所有端同步)──────────────────────────────
drawCountdownOverlay(SCREEN_WIDTH * totalSlots)
requestAnimationFrame(loop) requestAnimationFrame(loop)
} }
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
* 发送 → JSON.stringify({ event: string, data: any }) * 发送 → JSON.stringify({ event: string, data: any })
* 接收 ← JSON.stringify({ event: string, data: any }) * 接收 ← JSON.stringify({ event: string, data: any })
*/ */
import { setCurrentRoom, setGameState, getPlayerState, clearGameState, setPlayerGameOver, setAllGameOver, setPlayerTeam } from './stateManager.js' import { setCurrentRoom, setGameState, getPlayerState, clearGameState, setPlayerGameOver, setAllGameOver, setPlayerTeam, setCountdown, clearCountdown } from './stateManager.js'
/** /**
* WebSocket 服务器地址配置 * WebSocket 服务器地址配置
...@@ -166,6 +166,26 @@ function _dispatch(event, data) { ...@@ -166,6 +166,26 @@ function _dispatch(event, data) {
break break
} }
/**
* 服务端统一倒计时(3→2→1)
* data: { roomId, value }
*/
case 'room:countdown': {
console.log('[Socket] room:countdown, value:', data?.value)
setCountdown(data?.value ?? 0)
break
}
/**
* 服务端通知游戏正式开始
* data: { roomId, sessionId, gameDuration }
*/
case 'room:gameStart': {
console.log('[Socket] room:gameStart')
clearCountdown()
break
}
/** /**
* 服务端倒计时到期,所有玩家强制结束 * 服务端倒计时到期,所有玩家强制结束
* data: { roomId, durationSec } * data: { roomId, durationSec }
......
...@@ -11,6 +11,9 @@ const playerStates = new Map() ...@@ -11,6 +11,9 @@ const playerStates = new Map()
/** 玩家队伍信息 Map<playerId, team> */ /** 玩家队伍信息 Map<playerId, team> */
const playerTeams = new Map() const playerTeams = new Map()
/** 倒计时状态 */
let countdownState = { active: false, value: 0 }
export function setCurrentRoom(roomId) { export function setCurrentRoom(roomId) {
currentRoomId = roomId currentRoomId = roomId
} }
...@@ -96,7 +99,29 @@ export function getPlayerState(playerId) { ...@@ -96,7 +99,29 @@ export function getPlayerState(playerId) {
return playerStates.get(playerId) return playerStates.get(playerId)
} }
/**
* 设置倒计时值(服务端 room:countdown 驱动)
*/
export function setCountdown(value) {
countdownState = { active: true, value }
}
/**
* 清除倒计时(游戏开始后)
*/
export function clearCountdown() {
countdownState = { active: false, value: 0 }
}
/**
* 获取当前倒计时状态
*/
export function getCountdown() {
return countdownState
}
export function clearGameState() { export function clearGameState() {
playerStates.clear() playerStates.clear()
playerTeams.clear() playerTeams.clear()
countdownState = { active: false, value: 0 }
} }
...@@ -21,6 +21,12 @@ const roomPlayerCounter = new Map(); ...@@ -21,6 +21,12 @@ const roomPlayerCounter = new Map();
*/ */
const roomTimers = new Map(); const roomTimers = new Map();
/**
* 开始前倒计时表:roomId → { timer }
* allReady 后启动3秒倒计时,每秒广播 room:countdown,结束后广播 room:gameStart
*/
const countdownTimers = new Map();
/** /**
* 注册房间相关 WebSocket 事件处理 * 注册房间相关 WebSocket 事件处理
* *
...@@ -34,7 +40,9 @@ const roomTimers = new Map(); ...@@ -34,7 +40,9 @@ const roomTimers = new Map();
* room:created 创建成功确认 * room:created 创建成功确认
* room:joined 加入成功确认 * room:joined 加入成功确认
* room:playerJoined 有新玩家加入,广播当前人数 * room:playerJoined 有新玩家加入,广播当前人数
* room:allReady 所有人到齐,通知各端开始游戏 * room:allReady 所有人到齐,通知各端进入倒计时
* room:countdown 服务端统一倒计时(每秒广播 value: 3/2/1)
* room:gameStart 倒计时结束,通知各端正式开始游戏
* room:state 转发给大屏 * room:state 转发给大屏
* room:gameOver 转发给大屏 * room:gameOver 转发给大屏
* room:playerDisconnected 小游戏异常断开通知大屏 * room:playerDisconnected 小游戏异常断开通知大屏
...@@ -203,10 +211,8 @@ function registerRoomHandlers(ws, { broadcastToRoom, joinRoom, leaveAllRooms, ro ...@@ -203,10 +211,8 @@ function registerRoomHandlers(ws, { broadcastToRoom, joinRoom, leaveAllRooms, ro
broadcastToRoom(rid, 'room:allReady', allReadyPayload); broadcastToRoom(rid, 'room:allReady', allReadyPayload);
ws.sendEvent('room:allReady', allReadyPayload); ws.sendEvent('room:allReady', allReadyPayload);
// 启动服务端倒计时(gameDuration > 0 才限时) // 启动服务端统一倒计时 3→2→1→开始
if (gameDuration > 0) { _startGameCountdown(rid, gameDuration, session.id, broadcastToRoom);
_startRoomTimer(rid, gameDuration, session.id, broadcastToRoom);
}
} catch (err) { } catch (err) {
console.error('[room:join allReady] 错误:', err); console.error('[room:join allReady] 错误:', err);
} }
...@@ -375,9 +381,59 @@ async function onRoomEmpty(roomId) { ...@@ -375,9 +381,59 @@ async function onRoomEmpty(roomId) {
}); });
_clearRoomTimer(roomId); _clearRoomTimer(roomId);
_clearGameCountdown(roomId);
console.log(`[Room] 房间 ${roomId} 已标记为 finished(连接归零触发)`); console.log(`[Room] 房间 ${roomId} 已标记为 finished(连接归零触发)`);
} }
/**
* 启动游戏开始前的统一倒计时(3→2→1→开始)
* 由服务端控制节奏,每秒广播 room:countdown,结束后广播 room:gameStart
* 解决各客户端本地 setInterval 不同步、跳帧的问题
*/
function _startGameCountdown(roomId, gameDuration, sessionId, broadcastToRoom) {
// 清理可能存在的旧倒计时
_clearGameCountdown(roomId);
let remaining = 3;
// 立即广播第一个倒计时值(3)
broadcastToRoom(roomId, 'room:countdown', { roomId, value: remaining });
console.log(`[Room] 房间 ${roomId} 倒计时: ${remaining}`);
const timer = setInterval(() => {
remaining--;
if (remaining > 0) {
// 广播 2, 1
broadcastToRoom(roomId, 'room:countdown', { roomId, value: remaining });
console.log(`[Room] 房间 ${roomId} 倒计时: ${remaining}`);
} else {
// 倒计时结束,广播 room:gameStart
clearInterval(timer);
countdownTimers.delete(roomId);
broadcastToRoom(roomId, 'room:gameStart', { roomId, sessionId, gameDuration });
console.log(`[Room] 房间 ${roomId} 倒计时结束,游戏开始`);
// 游戏正式开始后才启动游戏时长计时器
if (gameDuration > 0) {
_startRoomTimer(roomId, gameDuration, sessionId, broadcastToRoom);
}
}
}, 1000);
countdownTimers.set(roomId, { timer });
}
/**
* 清理游戏开始前倒计时
*/
function _clearGameCountdown(roomId) {
const entry = countdownTimers.get(roomId);
if (entry) {
clearInterval(entry.timer);
countdownTimers.delete(roomId);
}
}
/** /**
* 启动房间服务端倒计时 * 启动房间服务端倒计时
* 时间到后广播 room:timeUp 给房间内所有人(小游戏 + 大屏) * 时间到后广播 room:timeUp 给房间内所有人(小游戏 + 大屏)
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论