FiveM Notifications System: Тосты, оповещения и ox_lib
Создайте отточенную систему уведомлений для FiveM. Тосты, индикаторы прогресса, радиальные меню и как ox_lib и лучшие скрипты обрабатывают обратную связь клиентского интерфейса.
Agency Scripts
Основатель и ведущий разработчик Agency Scripts
Зачем создавать кастомную систему уведомлений?
Стандартные уведомления FiveM функциональны, но лишены визуальной отделки и гибкости. Большинство ролевых серверов используют одинаковые текстовые всплывающие окна, из-за чего игрокам сложно отличить ошибку, подтверждение успеха или информационное предупреждение. Пользовательская система уведомлений на базе NUI даёт полный контроль над стилем, анимациями, позиционированием, звуковыми эффектами и поведением очереди. Она превращает базовый UI-элемент в нечто, соответствующее фирменному стилю вашего сервера и значительно улучшает опыт игроков. В этом уроке мы создадим полноценную систему toast-уведомлений с нуля, используя Lua на сервере и клиенте, а HTML, CSS и JavaScript обеспечат работу фронтенда NUI.
Настройка слоя NUI
Основа любой кастомной системы уведомлений в FiveM , это слой NUI (New UI). NUI позволяет отображать HTML-контент поверх игры, что даёт полный доступ к современным веб-технологиям. Начните с создания структуры ресурса с fxmanifest.lua , клиентский Lua-скрипт и html папка, содержащая ваши UI файлы. Манифест должен объявить вашу NUI страницу и зарегистрировать обработчики сообщений, чтобы ваши Lua скрипты могли общаться с фронтендом.
-- 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'
Ваш html/index.html файл должен быть минимальным. В нем нужен только контейнер div, куда JavaScript будет динамически вставлять уведомления. Держите HTML легким, так как каждый элемент уведомления создается и уничтожается программно. Подключите ваш стиль и скрипт, и убедитесь, что body имеет прозрачный фон, чтобы игровой мир оставался видимым за уведомлениями.
<!-- 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>
Проектирование CSS уведомлений Toast
Уведомления toast должны визуально отличаться по типу. Определите отдельные цветовые схемы для успех, ошибка, информация , и предупреждение уведомления. Разместите контейнер в правом верхнем углу экрана с помощью фиксированного позиционирования и расположите уведомления вертикально с небольшим промежутком между ними. Каждый тост должен иметь тонкий эффект glassmorphism с размытием фона, цветную левую границу для указания типа и плавные анимации появления и исчезновения. Используйте CSS keyframes для слайда справа и затухания при истечении уведомления.
/* 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%; }
}
Создание очереди уведомлений на JavaScript
Слой JavaScript обрабатывает входящие сообщения от Lua, создаёт DOM-элементы, управляет очередью и автоматическим скрытием уведомлений. Очередь уведомлений необходима, чтобы не допустить одновременного появления десяти уведомлений на экране. Ограничьте видимое количество максимум пятью и ставьте лишние уведомления в очередь, чтобы они появлялись по мере исчезновения предыдущих. Каждое уведомление должно иметь настраиваемую длительность, а клик по уведомлению должен закрывать его сразу. Индикатор прогресса внизу каждого toast даёт игрокам визуальный сигнал о времени отображения уведомления.
// 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);
}
Клиентская интеграция Lua
На клиентской стороне нужна функция, отправляющая NUI-сообщения в ваш HTML-слой, и экспорт, чтобы другие ресурсы могли вызывать уведомления без прямой зависимости от внутренностей вашего скрипта. SendNUIMessage native передаёт данные в браузерный контекст, где ваш JavaScript их обрабатывает. Оберните это в чистую API-функцию, принимающую заголовок, сообщение, тип и опциональную длительность. Зарегистрируйте как клиентское событие, так и экспорт, чтобы уведомления можно было вызывать как из серверных скриптов, так и из других клиентских ресурсов. Такой двойной подход обеспечивает максимальную совместимость с любыми фреймворками.
-- 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)
Диспетчер событий на стороне сервера
Серверный скрипт предоставляет вспомогательные функции для отправки уведомлений конкретным игрокам, всем игрокам или группам. Здесь обрабатываются кейсы, такие как рассылка объявлений, подтверждения транзакций или оповещения админов о подозрительной активности. Централизация логики отправки на сервере обеспечивает единый контроль доставки уведомлений. Здесь же можно добавить ограничение частоты, чтобы предотвратить спам уведомлений от вредоносных или багованных клиентских скриптов. Сервер должен проверять параметры уведомлений перед пересылкой, чтобы предотвратить инъекции NUI через специально сформированные сообщения.
-- 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)
Добавление звуковых эффектов и доработок
Звуковые эффекты поднимают уведомления с визуального элемента до многоуровневого сенсорного механизма обратной связи. Игроки часто имеют игру в фоне или сосредоточены на вождении и могут пропустить тихое уведомление. Короткий, ненавязчивый звонок для уведомлений об успехе, низкий гудок для ошибок и мягкий пинг для информационных оповещений обеспечивают восприятие важной информации даже когда игрок не смотрит на область уведомлений. Держите аудиофайлы короткими, менее 500 миллисекунд, и используйте формат OGG для совместимости с браузерами. Установите громкость около 30 процентов, чтобы звук гармонично сочетался с игровым аудио, а не заглушал его. Храните звуковые файлы в вашей html/sounds каталог и предварительно загружайте их в JavaScript, чтобы избежать задержек воспроизведения при первом уведомлении.
Расширенные функции и настройка
После того как основная система работает, рассмотрите добавление продвинутых функций для выделения вашего сервера. Постоянные уведомления, которые остаются на экране, пока игрок явно не закроет их, полезны для важных оповещений, таких как входящие звонки или таймеры тюрьмы. Кнопки действий внутри уведомлений позволяют игрокам отвечать напрямую, например, принимать запрос на обмен без открытия отдельного меню. Вы также можете реализовать группировку уведомлений, когда повторяющиеся идентичные уведомления сворачиваются в один тост с бейджем-счётчиком количества повторов. Для интеграции с фреймворком создайте bridge-файлы, которые переопределяют стандартные функции уведомлений в QBCore или ESX, чтобы каждый ресурс на сервере автоматически использовал вашу систему без изменений кода. Этот подход замены стандартных функций фреймворка своей реализацией , чистый способ обновить интерфейс всего сервера за один шаг.