FiveM NUI с React и Vite: setup, callbacks и разработка браузера
Постройте FiveM NUI с React и Vite: ui_page и файлы в fxmanifest, base './', SendNUIMessage, RegisterNUICallback, SetNuiFocus и разработка в браузере.
Обычный HTML работает для маленького NUI, но реальный интерфейс легче с React. Часть которая смущает людей это не React, это клей: манифест, пути вывода сборки, и как сообщения ходят туда и обратно.
Эта статья настраивает React и Vite NUI который загружается в FiveM, разговаривает с Lua в обе стороны, и может быть построен в нормальной вкладке браузера.
Макет проекта
Поместите интерфейс в папку web внутри ресурса и создайте его с Vite:
cd my_script
npm create vite@latest web -- --template react
cd web
npm installРезультат:
my_script/
fxmanifest.lua
client/main.lua
web/
package.json
vite.config.js
src/
dist/ (created by the build)vite.config: base './'
FiveM загружает вашу страницу из адреса nui://, не из корня веб сервера. Пути активов Vite по умолчанию начинаются с /, который затем указывает в никуда и оставляет вас с пустой страницей. Сделайте их относительными:
// web/vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
base: './',
build: { outDir: 'dist' },
})Затем соберите:
npm run buildfxmanifest.lua
Укажите ui_page на встроенный index.html и отправьте целую папку dist игроку с files:
fx_version 'cerulean'
game 'gta5'
ui_page 'web/dist/index.html'
files {
'web/dist/index.html',
'web/dist/**',
}
client_script 'client/main.lua'Только файлы перечисленные в files отправляются игрокам. Если изображение или шрифт отсутствует в игре, глоб файлов его не включил. Держите node_modules и src вне списка: игроки их не нужны.
Lua в UI: SendNUIMessage
Клиент скрипт отправляет сообщение и даёт интерфейсу фокус:
local open = false
local function setOpen(state)
open = state
SetNuiFocus(state, state)
SendNUIMessage({ action = 'setVisible', visible = state, data = { name = 'Mic' } })
end
RegisterCommand('myui', function()
setOpen(not open)
end, false)SetNuiFocus(hasFocus, hasCursor) даёт клавиатуру и мышь странице. Всегда верните её, или курсор застрянет: смотрите NUI фокус застрял курсор.
В React, слушайте сообщение:
import { useEffect, useState } from 'react'
export default function App() {
const [visible, setVisible] = useState(false)
const [name, setName] = useState('')
useEffect(() => {
const onMessage = (event) => {
const { action, visible, data } = event.data
if (action === 'setVisible') {
setVisible(visible)
if (data) setName(data.name)
}
}
window.addEventListener('message', onMessage)
return () => window.removeEventListener('message', onMessage)
}, [])
if (!visible) return null
return <div className="panel">Hello {name}</div>
}Страница которая отображает ничего когда скрыта это обычный паттерн, так как сама страница NUI остаётся загруженной.
UI к Lua: fetch и RegisterNUICallback
Для другого направления, страница POSTs в URL вашего ресурса. GetParentResourceName() это функция которую FiveM предоставляет странице и возвращает имя ресурса:
export async function nuiPost(eventName, data = {}) {
const resource = window.GetParentResourceName ? window.GetParentResourceName() : 'my_script'
const resp = await fetch(`https://${resource}/${eventName}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=UTF-8' },
body: JSON.stringify(data),
})
return resp.json()
}<button onClick={() => nuiPost('close')}>Close</button>В Lua, обработайте это. Callback должен быть вызван, или запрос остаётся в ожидании:
RegisterNUICallback('close', function(data, cb)
setOpen(false) -- gives focus back
cb({ ok = true })
end)data это декодированное JSON тело и cb отвечает на fetch. Что-либо что вы передадите в cb возвращается как ответ.
Разработка в браузере
Пересборка и перезапуск ресурса для каждого изменения CSS медленно. Запустите Vite и работайте в нормальной вкладке браузера:
npm run devВне игры, window.GetParentResourceName не существует и никто не отправляет вам сообщения, поэтому имитируйте оба:
export const isBrowser = !window.invokeNative
// in main.jsx, in dev only
if (isBrowser) {
setTimeout(() => {
window.postMessage({ action: 'setVisible', visible: true, data: { name: 'Mic' } }, '*')
}, 500)
}Функция nuiPost выше уже откатывается к имени ресурса по умолчанию. В браузере, запрос просто падает, поэтому поймайте это или пропустите вызов когда isBrowser это истина. Используйте тёмный или клеточный фон в разработке, так как реальная игра показывает мир позади страницы.
Итерация внутри игры
Для финальной проверки в игре, соберите и перезапустите только ресурс:
npm run buildзатем ensure my_script в консоли сервера. С vite build --watch, файлы пересобираются каждый раз когда вы сохраняете, и вы только перезапускаете ресурс.
Распространённые проблемы
- Пустая страница. Отсутствует
base: './', илиfilesне включаетweb/dist/**. - Ничего не происходит при нажатии. Имя
RegisterNUICallbackне совпадает с URL fetch, илиcbникогда не вызывается. - Курсор застрял после закрытия. Отсутствует
SetNuiFocus(false, false)на каждом пути закрытия, включая перезагрузку ресурса. - Шрифты и изображения отсутствуют в игре. Они ссылаются по абсолютному пути, или не находятся в
dist. - Сообщение никогда не приходит. Страница ещё не загружена, или имена
actionотличаются между Lua и слушателем.
Контрольный список
| Симптом | Решение |
|---|---|
| Пустой UI в игре, работает в браузере | base: './' в vite.config, затем npm run build |
| Файлы отсутствуют для игроков | files { 'web/dist/**' } в fxmanifest.lua |
| Кнопка не делает ничего | Совпадите имя RegisterNUICallback и вызовите cb |
| Не могу достичь игру со страницы | fetch в https://${GetParentResourceName()}/name |
| Курсор не возвращается | SetNuiFocus(false, false) при закрытии |
| Разработка без игры | Запустите vite, имитируйте postMessage и GetParentResourceName |
Короткие ответы
Почему мой React NUI это пустая страница в FiveM?
Обычно сборка использует абсолютные пути активов. Установите base: './' в vite.config, пересоберите, и убедитесь что files в fxmanifest.lua включает целую папку web/dist.
Как UI отправляет данные обратно в игру?
С fetch POST к https://${GetParentResourceName()}/eventName, обработано в Lua с RegisterNUICallback('eventName', ...), который должен вызвать его callback.
Могу ли я разрабатывать NUI без запуска FiveM?
Да. Запустите vite в браузере, обнаружьте что вы вне игры, и отправьте поддельные сообщения с window.postMessage чтобы имитировать что Lua отправил бы.
Скрипты без этой проблемы
Mic PhoneСкладной телефон, который раскладывается в планшет и работает и на настоящем телефоне игрока.Смотреть скрипт →
Clothing DesignerСоздавайте одежду прямо в FiveM — кисть, слои, импорт изображений и ИИ — и носите её.Смотреть скрипт →
Arcade MachinesСемь аркадных игр в настоящих автоматах, с таблицами рекордов и ставками.Смотреть скрипт →