Назад к блогу
Tutorial7 мин чтения

Разработка HUD FiveM: дизайн чистого и быстрого HUD для игрока

Создайте производительный HUD для FiveM с использованием HTML и JS. Здоровье, выносливость, голос, скорость и наложения миникарты, а также лучшие HUD скрипты для изучения или покупки.

Agency Scripts

Основатель и ведущий разработчик Agency Scripts

Зачем создавать кастомный HUD

Стандартный HUD GTA V разработан для однопользовательской экшн-игры, а не для сложных ролевых сценариев, которые требуют серверы FiveM. Кастомный HUD позволяет отображать специфическую для ролевой игры информацию, такую как голод, жажда, стресс и статус работы, которой просто нет в ванильной GTA. Помимо функциональности, кастомный HUD формирует визуальную идентичность вашего сервера и задаёт тон с момента появления игрока. Игроки сразу замечают разницу в качестве между сервером с дефолтными UI-элементами и сервером с отполированным, специально созданным HUD, который соответствует бренду и теме сервера. Создание собственного HUD с нуля также означает полный контроль над производительностью, обеспечивая минимальную нагрузку при отображении именно той информации, которая нужна вашим игрокам.

Структура ресурса NUI

FiveM использует NUI (New User Interface) для рендеринга HTML, CSS и JavaScript внутри игрового клиента. Ваш HUD ресурс требует определённой структуры папок для корректной работы. The 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 директива сообщает FiveM, какой HTML-файл отображать как NUI-оверлей, и files таблица перечисляет все ресурсы, к которым браузер должен иметь доступ. Если вы забудете включить CSS-файл или шрифт в этот список, браузер тихо не загрузит их без какого-либо сообщения об ошибке, что часто вызывает путаницу у разработчиков, начинающих работу с NUI. Поддерживайте структуру ресурса чистой, разделяя обязанности: HTML для разметки, CSS для стилей и JavaScript для логики и анимаций.

Отправка игровых данных в UI

Клиентский Lua-скрипт отвечает за чтение состояния игры и передачу его в слой NUI с регулярными интервалами. Нужно собирать данные о здоровье, броне и любых специфичных для фреймворка статусах, таких как голод и жажда. Ключевая функция , SendNUIMessage() , который отправляет JSON-пакет в JavaScript, работающий в NUI-окне. Будьте стратегичны с частотой обновлений, так как отправка данных каждый кадр тратит ресурсы 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 от любительских.

Настройка миникарты

Стандартная миникарта 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), чтобы обеспечить стабильное позиционирование на разных конфигурациях игроков.

Скрытие стандартных элементов HUD GTA

При создании кастомного HUD вы должны скрыть стандартные элементы GTA, которые заменяет ваш UI, иначе игроки увидят дублирующую информацию. FiveM предоставляет HideHudComponentThisFrame() native для этой цели, но её нужно вызывать каждый кадр, потому что GTA повторно включает компоненты HUD каждый тик. Идентификаторы компонентов охватывают всё: от звезд розыска до отображения денег и колеса оружия. Для полного замещения HUD обычно нужно скрыть индикаторы здоровья, брони, денег и транспортных средств, сохраняя при этом важные элементы, такие как субтитры и всплывающие уведомления. Создайте настраиваемую таблицу идентификаторов компонентов, чтобы владельцы серверов могли переключать, какие стандартные элементы скрывать без изменения вашего кода. Дополнительно используйте DisplayRadar(false) если ваш HUD включает собственную замену миникарты, но имейте в виду, что скрытие радара также отключает карту в меню паузы, если вы не включите её заново при обнаружении меню паузы.

Лучшие практики производительности

Производительность HUD критична, так как ваш HUD работает постоянно, пока игрок в игре, из-за чего даже небольшие неэффективности накапливаются и вызывают заметные падения FPS при длительных сессиях. Самая важная оптимизация , контроль частоты обновления. Статус-бары, которые меняются медленно, такие как голод и жажда, можно обновлять каждые 500 миллисекунд или даже каждую секунду, тогда как быстро меняющиеся значения, например скорость, требуют более частых обновлений. Используйте условный рендеринг, чтобы отправлять NUI-сообщения только при изменении значений, а не повторно передавать одни и те же данные. На стороне JavaScript избегайте innerHTML для обновлений, потому что это заставляет браузер повторно разбирать HTML, и используйте textContent или прямые изменения стиля. Минимизируйте CSS-анимации на элементах, которые часто обновляются, так как движок анимации браузера и ваши обновления JavaScript могут конфликтовать, вызывая визуальные сбои. Если ваш HUD использует кастомные шрифты, предварительно загрузите их в head вашего HTML, чтобы избежать сдвигов макета при загрузке файла шрифта. Наконец, профилируйте ваш ресурс с помощью встроенного в FiveM resmon команда и цель для вашего HUD, чтобы потреблять в среднем менее 0.1 мс на кадр, оставляя запас для десятков других ресурсов, работающих на клиенте.

Готовы начать?

Возьмите скрипты в нашем магазине или заходите в Discord за поддержкой, обновлениями и анонсами.