Desarrollo de HUD para FiveM: diseña una interfaz clara y rápida
Crea un HUD eficiente para FiveM con HTML y JS. Salud, resistencia, voz, velocidad y minimapa, además de los mejores scripts de HUD para estudiar o comprar.
Agency Scripts
Fundador y desarrollador principal en Agency Scripts
Por qué crear un HUD personalizado
El HUD predeterminado de GTA V se diseñó para un juego de acción para un solo jugador, no para las complejas situaciones de roleplay que requieren los servidores de FiveM. Un HUD personalizado te permite mostrar información específica del roleplay, como el hambre, la sed, el estrés y el estado laboral, que sencillamente no existe en GTA vanilla. Además de ofrecer funciones útiles, un HUD personalizado define la identidad visual de tu servidor y marca el tono desde el momento en que aparece un jugador. Los jugadores notan enseguida la diferencia de calidad entre un servidor que utiliza los elementos de interfaz predeterminados y uno con un HUD pulido, diseñado específicamente para el servidor y acorde con su marca y temática. Crear tu propio HUD desde cero también significa que controlas todos los aspectos del rendimiento y puedes asegurarte de que la interfaz superpuesta consuma los mínimos recursos mientras muestra exactamente la información que necesitan tus jugadores.
Estructura del recurso NUI
FiveM usa NUI (New User Interface) para mostrar HTML, CSS y JavaScript dentro del cliente del juego. El recurso de tu HUD necesita una estructura de carpetas específica para funcionar correctamente. El fxmanifest.lua archivo declara los metadatos del recurso, el client.lua el script envía datos del juego a la capa NUI, y la html/ carpeta contiene tu interfaz web. Aquí tienes un manifiesto de recurso mínimo para empezar:
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'
}
El ui_page directiva le indica a FiveM qué archivo HTML debe mostrar como superposición NUI, y la files tabla enumera todos los recursos a los que el navegador necesita acceder. Si olvidas incluir un archivo CSS o una fuente en esta lista, el navegador no podrá cargarlo y no mostrará ningún mensaje de error, lo que suele confundir a quienes se inician en el desarrollo de NUI. Mantén una estructura ordenada en tu recurso separando las responsabilidades: HTML para la estructura, CSS para los estilos y JavaScript para la lógica y las animaciones.
Enviar datos del juego a la interfaz
El script Lua del lado del cliente se encarga de leer el estado del juego y enviarlo periódicamente a la capa NUI. Debes recopilar la salud, la armadura y los valores de estado específicos de cada framework, como el hambre y la sed. La función clave es SendNUIMessage() , que envía una carga JSON al JavaScript que se ejecuta en el marco NUI. Elige bien la frecuencia de actualización: enviar datos en cada fotograma desperdicia ciclos de CPU, mientras que actualizar demasiado despacio hace que el HUD se sienta lento. Una frecuencia de 200-500 milisegundos ofrece un buen equilibrio para las barras de estado:
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)
Para el velocímetro, necesitas un hilo independiente con una frecuencia mayor, ya que la velocidad cambia rápidamente y los jugadores esperan información en tiempo real al conducir. Un intervalo de 50-100 milisegundos funciona bien para los datos del vehículo. Para ahorrar recursos, ejecuta el hilo del velocímetro solo cuando el jugador esté dentro de un vehículo y desactívalo al salir:
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)
Crear la interfaz HTML y CSS
El diseño visual de tu HUD determina cómo se siente dentro del juego. Los HUD modernos de FiveM usan diseños limpios y minimalistas, con fondos semitransparentes, esquinas redondeadas y animaciones sutiles. Coloca las barras de estado en la esquina inferior izquierda para que no obstaculicen el juego, y pon el velocímetro en la esquina inferior derecha cuando el jugador esté conduciendo. Usa propiedades personalizadas de CSS para los colores, así podrás cambiar el tema de todo el HUD modificando unas pocas variables. Las barras de progreso animadas con transiciones suaves le dan al HUD un acabado pulido:
<!-- 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>
Para el CSS, usa transition en el ancho de la barra para crear animaciones de llenado suaves, y aplica pointer-events: none a todo el contenedor del HUD para que no interfiera con los controles del juego. Usar un color distinto para cada barra de estado ayuda a los jugadores a identificar rápidamente qué recurso está bajo, sin tener que leer las etiquetas. El rojo para la salud, el azul para la armadura, el naranja para el hambre y el cian para la sed son convenciones muy extendidas que los jugadores reconocen de forma intuitiva.
Gestión de mensajes en JavaScript
La capa JavaScript recibe mensajes del cliente Lua y actualiza el DOM según corresponda. Registra un listener para el evento de mensaje que distribuya los mensajes según el campo action de la carga. Mantén ligera la lógica de actualización, ya que se ejecuta con la frecuencia a la que tus hilos Lua envían datos, y cualquier retraso en la capa JavaScript se traduce directamente en saltos visuales. Evita hacer consultas al DOM dentro del controlador de actualización y guarda en caché las referencias a los elementos durante la inicialización:
// 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;
}
});
Añade una clase CSS para los estados críticos que active una animación pulsante en la barra y llame la atención del jugador cuando su salud o hambre estén peligrosamente bajas. Este tipo de indicador visual es mucho más eficaz que depender de que los jugadores vigilen constantemente sus valores, y aporta un nivel de acabado que distingue los HUD aficionados de los profesionales.
Personalización del minimapa
El minimapa predeterminado de GTA es funcional, pero desentona visualmente con la mayoría de los diseños de HUD personalizados. FiveM te permite controlar la posición, el tamaño, la forma y el nivel de zoom del minimapa mediante funciones nativas. Puedes crear un minimapa circular, moverlo para adaptarlo al diseño de tu HUD o incluso ocultarlo por completo y sustituirlo por una solución personalizada. Lo más habitual es modificar la forma del minimapa para que complemente la estética de tu HUD, manteniendo intacta la funcionalidad del mapa del juego:
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)
Al personalizar el minimapa, ten siempre en cuenta las distintas relaciones de aspecto de pantalla. Un minimapa que se ve perfecto en una pantalla 16:9 aparecerá estirado o descolocado en monitores ultrapanorámicos. Calcula la relación entre la relación de aspecto predeterminada y la real, y aplícala a los componentes del ancho. Prueba el minimapa en al menos tres resoluciones habituales (1920x1080, 2560x1440 y 3440x1440) para garantizar una posición coherente en las distintas configuraciones de los jugadores.
Ocultar elementos del HUD predeterminado de GTA
Al crear un HUD personalizado, debes ocultar los elementos predeterminados de GTA que sustituye tu interfaz, de lo contrario los jugadores verán información duplicada. FiveM proporciona el HideHudComponentThisFrame() nativo para este propósito, pero debes llamarlo en cada fotograma porque GTA vuelve a activar los componentes del HUD en cada ciclo. Los ID de los componentes abarcan desde las estrellas de búsqueda hasta el indicador de efectivo y la rueda de armas. Para sustituir el HUD por completo, normalmente querrás ocultar la barra de salud, la barra de blindaje, el indicador de efectivo y los indicadores del vehículo, pero mantener visibles elementos esenciales como los subtítulos y las notificaciones emergentes. Crea una tabla configurable de ID de componentes para que los propietarios de servidores puedan elegir qué elementos predeterminados ocultar sin modificar tu código. Además, usa DisplayRadar(false) si tu HUD incluye su propio reemplazo del minimapa, pero ten en cuenta que ocultar el radar también desactiva el mapa del menú de pausa, a menos que vuelvas a activarlo al detectar el menú de pausa.
Buenas prácticas de rendimiento
El rendimiento del HUD es fundamental porque se ejecuta constantemente mientras el jugador está dentro del juego. Incluso las ineficiencias pequeñas pueden acumularse y provocar caídas de fotogramas notables durante sesiones de juego prolongadas. La optimización que más impacto tiene es controlar la frecuencia de actualización. Las barras de estado que cambian lentamente, como el hambre y la sed, pueden actualizarse cada 500 milisegundos o incluso cada segundo, mientras que los valores que cambian rápido, como la velocidad, necesitan actualizarse con más frecuencia. Usa renderizado condicional para enviar mensajes NUI solo cuando cambien los valores, en lugar de enviar los mismos datos una y otra vez. En JavaScript, evita innerHTML para las actualizaciones, porque obliga al navegador a volver a analizar el HTML, y usa textContent o cambios directos en las propiedades de estilo. Reduce al mínimo las animaciones CSS en los elementos que se actualizan con frecuencia, ya que el motor de animación del navegador y las actualizaciones de JavaScript pueden interferir entre sí y provocar fallos visuales. Si tu HUD usa fuentes personalizadas, precárgalas en el encabezado HTML para evitar cambios en el diseño cuando termine de cargarse el archivo de fuente. Por último, perfila tu recurso con el comando resmon integrado en FiveM y procura que tu HUD consuma menos de 0.1ms por fotograma de media, dejando margen para las docenas de recursos que también se ejecutan en el cliente.