Skip to content

07 把分数和时间显示在画面上 ​

先创建能看见的静态文本,再让客户端把服务端快照显示到 HUD。

这课把“日志里的我的分数”搬到画面上。服务端规则保持第 06 课不变,结算后仍自动开始下一局。

开始前检查 ​

第 06 课的客户端已经能收到正确快照。先停止试玩,打开场景与 UI 搭建指南,完成两个文本节点的创建、命名、布局和保存。

Name,必须完全一致类型初始文字
label_timeEUITextLabel等待时间同步
label_pointEUITextLabel我的分数: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 专题。