返回博客
Tutorial3 分钟阅读

FiveM HUD 开发:设计简洁快速的玩家 HUD

使用 HTML 和 JS 构建高性能 FiveM HUD。生命值、耐力、语音、速度和小地图覆盖层,以及最佳 HUD 脚本供学习或购买。

Agency Scripts

Agency Scripts 创始人兼首席开发者

为什么要打造自定义 HUD

默认 GTA V HUD 设计用于单人动作游戏,不适合 FiveM 服务器复杂角色扮演场景。自定义 HUD 可显示饥饿、口渴、压力和职位状态等角色扮演专用信息,这些在原版 GTA 中不存在。功能之外,自定义 HUD 定义服务器视觉身份,从玩家生成时刻设定基调。玩家立即感受到运行默认 UI 元素的服务器与拥有精致、专用 HUD 且符合品牌主题服务器的质量差异。从零构建 HUD 还意味着你掌控性能每一方面,确保覆盖层以最低资源成本运行,同时提供玩家所需信息。

NUI 资源结构

FiveM 使用 NUI(新用户界面)在游戏客户端内渲染 HTML、CSS 和 JavaScript。你的 HUD 资源需要特定文件夹结构才能正常工作。 fxmanifest.lua 文件声明资源元数据, client.lua 脚本将游戏数据发送到NUI层,且 html/ 包含您的基于网页界面的文件夹。以下是一个入门的最小资源清单:

fx_version 'cerulean'
game 'gta5'

description 'Custom HUD'
author 'YourName'
version '1.0.0'

ui_page 'html/index.html'

client_script 'client.lua'

files {
    'html/index.html',
    'html/style.css',
    'html/script.js',
    'html/fonts/*.woff2'
}

ui_page directive tells FiveM which HTML file to render as the NUI overlay, and the files 该表列出浏览器需要访问的所有资源。如果忘记将CSS文件或字体包含在此列表中,浏览器将默默加载失败且无错误信息,这常令NUI开发新手困惑。通过分离关注点保持资源结构清晰:HTML用于布局,CSS用于样式,JavaScript用于逻辑和动画。

向UI发送游戏数据

客户端Lua脚本负责读取游戏状态并定期推送到NUI层。需要收集生命值、护甲以及任何框架特定的状态值,如饥饿和口渴。关键函数是 SendNUIMessage(),向NUI框架中运行的JavaScript发送JSON负载。更新频率需策略性把控,因为每帧发送数据浪费CPU周期,更新过慢则HUD感觉卡顿。状态条的更新间隔200-500毫秒达到平衡:

CreateThread(function()
    while true do
        local ped = PlayerPedId()
        local health = GetEntityHealth(ped) - 100  -- GTA health starts at 100
        local maxHealth = GetEntityMaxHealth(ped) - 100
        local armor = GetPedArmour(ped)

        -- Framework-specific data (QBCore example)
        local playerData = QBCore.Functions.GetPlayerData()
        local hunger = playerData.metadata['hunger'] or 100
        local thirst = playerData.metadata['thirst'] or 100
        local stress = playerData.metadata['stress'] or 0

        SendNUIMessage({
            action = 'updateStatus',
            health = math.floor((health / maxHealth) * 100),
            armor = armor,
            hunger = math.floor(hunger),
            thirst = math.floor(thirst),
            stress = math.floor(stress)
        })

        Wait(300)
    end
end)

对于速度表,您需要一个以更快节奏运行的独立线程,因为速度变化迅速,玩家期望驾驶时获得实时反馈。50-100毫秒的间隔适合车辆数据。仅当玩家实际在车辆内时运行速度表线程以节省资源,玩家下车时禁用:

CreateThread(function()
    while true do
        local ped = PlayerPedId()
        if IsPedInAnyVehicle(ped, false) then
            local veh = GetVehiclePedIsIn(ped, false)
            local speed = GetEntitySpeed(veh) * 3.6  -- Convert to km/h
            local rpm = GetVehicleCurrentRpm(veh)
            local gear = GetVehicleCurrentGear(veh)
            local fuel = GetVehicleFuelLevel(veh)

            SendNUIMessage({
                action = 'updateVehicle',
                speed = math.floor(speed),
                rpm = rpm,
                gear = gear,
                fuel = math.floor(fuel)
            })
            Wait(50)
        else
            SendNUIMessage({ action = 'hideVehicle' })
            Wait(500)
        end
    end
end)

构建 HTML 和 CSS 界面

HUD的视觉设计决定了游戏中的感受。现代FiveM HUD采用干净、简约的设计,带有半透明背景、圆角和细微动画。将状态栏放置在左下角,不阻碍游戏操作,玩家驾驶时将速度计放在右下角。使用CSS自定义属性设置颜色,这样只需更改几个变量即可轻松更换整个HUD主题。带有平滑过渡的动画进度条使HUD更显精致:

<!-- html/index.html -->
<div id="hud-container">
    <div class="status-bars">
        <div class="bar-wrapper">
            <i class="icon health-icon"></i>
            <div class="bar">
                <div class="bar-fill health-fill" id="health-bar"></div>
            </div>
        </div>
        <div class="bar-wrapper">
            <i class="icon armor-icon"></i>
            <div class="bar">
                <div class="bar-fill armor-fill" id="armor-bar"></div>
            </div>
        </div>
        <div class="bar-wrapper">
            <i class="icon hunger-icon"></i>
            <div class="bar">
                <div class="bar-fill hunger-fill" id="hunger-bar"></div>
            </div>
        </div>
        <div class="bar-wrapper">
            <i class="icon thirst-icon"></i>
            <div class="bar">
                <div class="bar-fill thirst-fill" id="thirst-bar"></div>
            </div>
        </div>
    </div>
    <div class="speedometer" id="speedometer" style="display:none">
        <div class="speed-value" id="speed-value">0</div>
        <div class="speed-unit">KM/H</div>
        <div class="fuel-bar">
            <div class="fuel-fill" id="fuel-bar"></div>
        </div>
    </div>
</div>

对于CSS,使用 transition 应用于条形宽度以创建平滑填充动画,并应用 pointer-events: none 整个HUD容器,使其不干扰游戏输入。不同颜色的状态栏帮助玩家快速识别资源不足,无需阅读标签。红色代表生命值,蓝色代表护甲,橙色代表饥饿,青色代表口渴,这是玩家直观理解的广泛采用的约定。

JavaScript 消息处理

JavaScript层接收来自Lua客户端的消息并相应更新DOM。注册消息事件监听器,根据负载中的action字段分发。保持更新逻辑轻量,因为它以Lua线程发送数据的频率运行,JavaScript层的任何延迟都会直接导致视觉卡顿。避免在更新处理器内查询DOM,初始化时缓存元素引用:

// html/script.js
const elements = {
    healthBar: document.getElementById('health-bar'),
    armorBar: document.getElementById('armor-bar'),
    hungerBar: document.getElementById('hunger-bar'),
    thirstBar: document.getElementById('thirst-bar'),
    speedometer: document.getElementById('speedometer'),
    speedValue: document.getElementById('speed-value'),
    fuelBar: document.getElementById('fuel-bar')
};

window.addEventListener('message', (event) => {
    const data = event.data;

    switch (data.action) {
        case 'updateStatus':
            elements.healthBar.style.width = data.health + '%';
            elements.armorBar.style.width = data.armor + '%';
            elements.hungerBar.style.width = data.hunger + '%';
            elements.thirstBar.style.width = data.thirst + '%';

            // Color shift when low
            if (data.health < 25) {
                elements.healthBar.classList.add('critical');
            } else {
                elements.healthBar.classList.remove('critical');
            }
            break;

        case 'updateVehicle':
            elements.speedometer.style.display = 'flex';
            elements.speedValue.textContent = data.speed;
            elements.fuelBar.style.width = data.fuel + '%';
            break;

        case 'hideVehicle':
            elements.speedometer.style.display = 'none';
            break;
    }
});

为关键状态添加一个CSS类,该类会触发条形图的脉冲动画,当玩家的生命值或饥饿值降至危险水平时吸引玩家注意。这种视觉反馈比依赖玩家不断监控状态数字更有效,并且增加了一层精致感,使业余HUD与专业HUD区分开来。

小地图定制

默认 GTA 小地图功能正常,但视觉上与大多数自定义 HUD 设计冲突。FiveM 通过原生函数让你控制小地图位置、大小、形状和缩放级别。你可以创建圆形小地图,移动以匹配 HUD 布局,甚至完全隐藏并用自定义方案替代。最常见做法是调整小地图形状以补充 HUD 美学,同时保持游戏地图功能不变:

CreateThread(function()
    -- Wait for map to load
    Wait(500)

    -- Set minimap shape and position
    local minimapHandle = RequestScaleformMovie('MINIMAP')
    SetMinimapClipType(1)  -- 0 = rectangle, 1 = circle

    -- Adjust minimap position and size
    local defaultAspect = 1920 / 1080
    local resX, resY = GetActiveScreenResolution()
    local aspect = resX / resY
    local ratio = defaultAspect / aspect

    SetMinimapComponentPosition('minimap', 'L', 'B',
        0.0, -0.032, 0.145 * ratio, 0.210)
    SetMinimapComponentPosition('minimap_mask', 'L', 'B',
        0.0, 0.032, 0.128 * ratio, 0.300)
    SetMinimapComponentPosition('minimap_blur', 'L', 'B',
        -0.01, -0.032, 0.272 * ratio, 0.420)

    -- Hide default health and armor bars
    local minimap = RequestScaleformMovie('MINIMAP')
    while not HasScaleformMovieLoaded(minimap) do Wait(0) end

    while true do
        -- Disable default HUD components
        HideHudComponentThisFrame(6)  -- Vehicle name
        HideHudComponentThisFrame(7)  -- Area name
        HideHudComponentThisFrame(8)  -- Vehicle class
        HideHudComponentThisFrame(9)  -- Street name
        Wait(0)
    end
end)

自定义小地图时,务必考虑不同屏幕宽高比。16:9 显示器上完美的小地图,在超宽屏上会被拉伸或错位。计算默认与实际宽高比的比例,并应用于宽度组件。至少在三种常见分辨率(1920x1080、2560x1440 和 3440x1440)测试小地图,确保不同玩家设置下位置一致。

隐藏默认GTA HUD元素

自定义 HUD 时,必须隐藏被自定义 UI 替代的默认 GTA 元素,否则玩家会看到重复信息。FiveM 提供了 HideHudComponentThisFrame() native 用于此目的,但必须每帧调用,因为 GTA 每个刻都会重新启用 HUD 组件。组件 ID 涵盖从通缉星级到现金显示再到武器轮盘的所有内容。对于完整替代 HUD,通常需要隐藏生命条、护甲条、现金显示和车辆指示器,同时保持字幕和通知弹窗等关键元素可见。创建一个可配置的组件 ID 表,使服务器所有者无需修改代码即可切换隐藏哪些默认元素。此外,使用 DisplayRadar(false) 如果您的HUD包含自己的小地图替代,但请注意隐藏雷达也会禁用暂停菜单地图,除非在检测到暂停菜单时重新启用它。

性能最佳实践

HUD 性能至关重要,因为玩家在游戏中时 HUD 会持续运行,即使是微小的低效也会在长时间游戏中累积成明显的帧率下降。最有效的优化是控制更新频率。像饥饿和口渴这样变化缓慢的状态条可以每 500 毫秒甚至每秒更新一次,而速度等快速变化的数值需要更频繁的更新。使用条件渲染,仅在数值实际变化时发送 NUI 消息,而不是重复推送相同数据。在 JavaScript 端,避免 innerHTML 用于更新,因为它强制浏览器重新解析HTML,并使用 textContent 或直接更改样式属性。尽量减少频繁更新元素上的CSS动画,因为浏览器的动画引擎和您的JavaScript更新可能冲突,导致视觉故障。如果您的HUD使用自定义字体,请在HTML头部预加载它们,以防字体文件加载完成时布局发生变化。最后,使用FiveM内置的工具对您的资源进行性能分析。 resmon 命令并确保您的 HUD 平均每帧消耗少于 0.1ms,为客户端运行的其他数十个资源留出余地。

准备好开始了吗?

在我们的商店获取脚本,或加入 Discord 获取支持、更新以及新功能预告。