Tutorial 2026-04-05

Criar um Sistema de Notificações Personalizado para FiveM

TDYSKY

TDYSKY

Fundador & Lead Developer na Agency Scripts

Por que construir um sistema de notificação personalizado?

As notificações padrão do FiveM são funcionais, mas carecem de polimento visual e flexibilidade. A maioria dos servidores de RPG depende de pop-ups de texto genéricos que parecem idênticos, tornando difícil para os jogadores distinguir entre um erro, uma confirmação de sucesso ou um alerta informativo. Um sistema de notificação personalizado desenvolvido com NUI oferece controle total sobre estilo, animações, posicionamento, efeitos sonoros e comportamento de fila. Ele transforma um elemento básico da UI em algo que corresponde à identidade da marca do seu servidor e melhora significativamente a experiência do jogador. Neste tutorial, construiremos um sistema completo de notificação do sistema do zero usando Lua no lado do servidor e do cliente, com HTML, CSS e JavaScript alimentando o frontend NUI.

Configurando a camada NUI

A base de qualquer sistema de notificação personalizado no FiveM é a camada NUI (Nova UI). NUI permite renderizar conteúdo HTML como uma sobreposição no jogo, o que significa que você tem todo o poder das modernas tecnologias da web à sua disposição. Comece criando sua estrutura de recursos com um fxmanifest.lua, um script Lua do lado do cliente e uma pasta html contendo seus arquivos de UI. O manifesto precisa declarar sua página NUI e registrar manipuladores de mensagens para que seus scripts Lua possam se comunicar com o frontend.

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

Seu arquivo html/index.html deve ser mínimo. Ele só precisa de um container div onde as notificações serão injetadas dinamicamente por JavaScript. Mantenha o HTML simples porque cada elemento de notificação é criado e destruído programaticamente. Vincule sua folha de estilo e script e certifique-se de que o corpo tenha um fundo transparente para que o mundo do jogo permaneça visível por trás de suas notificações.

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

Projetando o CSS de notificação do Toast

As notificações do sistema devem ser visualmente distintas por tipo. Defina esquemas de cores separados parasucesso, erro, informações, eavisonotificações. Posicione o contêiner no canto superior direito da tela usando posicionamento fixo e empilhe as notificações verticalmente com um pequeno espaço entre elas. Cada brinde deve ter um efeito sutil de morfismo de vidro com desfoque de fundo, uma borda esquerda colorida para indicar o tipo e animações suaves de entrada e saída. Use quadros-chave CSS para deslizar da direita e desaparecer quando a notificação expirar.

/* 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%; }
}

Construindo a fila de notificação JavaScript

A camada JavaScript lida com mensagens recebidas de Lua, cria elementos DOM, gerencia a fila e lida com o descarte automático. Uma fila de notificações é essencial porque você não deseja que dez notificações se acumulem na tela simultaneamente. Limite a contagem visível a um máximo de cinco e coloque quaisquer notificações de estouro na fila para que apareçam quando as anteriores expirarem. Cada notificação deve ter uma duração configurável e clicar em uma notificação deve descartá-la imediatamente. A barra de progresso na parte inferior de cada brinde dá aos jogadores um indicador visual de quanto tempo a notificação permanecerá na tela.

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

Integração Lua do lado do cliente

No lado do cliente, você precisa de uma função que envie mensagens NUI para sua camada HTML e uma exportação para que outros recursos possam acionar notificações sem depender diretamente dos detalhes internos do seu script. O SendNUIMessage nativo envia dados para o contexto do navegador onde seu JavaScript os coleta. Envolva isso em uma função de API limpa que aceita título, mensagem, tipo e duração opcional. Registre um evento de cliente e uma exportação para que as notificações possam ser acionadas a partir de scripts do lado do servidor e de outros recursos do cliente. Esta abordagem dupla garante compatibilidade máxima com qualquer estrutura.

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

Envio de eventos do lado do servidor

O script do lado do servidor fornece funções auxiliares para enviar notificações a jogadores específicos, todos os jogadores ou grupos de jogadores. É aqui que você lida com casos de uso como transmissão de anúncios, envio de confirmações de transações ou alertas de administradores sobre atividades suspeitas. Ao centralizar a lógica de despacho no servidor, você mantém um único ponto de controle sobre a entrega de notificações. Você também pode adicionar limitação de taxa aqui para evitar spam de notificação de scripts de cliente maliciosos ou com bugs. O servidor deve validar os parâmetros de notificação antes de encaminhar para evitar injeção de NUI por meio de mensagens criadas.

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

Adicionando efeitos sonoros e polimento

Os efeitos sonoros elevam as notificações de um elemento apenas visual para um mecanismo de feedback multissensorial. Os jogadores geralmente têm o jogo em segundo plano ou estão focados na direção e podem perder um brinde silencioso. Um toque curto e sutil para notificações de sucesso, um zumbido baixo para erros e um ping suave para alertas de informações garantem que os jogadores registrem informações importantes mesmo quando não estão olhando para a área de notificação. Mantenha seus arquivos de áudio curtos, abaixo de 500 milissegundos, e use o formato OGG para compatibilidade do navegador. Defina o volume para cerca de 30% para que combine com o áudio do jogo, em vez de sobrecarregá-lo. Armazene os arquivos de som em seu diretório html/sounds e pré-carregue-os em JavaScript para evitar atrasos na reprodução na primeira notificação.

Recursos avançados e personalização

Depois de ter o sistema central funcionando, considere adicionar recursos avançados para diferenciar seu servidor. Notificações persistentes que permanecem na tela até que o jogador clique explicitamente nelas são úteis para alertas importantes, como chamadas telefônicas pendentes ou cronômetros de prisão. Os botões de ação dentro das notificações permitem que os jogadores respondam diretamente, por exemplo, aceitando uma solicitação de troca sem abrir um menu separado. Você também pode implementar o agrupamento de notificações, onde notificações idênticas repetidas são resumidas em um único sistema com um selo de contador mostrando o número de ocorrências. Para integração de estrutura, crie arquivos de ponte que substituam as funções de notificação padrão no QBCore ou ESX para que cada recurso em seu servidor use automaticamente seu sistema personalizado sem nenhuma alteração de código. Esse padrão de substituição dos padrões da estrutura por sua própria implementação é uma maneira limpa de atualizar a UI de um servidor inteiro em uma única etapa.

Partilhar este artigo

Pronto para melhorar o teu servidor?

Explora os nossos scripts FiveM premium na loja Agency Scripts ou junta-te à nossa comunidade no Discord para suporte e atualizações.