主题
07 把分数和时间显示在画面上
先创建能看见的静态文本,再让客户端把服务端快照显示到 HUD。
这课把“日志里的我的分数”搬到画面上。服务端规则保持第 06 课不变,结算后仍自动开始下一局。
开始前检查
第 06 课的客户端已经能收到正确快照。先停止试玩,打开场景与 UI 搭建指南,完成两个文本节点的创建、命名、布局和保存。
| Name,必须完全一致 | 类型 | 初始文字 |
|---|---|---|
label_time | EUITextLabel | 等待时间同步 |
label_point | EUITextLabel | 我的分数:0 |
位置和尺寸按搭建指南填写。节点可以位于 UI 根下的容器内,因为客户端会递归查找;不能在同一界面放多个同名节点。
1. 先看见静态 HUD
文件:先保留第 06 课 Lua 文件,不改代码。操作:保存 UI,试玩。
预期:画面上能看见两个静态文本,文字不出屏、不被裁切、不遮住推球操作。它们暂时不会随得分变化,这正好证明“UI 创建”和“数据更新”是两件事。看不到文字先修 UI 布局,不要进入下一步。

2. 安装本课客户端
文件:client/main.lua。操作:下载第 07 课检查点,整文件替换客户端入口并保存同步。包内的 server/main.lua 和 common/remote_events.lua 与第 06 课内容相同,跳读者可完整覆盖三个文件。
预期:启动后静态文字被当前服务端数据替换;命中后“我的分数”改变,倒计时逐秒减少。
3. 读懂节点获取与显示
文件:client/main.lua 的 hud 区域。操作:阅读解释片段,不追加到文件末尾。
lua
-- [获取 UI 根节点] 从本地玩家的 PlayerGui 中获取 EUI 树根
local function getRoot()
local player = Players.LocalPlayer
if not player or not player.PlayerGui then return nil end
local manager = player.PlayerGui.EuiManager
return manager and manager:GetRootNode()
end
-- [安全查找 UI 节点] 递归查找指定名字和类型的 UI 控件
local function findNode(root, name, expectedType)
local node = root:FindFirstChild(name, true)
if node and node:IsA(expectedType) then return node end
return nil
end
-- [HUD 文本刷新] 将快照状态转换为玩家可见的文字
local function updateHud()
local root = getRoot()
local labelTime = root and findNode(root, "label_time", "EUITextLabel")
local labelPoint = root and findNode(root, "label_point", "EUITextLabel")
-- 容错检查:如果场景中尚未搭好 UI 节点,只打印一次警告并自动持续重试
if not labelTime or not labelPoint then
if not missingUiLogged then
print("[推球][WARN] HUD 尚未就绪,请核对节点名称;脚本会自动重试")
missingUiLogged = true
end
return
end
if missingUiLogged then
print("[推球][HUD] 节点已就绪,恢复显示")
end
missingUiLogged = false
-- 状态 1:尚未收到服务端快照,显示等待连接提示
if not snapshot then
labelTime.Text = "正在连接服务端"
labelPoint.Text = "我的分数:—"
return
end
-- 状态 2:已建立同步,显示个人实时分数
labelPoint.Text = "我的分数:" .. tostring(snapshot.score)
-- 状态 3:根据局阶段展示倒计时或结算等待
if snapshot.phase == "running" then
local seconds = math.max(0, math.ceil(snapshot.endTime - World:GetServerTime()))
labelTime.Text = "剩余:" .. tostring(seconds) .. " 秒"
else
labelTime.Text = "本局结束,等待自动开局"
end
end获取路径是:本地 Player → PlayerGui → EuiManager → UI 根节点 → 名字匹配的具体文本节点。任何一段尚未加载好,都先返回,等待下一次更新。
完整客户端每 0.25 秒调用一次 updateHud,重新获取当前节点。缺失时只打印一次提示,节点后来就绪会自动恢复,不会不断建立新的消息监听。
倒计时取“服务器下发的结束时间 − 当前同步时间”。画面显示 0 不等于客户端已经结束回合;真正是否计分仍由服务器决定。
运行并核对
文件:无需修改。操作:推球命中两次;等到结算;再等自动下一局。
预期:自己的分数与服务端日志一致;剩余时间不显示负数;结算时显示“等待自动开局”;新局分数清零。缩小试玩窗口后两个标签仍需可读,必要时回到搭建指南调整布局。
如果日志有正确快照、画面却不更新,按 Name→EUITextLabel 类型→当前 UI 是否保存的顺序查。日志提示 HUD 尚未就绪时,脚本会继续尝试,不需要重复粘贴一份监听来“重试”。
自己改一处
文件:client/main.lua。操作:只把 labelPoint.Text 赋值中的“我的分数:”改成“这局得分:”。
预测:它是修改显示还是计分?提示:这行读取的是 snapshot.score。答案:显示名称变化,服务端积分不变。恢复原文字后,再把编辑器里 label_point 临时改名,试玩观察 WARN;停止试玩恢复正确名字、保存 UI 后再试,应该恢复显示。
保存进度
下载第 07 课完整代码。下一课:加上再来一局按钮。
API:Players、PlayerGui、EUIManager、EUITextLabel、World。想动态创建界面,阅读UI/EUI 专题。
