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.