Wróć do bloga
Tutorial8 min czytania

Tworzenie FiveM HUD: Zaprojektuj przejrzysty, szybki HUD gracza

Stwórz wydajny HUD dla FiveM za pomocą HTML i JS. Zdrowie, wytrzymałość, głos, prędkość i nakładki minimapy oraz najlepsze skrypty HUD do nauki lub zakupu.

Agency Scripts

Założyciel i główny deweloper w Agency Scripts

Dlaczego warto stworzyć własny HUD

Domyślny HUD w GTA V został zaprojektowany z myślą o grze akcji dla jednego gracza, a nie o skomplikowanych scenariuszach roleplay, których wymagają serwery FiveM. Niestandardowy HUD pozwala wyświetlać informacje specyficzne dla roleplay, takie jak głód, pragnienie, stres czy status pracy, które w czystym GTA po prostu nie istnieją. Oprócz funkcjonalności, autorski HUD definiuje tożsamość wizualną twojego serwera i nadaje mu ton od momentu wejścia gracza do gry. Gracze od razu zauważają różnicę jakości między serwerem korzystającym z domyślnych elementów UI a serwerem z dopracowanym, dedykowanym HUD-em dopasowanym do marki i motywu serwera. Tworzenie własnego HUD-a od podstaw oznacza również pełną kontrolę nad każdym aspektem wydajności, co gwarantuje, że nakładka działa przy minimalnym zużyciu zasobów, dostarczając dokładnie tych informacji, których potrzebują twoi gracze.

Struktura zasobu NUI

FiveM używa NUI (New User Interface) do renderowania HTML, CSS i JavaScriptu wewnątrz klienta gry. Twój resource HUD wymaga określonej struktury folderów, aby działać poprawnie. Plik fxmanifest.lua plik deklaruje metadane resource, a client.lua skrypt wysyła dane gry do warstwy NUI, a html/ folder zawiera twój interfejs webowy. Oto minimalny manifest resource na początek:

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'
}

Ten ui_page dyrektywa informuje FiveM, który plik HTML ma być renderowany jako nakładka NUI, a files tabela zawiera listę wszystkich zasobów, do których przeglądarka potrzebuje dostępu. Jeśli zapomnisz dołączyć plik CSS lub czcionkę do tej listy, przeglądarka po cichu nie załaduje go bez żadnego komunikatu o błędzie, co jest częstym źródłem dezorientacji dla programistów zaczynających z NUI. Utrzymuj czystą strukturę resource poprzez separację zadań: HTML do struktury, CSS do stylizacji, a JavaScript do logiki i animacji.

Wysyłanie danych gry do UI

Skrypt Lua po stronie klienta odpowiada za odczytywanie stanu gry i przesyłanie go do warstwy NUI w regularnych odstępach czasu. Musisz zebrać poziom zdrowia, pancerz oraz wszelkie wartości statusu specyficzne dla danego frameworka, takie jak głód i pragnienie. Kluczową funkcją jest SendNUIMessage(), co wysyła ładunek JSON do JavaScriptu działającego w ramce NUI. Przemyśl częstotliwość aktualizacji, ponieważ wysyłanie danych w każdej klatce marnuje cykle CPU, a zbyt wolne aktualizowanie sprawia, że HUD wydaje się opóźniony. Tick rate na poziomie 200-500 milisekund zapewnia właściwy balans dla pasków statusu:

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)

W przypadku prędkościomierza potrzebujesz osobnego wątku działającego z wyższą częstotliwością ticków, ponieważ prędkość zmienia się szybko, a gracze oczekują natychmiastowej reakcji podczas jazdy. Interwał 50-100 milisekund sprawdza się dobrze dla danych pojazdu. Aby oszczędzać zasoby, uruchamiaj wątek prędkościomierza tylko wtedy, gdy gracz rzeczywiście znajduje się w pojeździe, i wyłączaj go po opuszczeniu auta:

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)

Tworzenie interfejsu HTML i CSS

Projekt wizualny Twojego HUD określa wrażenia z gry. Nowoczesne HUD-y w FiveM stawiają na czysty, minimalistyczny design z półprzezroczystym tłem, zaokrąglonymi rogami i subtelnymi animacjami. Umieść paski stanu w lewym dolnym rogu, gdzie nie przeszkadzają w rozgrywce, a prędkościomierz w prawym dolnym rogu, gdy gracz prowadzi pojazd. Używaj właściwości niestandardowych CSS dla kolorów, aby łatwo zmieniać motyw całego HUD za pomocą kilku zmiennych. Animowane paski postępu z płynnymi przejściami nadają HUD dopracowany wygląd:

<!-- 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>

W przypadku CSS użyj transition na szerokość paska, aby uzyskać płynne animacje wypełniania, oraz zastosuj pointer-events: none do całego kontenera HUD, aby nie zakłócał sterowania w grze. Różne kolory poszczególnych pasków stanu pomagają graczom szybko rozpoznać, który zasób się wyczerpuje, bez konieczności czytania etykiet. Kolor czerwony dla zdrowia, niebieski dla pancerza, pomarańczowy dla głodu i błękitny dla pragnienia to powszechnie przyjęta konwencja, którą gracze intuicyjnie rozumieją.

Obsługa komunikatów JavaScript

Warstwa JavaScript odbiera komunikaty z klienta Lua i odpowiednio aktualizuje DOM. Zarejestruj listener zdarzenia message, który rozdziela zadania na podstawie pola action w przesyłanych danych. Zadbaj o lekkość logiki aktualizacji, ponieważ działa ona z taką samą częstotliwością, z jaką wątki Lua wysyłają dane, a wszelkie opóźnienia w warstwie JavaScript przekładają się bezpośrednio na zacinanie obrazu. Unikaj zapytań DOM wewnątrz handlera aktualizacji, buforując referencje do elementów podczas inicjalizacji:

// 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;
    }
});

Dodaj klasę CSS dla stanów krytycznych, która wyzwala pulsującą animację na pasku, zwracając uwagę gracza, gdy jego zdrowie lub głód spadną niebezpiecznie nisko. Taki komunikat wizualny jest znacznie skuteczniejszy niż oczekiwanie, że gracze będą stale monitorować liczby swojego statusu, a do tego dodaje szlifu odróżniającego amatorskie interfejsy HUD od profesjonalnych.

Personalizacja minimapy

Domyślna minimapa GTA jest funkcjonalna, ale wizualnie gryzie się z większością niestandardowych projektów HUD. FiveM daje kontrolę nad pozycją, rozmiarem, kształtem i poziomem powiększenia minimapy za pomocą funkcji natywnych. Możesz stworzyć okrągłą minimapę, przesunąć ją tak, aby pasowała do układu HUD-a, a nawet całkowicie ją ukryć i zastąpić własnym rozwiązaniem. Najczęstszym podejściem jest zmiana kształtu minimapy w taki sposób, aby dopełniała estetykę HUD-a, zachowując nienaruszoną funkcjonalność mapy gry:

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)

Dostosowując minimapę, zawsze bierz pod uwagę różne proporcje ekranu. Minimapa, która wygląda idealnie na ekranie 16:9, będzie rozciągnięta lub źle umiejscowiona na monitorach ultrawide. Oblicz stosunek domyślnych proporcji do rzeczywistych i zastosuj go do komponentów szerokości. Przetestuj swoją minimapę w co najmniej trzech popularnych rozdzielczościach (1920x1080, 2560x1440 i 3440x1440), aby zapewnić spójne pozycjonowanie na różnych konfiguracjach graczy.

Ukrywanie domyślnych elementów HUD w GTA

Tworząc własny HUD, musisz ukryć domyślne elementy GTA, które zastępuje twoje własne UI, w przeciwnym razie gracze zobaczą zduplikowane informacje. FiveM udostępnia HideHudComponentThisFrame() native do tego celu, ale musi być wywoływany w każdej klatce, ponieważ GTA ponownie włącza komponenty HUD w każdym ticku. Identyfikatory komponentów obejmują wszystko: od gwiazdek pościgu, przez stan gotówki, po koło wyboru broni. W przypadku całkowicie własnego HUD zazwyczaj ukrywa się pasek zdrowia, pancerza, stan gotówki i wskaźniki pojazdu, pozostawiając widoczne niezbędne elementy, takie jak napisy czy powiadomienia. Stwórz konfigurowalną tabelę z ID komponentów, aby właściciele serwerów mogli przełączać ukrywanie domyślnych elementów bez modyfikowania twojego kodu. Dodatkowo użyj DisplayRadar(false) jeśli Twój HUD zawiera własny zamiennik minimapy, pamiętaj jednak, że ukrycie radaru wyłącza również mapę w menu pauzy, chyba że włączysz ją ponownie po wykryciu menu pauzy.

Dobre praktyki wydajności

Wydajność HUD jest kluczowa, ponieważ Twój HUD działa nieprzerwanie podczas gry, przez co nawet drobne nieoptymalności kumulują się w zauważalne spadki klatek podczas dłuższych sesji. Najważniejszą optymalizacją jest kontrolowanie częstotliwości aktualizacji. Paski stanu zmieniające się powoli, takie jak głód i pragnienie, mogą aktualizować się co 500 milisekund lub nawet co sekundę, podczas gdy szybko zmieniające się wartości, jak prędkość, wymagają częstszych aktualizacji. Użyj renderowania warunkowego, aby wysyłać wiadomości NUI tylko wtedy, gdy wartości rzeczywiście ulegną zmianie, zamiast wielokrotnie przesyłać te same dane. Po stronie JavaScript unikaj innerHTML do aktualizacji, ponieważ wymusza ponowne parsowanie HTML przez przeglądarkę, i używaj textContent lub bezpośrednie zmiany właściwości stylów. Zminimalizuj animacje CSS na często aktualizowanych elementach, ponieważ silnik animacji przeglądarki może kolidować z aktualizacjami JavaScript, powodując błędy wizualne. Jeśli twój HUD korzysta z niestandardowych czcionek, wstępnie załaduj je w sekcji head pliku HTML, aby uniknąć przesunięć układu po załadowaniu pliku czcionki. Na koniec sprofiluj swój resource za pomocą wbudowanego w FiveM resmon polecenia i dąż do tego, aby twój HUD zużywał średnio poniżej 0.1ms na klatkę, pozostawiając zapas dla dziesiątek innych zasobów działających na kliencie.

Pasujący skrypt

Gotowe na twój serwer, aktualizacje w zestawie.

Gotowy, aby zacząć?

Kup skrypty w naszym sklepie lub dołącz do Discorda, aby uzyskać wsparcie, aktualizacje i sprawdzić nadchodzące nowości.