Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
P
paopao
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
cocktail-party
paopao
Commits
11dc456f
提交
11dc456f
authored
3月 19, 2026
作者:
lidongxu
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
修改倒计时架构
上级
16ed8fb7
隐藏空白字符变更
内嵌
并排
正在显示
4 个修改的文件
包含
155 行增加
和
7 行删除
+155
-7
main.js
big-screen/src/main.js
+48
-1
socket.js
big-screen/src/socket.js
+21
-1
stateManager.js
big-screen/src/stateManager.js
+25
-0
roomHandler.js
server/src/socket/roomHandler.js
+61
-5
没有找到文件。
big-screen/src/main.js
浏览文件 @
11dc456f
...
...
@@ -2,7 +2,7 @@
* 大屏展示页入口:支持多玩家并排渲染
*/
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
{
drawBackground
}
from
'./renderer/background.js'
import
{
drawBubbleGrid
}
from
'./renderer/bubbleGrid.js'
...
...
@@ -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
...
...
@@ -263,6 +307,9 @@ function loop() {
drawIdleScreen
(
ctx
,
SCREEN_WIDTH
,
SCREEN_HEIGHT
,
roomId
,
connStatus
,
SCREEN_NAME
,
frameCount
)
}
// ── 倒计时覆盖层(服务端驱动,所有端同步)──────────────────────────────
drawCountdownOverlay
(
SCREEN_WIDTH
*
totalSlots
)
requestAnimationFrame
(
loop
)
}
...
...
big-screen/src/socket.js
浏览文件 @
11dc456f
...
...
@@ -5,7 +5,7 @@
* 发送 → 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 服务器地址配置
...
...
@@ -166,6 +166,26 @@ function _dispatch(event, data) {
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 }
...
...
big-screen/src/stateManager.js
浏览文件 @
11dc456f
...
...
@@ -11,6 +11,9 @@ const playerStates = new Map()
/** 玩家队伍信息 Map<playerId, team> */
const
playerTeams
=
new
Map
()
/** 倒计时状态 */
let
countdownState
=
{
active
:
false
,
value
:
0
}
export
function
setCurrentRoom
(
roomId
)
{
currentRoomId
=
roomId
}
...
...
@@ -96,7 +99,29 @@ export function getPlayerState(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
()
{
playerStates
.
clear
()
playerTeams
.
clear
()
countdownState
=
{
active
:
false
,
value
:
0
}
}
server/src/socket/roomHandler.js
浏览文件 @
11dc456f
...
...
@@ -21,6 +21,12 @@ const roomPlayerCounter = new Map();
*/
const
roomTimers
=
new
Map
();
/**
* 开始前倒计时表:roomId → { timer }
* allReady 后启动3秒倒计时,每秒广播 room:countdown,结束后广播 room:gameStart
*/
const
countdownTimers
=
new
Map
();
/**
* 注册房间相关 WebSocket 事件处理
*
...
...
@@ -34,7 +40,9 @@ const roomTimers = new Map();
* room:created 创建成功确认
* room:joined 加入成功确认
* room:playerJoined 有新玩家加入,广播当前人数
* room:allReady 所有人到齐,通知各端开始游戏
* room:allReady 所有人到齐,通知各端进入倒计时
* room:countdown 服务端统一倒计时(每秒广播 value: 3/2/1)
* room:gameStart 倒计时结束,通知各端正式开始游戏
* room:state 转发给大屏
* room:gameOver 转发给大屏
* room:playerDisconnected 小游戏异常断开通知大屏
...
...
@@ -203,10 +211,8 @@ function registerRoomHandlers(ws, { broadcastToRoom, joinRoom, leaveAllRooms, ro
broadcastToRoom
(
rid
,
'room:allReady'
,
allReadyPayload
);
ws
.
sendEvent
(
'room:allReady'
,
allReadyPayload
);
// 启动服务端倒计时(gameDuration > 0 才限时)
if
(
gameDuration
>
0
)
{
_startRoomTimer
(
rid
,
gameDuration
,
session
.
id
,
broadcastToRoom
);
}
// 启动服务端统一倒计时 3→2→1→开始
_startGameCountdown
(
rid
,
gameDuration
,
session
.
id
,
broadcastToRoom
);
}
catch
(
err
)
{
console
.
error
(
'[room:join allReady] 错误:'
,
err
);
}
...
...
@@ -375,9 +381,59 @@ async function onRoomEmpty(roomId) {
});
_clearRoomTimer(roomId);
_clearGameCountdown(roomId);
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 给房间内所有人(小游戏 + 大屏)
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论