Sistema de notificaciones para FiveM: avisos, alertas y ox_lib
Crea un sistema de notificaciones pulido para FiveM. Descubre cómo ox_lib y otros recursos gestionan avisos, barras de progreso y menús radiales.
Agency Scripts
Fundador y desarrollador principal en Agency Scripts
¿Por qué crear un sistema de notificaciones personalizado?
Las notificaciones predeterminadas de FiveM son funcionales, pero carecen de atractivo visual y flexibilidad. La mayoría de los servidores de roleplay dependen de ventanas emergentes de texto genéricas que se ven todas iguales, lo que dificulta distinguir entre un error, una confirmación de éxito o un aviso informativo. Un sistema de notificaciones personalizado creado con NUI te da control total sobre los estilos, las animaciones, la posición, los efectos de sonido y la gestión de la cola. Convierte un elemento básico de la interfaz en algo que coincide con la identidad de marca de tu servidor y mejora notablemente la experiencia del jugador. En este tutorial, crearemos desde cero un sistema completo de notificaciones toast con Lua en el servidor y el cliente, y HTML, CSS y JavaScript para la interfaz NUI.
Configuración de la capa NUI
La base de cualquier sistema de notificaciones personalizado en FiveM es la capa NUI (New UI). NUI permite mostrar contenido HTML como una capa sobre el juego, lo que te da acceso a todas las posibilidades de las tecnologías web modernas. Para empezar, crea la estructura de tu recurso con un fxmanifest.luaun script Lua del cliente y una html que contenga los archivos de la interfaz. El manifest debe declarar la página NUI y registrar controladores de mensajes para que tus scripts Lua puedan comunicarse con la interfaz.
-- fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
ui_page 'html/index.html'
files {
'html/index.html',
'html/style.css',
'html/script.js',
'html/sounds/*.ogg'
}
client_script 'client.lua'
server_script 'server.lua'
Tu html/index.html El archivo debe ser mínimo. Solo necesita un elemento div contenedor donde JavaScript insertará las notificaciones dinámicamente. Mantén el HTML ligero, porque cada elemento de notificación se crea y se destruye mediante código. Enlaza la hoja de estilos y el script, y asegúrate de que el fondo del body sea transparente para que el mundo del juego siga visible detrás de las notificaciones.
<!-- html/index.html -->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="notification-container"></div>
<script src="script.js"></script>
</body>
</html>
Diseño del CSS de las notificaciones toast
Las notificaciones toast deben distinguirse visualmente según su tipo. Define esquemas de color separados para éxito, error, info, y advertencia notificaciones. Coloca el contenedor en la esquina superior derecha de la pantalla usando posicionamiento fijo y apila las notificaciones verticalmente con un pequeño espacio entre ellas. Cada aviso debe tener un sutil efecto de glassmorphism con desenfoque del fondo, un borde izquierdo de color para indicar el tipo y animaciones de entrada y salida fluidas. Usa fotogramas clave de CSS para deslizarlo desde la derecha y hacerlo desaparecer gradualmente cuando venza la notificación.
/* html/style.css */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: transparent; font-family: 'Segoe UI', sans-serif; overflow: hidden; }
#notification-container {
position: fixed;
top: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 10px;
z-index: 9999;
max-width: 380px;
width: 100%;
}
.toast {
background: rgba(15, 15, 25, 0.85);
backdrop-filter: blur(12px);
border-radius: 10px;
padding: 14px 18px;
border-left: 4px solid #3b82f6;
color: #e2e8f0;
animation: slideIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
display: flex;
align-items: flex-start;
gap: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
.toast.success { border-left-color: #22c55e; }
.toast.error { border-left-color: #ef4444; }
.toast.warning { border-left-color: #f59e0b; }
.toast.info { border-left-color: #3b82f6; }
.toast-icon { font-size: 20px; flex-shrink: 0; margin-top: 2px; }
.toast-body { flex: 1; }
.toast-title { font-weight: 700; font-size: 14px; margin-bottom: 4px; }
.toast-msg { font-size: 13px; color: #94a3b8; line-height: 1.5; }
.toast-progress {
position: absolute;
bottom: 0; left: 0;
height: 3px;
background: currentColor;
border-radius: 0 0 0 10px;
animation: progress linear forwards;
}
@keyframes slideIn {
from { opacity: 0; transform: translateX(100px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes slideOut {
from { opacity: 1; transform: translateX(0); }
to { opacity: 0; transform: translateX(100px); }
}
@keyframes progress {
from { width: 100%; }
to { width: 0%; }
}
Creación de la cola de notificaciones en JavaScript
La capa de JavaScript gestiona los mensajes entrantes de Lua, crea elementos DOM, administra la cola y controla el cierre automático. Una cola de notificaciones es esencial porque no querrás que diez notificaciones se apilen en la pantalla al mismo tiempo. Limita a cinco el número máximo de notificaciones visibles y pon en cola las que superen ese límite para que aparezcan cuando se cierren las anteriores. Cada notificación debe tener una duración configurable y, al hacer clic en ella, debe cerrarse inmediatamente. La barra de progreso en la parte inferior de cada aviso indica visualmente a los jugadores cuánto tiempo permanecerá en pantalla.
// html/script.js
const container = document.getElementById('notification-container');
const MAX_VISIBLE = 5;
const queue = [];
let activeCount = 0;
const icons = {
success: '✔',
error: '✖',
warning: '⚠',
info: 'ℹ'
};
const sounds = {
success: new Audio('sounds/success.ogg'),
error: new Audio('sounds/error.ogg'),
warning: new Audio('sounds/warning.ogg'),
info: new Audio('sounds/info.ogg')
};
window.addEventListener('message', (event) => {
if (event.data.type === 'showNotification') {
addNotification(event.data);
}
});
function addNotification(data) {
if (activeCount >= MAX_VISIBLE) {
queue.push(data);
return;
}
createToast(data);
}
function createToast(data) {
activeCount++;
const duration = data.duration || 5000;
const toast = document.createElement('div');
toast.className = `toast ${data.style || 'info'}`;
toast.style.position = 'relative';
toast.innerHTML = `
<div class="toast-icon">${icons[data.style] || icons.info}</div>
<div class="toast-body">
<div class="toast-title">${data.title || ''}</div>
<div class="toast-msg">${data.message}</div>
</div>
<div class="toast-progress" style="animation-duration:${duration}ms"></div>
`;
toast.addEventListener('click', () => dismissToast(toast));
container.appendChild(toast);
if (data.sound !== false && sounds[data.style]) {
sounds[data.style].currentTime = 0;
sounds[data.style].play().catch(() => {});
}
setTimeout(() => dismissToast(toast), duration);
}
function dismissToast(toast) {
if (toast.dataset.dismissed) return;
toast.dataset.dismissed = 'true';
toast.style.animation = 'slideOut 0.3s ease forwards';
setTimeout(() => {
toast.remove();
activeCount--;
if (queue.length > 0) {
createToast(queue.shift());
}
}, 300);
}
Integración de Lua del lado del cliente
En el lado del cliente, necesitas una función que envíe mensajes NUI a la capa HTML y una exportación para que otros recursos puedan activar notificaciones sin depender directamente de los detalles internos de tu script. El SendNUIMessage nativo envía datos al contexto del navegador, donde JavaScript los recoge. Envuélvelo en una función API clara que acepte un título, un mensaje, un tipo y una duración opcional. Registra tanto un evento del cliente como una exportación para que las notificaciones puedan activarse desde scripts del servidor y otros recursos del cliente. Este enfoque dual garantiza la máxima compatibilidad con cualquier framework.
-- client.lua
local function ShowNotification(title, message, style, duration, sound)
SendNUIMessage({
type = 'showNotification',
title = title or '',
message = message or '',
style = style or 'info',
duration = duration or 5000,
sound = sound ~= false
})
end
-- Export for other resources
exports('ShowNotification', ShowNotification)
-- Event-based trigger from server
RegisterNetEvent('notifications:show', function(title, message, style, duration)
ShowNotification(title, message, style, duration)
end)
-- Convenience commands for testing
RegisterCommand('testnotify', function()
ShowNotification('Success', 'Your item has been saved.', 'success', 4000)
Wait(500)
ShowNotification('Error', 'Insufficient funds for this purchase.', 'error', 5000)
Wait(500)
ShowNotification('Warning', 'Your vehicle is low on fuel.', 'warning', 4000)
Wait(500)
ShowNotification('Info', 'Press E to interact with the NPC.', 'info', 3000)
end, false)
Envío de eventos del lado del servidor
El script del lado del servidor proporciona funciones auxiliares para enviar notificaciones a jugadores específicos, a todos los jugadores o a grupos de jugadores. Aquí puedes gestionar casos de uso como difundir anuncios, enviar confirmaciones de transacciones o alertar a los administradores sobre actividades sospechosas. Al centralizar la lógica de envío en el servidor, mantienes un único punto de control para la entrega de notificaciones. También puedes añadir aquí límites de frecuencia para evitar el spam de notificaciones causado por scripts de cliente maliciosos o con errores. El servidor debe validar los parámetros de las notificaciones antes de reenviarlas, para evitar la inyección de NUI mediante mensajes manipulados.
-- server.lua
local function NotifyPlayer(source, title, message, style, duration)
if not source or source <= 0 then return end
title = tostring(title or '')
message = tostring(message or '')
style = style or 'info'
TriggerClientEvent('notifications:show', source, title, message, style, duration)
end
local function NotifyAll(title, message, style, duration)
TriggerClientEvent('notifications:show', -1, title, message, style, duration)
end
exports('NotifyPlayer', NotifyPlayer)
exports('NotifyAll', NotifyAll)
-- Example: welcome notification
AddEventHandler('playerJoining', function()
local src = source
Wait(3000)
NotifyPlayer(src, 'Welcome', 'Welcome to the server! Have fun.', 'success', 6000)
end)
-- Admin broadcast command
RegisterCommand('broadcast', function(source, args)
if source > 0 and not IsPlayerAceAllowed(source, 'command.broadcast') then return end
local msg = table.concat(args, ' ')
NotifyAll('Announcement', msg, 'info', 8000)
end, true)
Añadir efectos de sonido y pulir el sistema
Los efectos de sonido convierten las notificaciones de un elemento solo visual en un mecanismo de respuesta multisensorial. A menudo, los jugadores tienen el juego en segundo plano o están concentrados en conducir, y pueden pasar por alto un aviso silencioso. Un timbre breve y sutil para las notificaciones de éxito, un zumbido grave para los errores y un pitido suave para los avisos informativos ayudan a que los jugadores reciban la información importante incluso cuando no están mirando el área de notificaciones. Mantén los archivos de audio cortos, de menos de 500 milisegundos, y usa el formato OGG para garantizar la compatibilidad con el navegador. Ajusta el volumen a alrededor del 30 por ciento para que se mezcle con el audio del juego sin imponerse. Guarda los archivos de sonido en tu html/sounds directorio y precárgalos en JavaScript para evitar retrasos de reproducción en la primera notificación.
Funciones avanzadas y personalización
Cuando el sistema básico esté funcionando, plantéate añadir funciones avanzadas para diferenciar tu servidor. Las notificaciones persistentes que permanecen en pantalla hasta que el jugador hace clic en ellas son útiles para avisos importantes, como llamadas telefónicas pendientes o temporizadores de cárcel. Los botones de acción dentro de las notificaciones permiten que los jugadores respondan directamente, por ejemplo, aceptando una solicitud de intercambio sin abrir otro menú. También puedes implementar la agrupación de notificaciones, donde las notificaciones idénticas que se repiten se fusionan en un solo aviso con una insignia que muestra el número de veces que se han producido. Para integrar el sistema con un framework, crea archivos puente que reemplacen las funciones de notificación predeterminadas de QBCore o ESX, para que todos los recursos de tu servidor usen automáticamente tu sistema personalizado sin cambios en el código. Este patrón de sustituir los valores predeterminados del framework por tu propia implementación es una forma sencilla de actualizar la interfaz de todo el servidor de una sola vez.