FiveM NUI คืออะไร ใช้ HTML CSS JavaScript อย่างไร
FiveM NUI คือระบบสร้าง User Interface ภายในเกมด้วยเทคโนโลยีเว็บ เช่น HTML, CSS และ JavaScript โดย FiveM ใช้ Chromium Embedded Framework หรือ CEF ในการแสดงหน้าเว็บเป็น UI ซ้อนอยู่บนเกม
NUI สามารถใช้สร้างระบบต่าง ๆ เช่น
Inventory
Phone
Garage
Bank
Shop
Character Creator
Job Menu
Scoreboard
HUD
Admin Panel
Vehicle UI
Architecture พื้นฐานคือ
FiveM Client Script
↓
SendNUIMessage
↓
NUI Browser
↓
HTML + CSS + JavaScript
และเมื่อต้องการส่งข้อมูลกลับจากหน้า UI ไปยัง Client Script
NUI JavaScript
↓
fetch()
↓
NUI Callback
↓
FiveM Client Script
Resource ขั้นพื้นฐานอาจมีโครงสร้าง
my_ui/
├── fxmanifest.lua
├── client.lua
└── html/
├── index.html
├── style.css
└── app.js
จากนั้นกำหนด NUI ใน fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
ui_page 'html/index.html'
files {
'html/index.html',
'html/style.css',
'html/app.js'
}
client_script 'client.lua'
เพียงเท่านี้ Resource ก็มีหน้า NUI ที่ FiveM สามารถโหลดได้แล้ว
① NUI ย่อมาจากอะไร
NUI ย่อมาจาก
New UI
ใน FiveM หมายถึงระบบ HTML-based User Interface ของ CitizenFX
UI จึงสามารถใช้เทคโนโลยีเว็บที่ Developer คุ้นเคย เช่น
HTML
CSS
JavaScript
React
Vue
Angular
WebGL
ได้
② FiveM NUI ใช้อะไรแสดงผล
FiveM ใช้ Chromium Embedded Framework หรือ CEF
Concept คล้าย Browser ที่ถูกฝังอยู่ใน FiveM
ดังนั้น NUI สามารถทำสิ่งอย่าง
DOM
CSS
JavaScript
Events
Fetch
Web APIs
ได้หลายอย่างเหมือน Web Application
แต่ Environment ไม่เหมือนเว็บไซต์ทั่วไปทุกจุด
เพราะ NUI ต้องสื่อสารกับ FiveM Runtime ด้วย
③ NUI ต่างจาก HUD Native อย่างไร
HUD แบบ Native อาจวาดด้วย FiveM/GTA Functions
เช่น
DrawText
DrawRect
Sprites
ส่วน NUI ใช้
HTML
CSS
JavaScript
ข้อดีของ NUI คือสร้าง Interface ซับซ้อนได้ง่ายกว่า เช่น
Grid Inventory
Animations
Responsive Layout
Search
Drag & Drop
Forms
Charts
④ NUI ใช้ฝั่ง Client หรือ Server
ตัว UI ทำงานฝั่ง Client
Architecture คือ
Browser NUI
↕
Client Script
↕
Server Script
↕
Database
NUI ไม่ควรเข้าถึง Database โดยตรง
หากต้องการข้อมูล Database เช่นรถของผู้เล่น
ควรเป็น
NUI
↓
Client
↓
Server
↓
Validate
↓
Database
↓
Server
↓
Client
↓
NUI
⑤ Resource NUI ต้องมีไฟล์อะไร
ตัวอย่างง่าย
my_menu/
├── fxmanifest.lua
├── client.lua
└── html/
├── index.html
├── style.css
└── app.js
หาก Resource ใหญ่สามารถเป็น
my_menu/
├── fxmanifest.lua
├── client/
│ ├── main.lua
│ └── nui.lua
├── server/
│ └── main.lua
└── web/
├── index.html
├── css/
├── js/
├── images/
└── fonts/
ได้
⑥ Folder ต้องชื่อ html ไหม
ไม่
สามารถใช้
html/
web/
ui/
nui/
dist/
ก็ได้
สิ่งสำคัญคือ Path ใน fxmanifest.lua ต้องตรง
เช่น
ui_page 'web/index.html'
⑦ ui_page คืออะไร
ui_page กำหนดหน้า Root ของ Fullscreen NUI Resource
ตัวอย่าง
ui_page 'html/index.html'
หมายถึง FiveM จะใช้
html/index.html
เป็นหน้าหลักของ UI
หนึ่ง Resource กำหนด Fullscreen ui_page หลักหนึ่งรายการตามระบบนี้
⑧ files คืออะไร
หน้า NUI และ Assets ฝั่ง Client ต้องถูกเพิ่มเข้า Resource Packfile
เช่น
files {
'html/index.html',
'html/style.css',
'html/app.js'
}
หากมีรูป
files {
'html/index.html',
'html/style.css',
'html/app.js',
'html/images/logo.webp'
}
หรือใช้ Wildcard ตามที่ Manifest รองรับ
⑨ ถ้าลืมใส่ CSS หรือ JS ใน files
อาจเกิดอาการ
หน้า UI เปิด
แต่ไม่มี CSS
JavaScript ไม่ทำงาน
รูปไม่ขึ้น
Font ไม่โหลด
เพราะ Browser หา Asset ไม่เจอ
ดังนั้นเมื่อ NUI ดูผิดปกติให้ตรวจ
ui_page
files
HTML paths
CSS paths
JS paths
ก่อน
⑩ HTML ขั้นพื้นฐานเขียนอย่างไร
html/index.html
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
>
<title>FiveM NUI</title>
<link
rel="stylesheet"
href="style.css"
>
</head>
<body>
<div id="menu">
<h1>FiveM Menu</h1>
<button id="close">
ปิด
</button>
</div>
<script src="app.js"></script>
</body>
</html>
นี่คือ HTML ปกติ
⑪ CSS ใช้เหมือนเว็บไซต์ไหม
พื้นฐานใช่
เช่น style.css
body {
margin: 0;
font-family: Arial, sans-serif;
}
#menu {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 30px;
background: rgba(0, 0, 0, 0.85);
border-radius: 12px;
}
#menu.active {
display: block;
}
คุณสามารถใช้
Flexbox
Grid
Transitions
Animations
CSS Variables
ได้ตาม Browser Environment
⑫ JavaScript ทำหน้าที่อะไร
JavaScript เป็น Logic ฝั่ง UI
เช่น
const menu =
document.getElementById('menu');
const closeButton =
document.getElementById('close');
จากนั้นสามารถ
เปิด/ปิด UI
เปลี่ยนข้อมูล
รับ Message
ส่ง Callback
จัดการ Click
Search
Animation
ได้
⑬ NUI เริ่มมองเห็นทันทีไหม
ถ้า HTML มี Content Visible ตั้งแต่ Load หน้า UI อาจปรากฏทันที
ปกติจึงซ่อน UI เริ่มต้น
เช่น
#menu {
display: none;
}
แล้วให้ Client Script ส่ง Message เพื่อเปิด
⑭ SendNUIMessage คืออะไร
SendNUIMessage ใช้ส่งข้อมูลจาก Lua Client Script ไปยัง NUI
ตัวอย่าง
SendNUIMessage({
action = 'open'
})
ข้อมูลจะถูกแปลงเป็น JSON-compatible message และส่งเข้า NUI Page
⑮ ฝั่ง JavaScript รับ SendNUIMessage อย่างไร
ใช้
window.addEventListener(
'message',
(event) => {
const data =
event.data;
console.log(data);
}
);
ถ้า Lua ส่ง
SendNUIMessage({
action = 'open'
})
JavaScript จะได้
event.data.action
เป็น
open
⑯ ตัวอย่างเปิด Menu
Client Lua
RegisterCommand(
'menu',
function()
SendNUIMessage({
action = 'open'
})
end,
false
)
JavaScript
window.addEventListener(
'message',
(event) => {
if (
event.data.action
=== 'open'
) {
menu.classList.add(
'active'
);
}
}
);
ตอน Player พิมพ์
/menu
UI จะแสดง
⑰ ส่งข้อมูลหลายค่าได้ไหม
ได้
Lua
SendNUIMessage({
action = 'open',
playerName = 'Somchai',
cash = 5000,
bank = 25000
})
JavaScript
console.log(
event.data.playerName
);
console.log(
event.data.cash
);
ข้อมูลต้องอยู่ในรูปที่ JSON Encode ได้
⑱ ส่ง Table ได้ไหม
ได้หากข้อมูล JSON-encodable
เช่น
SendNUIMessage({
action = 'vehicles',
vehicles = {
{
id = 1,
plate = 'ABC123',
model = 'sultan'
},
{
id = 2,
plate = 'XYZ789',
model = 'blista'
}
}
})
JavaScript จะได้รับ Array/Object ตามข้อมูลที่ส่ง
⑲ อย่าส่งข้อมูลมากเกินจำเป็น
ถ้า UI ต้องใช้แค่
plate
model
stored
ไม่จำเป็นต้องส่ง
Server secrets
Internal permissions
Database credentials
Raw SQL
ข้อมูลผู้เล่นคนอื่นทั้งหมด
Client และ NUI ต้องถูกมองเป็น Untrusted Environment
ส่งเฉพาะข้อมูลที่ต้องแสดงจริง
⑳ SetNuiFocus คืออะไร
SetNuiFocus ใช้กำหนด Keyboard Focus และ Mouse Cursor ให้ NUI
รูปแบบ
SetNuiFocus(
hasFocus,
hasCursor
)
ตัวอย่าง
SetNuiFocus(
true,
true
)
ทำให้ UI รับ Keyboard และ Mouse Cursor
㉑ เปิด UI พร้อม Focus
RegisterCommand(
'menu',
function()
SetNuiFocus(
true,
true
)
SendNUIMessage({
action = 'open'
})
end,
false
)
ตอนนี้ Player สามารถคลิก Button ใน NUI ได้
㉒ ปิด Focus อย่างไร
เมื่อปิด UI
SetNuiFocus(
false,
false
)
สำคัญมาก
ไม่เช่นนั้น Player อาจพบว่า
เมาส์ค้าง
เดินไม่ได้
Keyboard ไปอยู่ UI
แม้ Menu หายไปแล้ว
㉓ NUI Focus Stack คืออะไร
Cfx.re ระบุว่า NUI มี Focus Stack แบบจำกัด
Resource ที่ Focus ล่าสุดจะถูกจัดไว้ด้านบน
Fullscreen NUI ปัจจุบันทำงานในลักษณะ Fullscreen Iframes
และไม่มี Click-through ผ่าน Resource Layers ตาม Behavior ปัจจุบัน
ดังนั้น Server ที่มี UI หลาย Resource ต้องจัด Focus ให้ดี
㉔ UI หลายตัวแย่ง Focus กันได้ไหม
ได้
ตัวอย่าง
Inventory
Phone
Garage
Admin Menu
ถ้าแต่ละ Resource เรียก
SetNuiFocus(true, true)
โดยไม่ประสาน State กัน
Player อาจพบ
Cursor ผิด
UI ซ้อน
Input ค้าง
Resource ใหญ่ควรมี UI State Management ที่ชัดเจน
㉕ NUI ส่งข้อมูลกลับ Lua อย่างไร
ใช้ NUI Callback
Browser JavaScript ส่ง Request เช่น
fetch(
`https://${GetParentResourceName()}/close`,
{
method: 'POST',
headers: {
'Content-Type':
'application/json; charset=UTF-8'
},
body:
JSON.stringify({})
}
);
แล้ว Client Script Register Callback ชื่อ
close
㉖ GetParentResourceName คืออะไร
ใน NUI Browser สามารถใช้
GetParentResourceName()
เพื่อหาชื่อ Resource ปัจจุบัน
จึงไม่ต้อง Hardcode
fetch(
'https://my_inventory/close'
)
เพราะถ้า Resource Rename Request จะพัง
ใช้
const resourceName =
GetParentResourceName();
ดีกว่า
㉗ ทำไม NUI Callback ต้องใช้ https
Resource ที่ใช้
fx_version 'cerulean'
ทำงานกับ Secure-context NUI
ดังนั้น Callback Pattern ปัจจุบันใช้
https://
ไม่ใช่
http://
Script เก่าจำนวนมากที่ใช้ http://resource/event จึงอาจต้อง Migration
㉘ ตัวอย่าง Callback ปิด Menu
Browser
closeButton.addEventListener(
'click',
async () => {
await fetch(
`https://${GetParentResourceName()}/close`,
{
method: 'POST',
headers: {
'Content-Type':
'application/json; charset=UTF-8'
},
body:
JSON.stringify({})
}
);
}
);
Client Lua
RegisterNuiCallback(
'close',
function(data, cb)
SetNuiFocus(
false,
false
)
SendNUIMessage({
action = 'close'
})
cb({
ok = true
})
end
)
㉙ RegisterNuiCallback กับ RegisterNUICallback ต่างกันอย่างไร
เอกสาร Cfx.re ปัจจุบันระบุว่า API แบบเก่า
RegisterNUICallback
ถูกเก็บไว้เพื่อ Backward Compatibility
และแนะนำ New Code ให้ใช้
REGISTER_NUI_CALLBACK
โดย Lua wrapper/documentation ปัจจุบันใช้รูป
RegisterNuiCallback(
'eventName',
function(data, cb)
-- ...
end
)
ดังนั้น Script เก่าที่ใช้ RegisterNUICallback ยังพบได้มาก แต่ Code ใหม่ควรอ้าง API ปัจจุบัน
㉚ data ใน NUI Callback คืออะไร
ถ้า JavaScript ส่ง
body:
JSON.stringify({
vehicleId: 521
})
Client Lua จะได้
RegisterNuiCallback(
'selectVehicle',
function(data, cb)
print(
data.vehicleId
)
cb({
ok = true
})
end
)
POST JSON จะถูก Parse เป็นข้อมูลที่ Callback ใช้ได้ตาม Runtime API
㉛ cb คืออะไร
cb คือ Callback Response กลับไป Browser
ตัวอย่าง
cb({
ok = true
})
Browser สามารถอ่าน Response
const response =
await fetch(...);
const data =
await response.json();
console.log(
data.ok
);
㉜ ต้องเรียก cb ทุกครั้งไหม
ควรเรียกทุกครั้ง
Cfx.re ระบุชัดว่า NUI Callback ควรส่ง Response กลับเสมอ
ถ้าไม่ตอบ Callback
Request อาจ Timeout และ Error จะไปปรากฏใน Fetch ฝั่ง Browser
แม้ไม่มีข้อมูลก็ใช้
cb({})
หรือ
cb({
ok = true
})
ได้
㉝ ตัวอย่าง Error Handling
RegisterNuiCallback(
'getVehicle',
function(data, cb)
local vehicleId =
tonumber(
data.vehicleId
)
if not vehicleId then
cb({
ok = false,
error = 'invalid_vehicle'
})
return
end
cb({
ok = true,
id = vehicleId
})
end
)
ทุก Branch ตอบ cb
㉞ Browser ควรตรวจ HTTP Response ไหม
ควร
const response =
await fetch(
`https://${GetParentResourceName()}/getVehicle`,
options
);
if (!response.ok) {
console.error(
'NUI request failed'
);
return;
}
const data =
await response.json();
ทำให้ Debug ง่ายกว่าปล่อย Promise Error เงียบ ๆ
㉟ NUI Callback ติดต่อ Server โดยตรงไหม
โดยทั่วไป NUI Callback เข้าสู่ Client Runtime ของ Resource
ถ้าต้องการ Server Data ให้ Client ทำต่อ เช่น
NUI
↓
Client NUI Callback
↓
TriggerServerEvent
↓
Server
หรือใช้ Callback/Library Architecture ที่เหมาะสม
NUI Browser ไม่ควรกลายเป็น Server Authority
㊱ ตัวอย่างขอข้อมูลรถจาก Server
NUI
fetch(
`https://${GetParentResourceName()}/getVehicles`,
{
method: 'POST',
headers: {
'Content-Type':
'application/json; charset=UTF-8'
},
body:
JSON.stringify({})
}
);
Client
RegisterNuiCallback(
'getVehicles',
function(data, cb)
TriggerServerEvent(
'garage:getVehicles'
)
cb({
ok = true
})
end
)
แต่ถ้าต้องการ Response จริงจาก Server ควรใช้ Request/Response Architecture ที่ชัดเจนแทนการส่ง ok ก่อน Data พร้อม
㊲ Request/Response Garage ที่ดีควรเป็นอย่างไร
Concept
NUI
↓
Client
↓
Server Callback/Event Request
↓
Server Validate
↓
Database
↓
Server Result
↓
Client
↓
SendNUIMessage
↓
NUI Render
ทำให้ Server เป็นผู้ตัดสินข้อมูลจริง
㊳ อย่าเชื่อ vehicleId จาก NUI
NUI ส่ง
{
"vehicleId": 521
}
ไม่ได้แปลว่ารถ 521 เป็นของ Player
Server ต้องตรวจ
Character ID
Vehicle Owner
Stored State
Permissions
Garage
Cooldown
เหมือนกับ Client Input ประเภทอื่น
㊴ NUI ไม่ใช่ Security Boundary
ผู้เล่นสามารถแก้หรือเรียก Client-side Logic ได้หลายรูปแบบ
ดังนั้นห้ามคิดว่า
ปุ่มไม่มีใน UI
=
ผู้เล่นทำ Action ไม่ได้
Server Event ต้อง Validate เสมอ
เช่น Delete Vehicle ต้องตรวจ Server-side
ไม่ใช่อาศัยว่า UI ไม่มีปุ่ม Delete
㊵ NUI Asset URL ปัจจุบันใช้แบบไหน
Cfx.re ปัจจุบันลงทะเบียน Resource File Scope เช่น
https://cfx-nui-resourceName/
ตัวอย่าง Concept
<script
src="https://cfx-nui-my-resource/production.js"
></script>
ระบบเก่าเคยใช้
nui://
แต่ Documentation ปัจจุบันระบุว่า nui:// ไม่เป็น Secure Context ใน Browser รุ่นใหม่แล้ว
㊶ Relative Path ยังใช้ได้ไหม
หาก index.html, style.css และ app.js อยู่ Folder เดียวกัน สามารถใช้
<link
rel="stylesheet"
href="style.css"
>
<script
src="app.js"
></script>
ได้ตาม Resource Structure
ไม่จำเป็นต้องเขียน cfx-nui URL สำหรับทุก Asset
㊷ NUI ใช้ React ได้ไหม
ได้
NUI เป็น Web UI Environment
จึงสามารถใช้ Framework อย่าง
React
Vue
Angular
Svelte
ได้หาก Build Output ถูกจัด Deploy เข้า Resource อย่างถูกต้อง
Cfx.re Documentation ยกตัวอย่าง Web Framework เช่น React/Angular ในความสามารถของ NUI
㊸ React Resource ควรจัด Folder อย่างไร
ตัวอย่าง
my_phone/
├── fxmanifest.lua
├── client/
├── server/
└── web/
├── src/
├── package.json
└── dist/
Production Manifest ควรอ้าง Build Output เช่น
ui_page 'web/dist/index.html'
files {
'web/dist/index.html',
'web/dist/**/*'
}
ตาม Build Structure จริง
㊹ อย่าส่ง node_modules เข้า NUI โดยไม่จำเป็น
Project Frontend อาจมี
node_modules/
หลายร้อย MB
Production Resource ควร Build/Bundled Frontend แล้ว Deploy Output เช่น
dist/
แทนการ Pack Dependency Source ทั้งหมด
㊺ Responsive UI สำคัญไหม
สำคัญ
ผู้เล่นอาจใช้ Resolution
1920x1080
2560x1440
3440x1440
1366x768
ควรออกแบบ NUI ด้วย
Flexbox
Grid
vw/vh
rem
max-width
media queries
และทดสอบหลาย Aspect Ratios
㊻ อย่า Hardcode 1920x1080 ทุกอย่าง
ไม่ควร
#menu {
width: 1200px;
left: 360px;
}
แล้วหวังว่าจะเหมาะกับทุกจอ
ดีกว่าใช้
#menu {
width: min(90vw, 1000px);
}
หรือ Layout ที่ Responsive
㊼ NUI Animation กิน FPS ได้ไหม
ได้
โดยเฉพาะ
Blur หนัก
Box-shadow จำนวนมาก
Large transparent layers
High-resolution video
CSS animations ต่อเนื่อง
Canvas/WebGL หนัก
Browser UI ก็ใช้ CPU/GPU
UI ที่สวยไม่ได้แปลว่าต้อง Animate ทุก Element ตลอดเวลา
㊽ UI ซ่อนแล้ว JavaScript ยังทำงานไหม
เป็นไปได้
การใช้
display: none;
ซ่อน Element ไม่ได้แปลว่า JavaScript Timers หรือ Event Logic ทั้งหมดหยุด
ถ้ามี
setInterval(...)
requestAnimationFrame(...)
อาจยังมี Work
ต้องจัด Lifecycle ของ Frontend ด้วย
㊾ อย่าส่ง SendNUIMessage ทุก Frame
ตัวอย่างไม่ดี
CreateThread(function()
while true do
Wait(0)
SendNUIMessage({
action = 'update',
health = 100
})
end
end)
หากข้อมูลไม่จำเป็นต้อง Update ทุก Frameจะสร้าง Message Traffic โดยไม่จำเป็น
ควร Update เมื่อข้อมูลเปลี่ยน หรือใช้ Frequency ที่เหมาะสม
㊿ HUD ต้อง Update ทุก Frame ไหม
บาง Visual State อาจต้องความถี่สูง แต่ไม่ใช่ทุกค่า
เช่น
Player Name
Job
Cash
ไม่จำเป็นต้องส่ง 60 ครั้งต่อวินาที
ส่งเมื่อค่ามีการเปลี่ยนแปลงจะมีประสิทธิภาพกว่า
51 NUI Developer Tools ใช้อย่างไร
Cfx.re มี NUI Developer Tools สำหรับ Debug Browser UI
Documentation ปัจจุบันระบุว่า Remote Debugging ถูกเปิดให้เข้าถึงเมื่อเกมทำงาน
และสามารถใช้คำสั่ง
nui_devTools
ใน F8 เมื่อ Developer Mode เปิด
ช่วย Debug
Console
Network
DOM
CSS
JavaScript
Fetch
Errors
ได้เหมือน Browser DevTools
52 NUI Error ควรดูที่ไหน
ตรวจอย่างน้อย
F8
NUI DevTools Console
NUI Network
FXServer Console
เพราะ Error อาจอยู่คนละ Layer
ตัวอย่าง
CSS ไม่โหลด
→ Browser/NUI
fetch callback fail
→ Browser + Client
Server Event fail
→ FXServer Console
53 UI ไม่เปิดควรตรวจอะไร
ตรวจ
① Resource Started หรือไม่
② ui_page ถูกหรือไม่
③ index.html มีจริงไหม
④ files ครบไหม
⑤ JavaScript Error หรือไม่
⑥ SendNUIMessage ถูกเรียกไหม
⑦ action ตรงกันไหม
⑧ CSS ยัง display:none อยู่ไหม
อย่าเริ่มจาก Restart FXServer ทั้งเครื่อง
54 UI เปิดแต่คลิกไม่ได้
ตรวจ
SetNuiFocus(
true,
true
)
หากต้องใช้ Mouse
และดูว่า Resource อื่นแย่ง Focus หรือไม่
รวมถึงตรวจ CSS เช่น
pointer-events
z-index
overlay
ด้วย
55 ปิด UI แล้วเดินไม่ได้
สาเหตุพบบ่อยคือไม่ได้คืน Focus
ตอนปิดควรใช้
SetNuiFocus(
false,
false
)
และควร Cleanup ใน Resource Stop ด้วย
56 Resource Stop ต้อง Cleanup NUI
ตัวอย่าง
AddEventHandler(
'onClientResourceStop',
function(resourceName)
if resourceName ~=
GetCurrentResourceName() then
return
end
SetNuiFocus(
false,
false
)
SendNUIMessage({
action = 'close'
})
end
)
ช่วยลดปัญหา Cursor/Input ค้างหลัง restart resource
57 NUI State ควรแยกจาก Game State
ตัวอย่าง
UI open
UI selected tab
Search text
เป็น Presentation State
ส่วน
Vehicle owner
Player money
Inventory count
Permission
เป็น Game/Server State
อย่าทำให้ Browser เป็น Source of Truth ของข้อมูลสำคัญ
58 ตัวอย่าง Resource NUI ที่สมบูรณ์
โครงสร้าง
com_menu/
├── fxmanifest.lua
├── client.lua
└── html/
├── index.html
├── style.css
└── app.js
fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
author 'comsiam'
description 'FiveM NUI example'
version '1.0.0'
ui_page 'html/index.html'
files {
'html/index.html',
'html/style.css',
'html/app.js'
}
client_script 'client.lua'
client.lua
local menuOpen =
false
local function setMenu(
state
)
menuOpen =
state
SetNuiFocus(
state,
state
)
SendNUIMessage({
action =
state
and 'open'
or 'close'
})
end
RegisterCommand(
'uimenu',
function()
setMenu(
not menuOpen
)
end,
false
)
RegisterNuiCallback(
'close',
function(data, cb)
setMenu(
false
)
cb({
ok = true
})
end
)
AddEventHandler(
'onClientResourceStop',
function(resourceName)
if resourceName ~=
GetCurrentResourceName() then
return
end
SetNuiFocus(
false,
false
)
end
)
index.html
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
>
<title>FiveM NUI</title>
<link
rel="stylesheet"
href="style.css"
>
</head>
<body>
<main
id="menu"
class="menu"
>
<h1>
FiveM NUI
</h1>
<p>
HTML CSS JavaScript
</p>
<button id="close">
ปิดเมนู
</button>
</main>
<script src="app.js"></script>
</body>
</html>
style.css
* {
box-sizing: border-box;
}
body {
margin: 0;
overflow: hidden;
font-family: Arial, sans-serif;
}
.menu {
display: none;
position: absolute;
top: 50%;
left: 50%;
width: min(90vw, 700px);
transform:
translate(-50%, -50%);
padding: 32px;
background:
rgba(20, 20, 20, 0.95);
border-radius: 16px;
}
.menu.active {
display: block;
}
app.js
const menu =
document.getElementById(
'menu'
);
const closeButton =
document.getElementById(
'close'
);
window.addEventListener(
'message',
(event) => {
const data =
event.data;
if (
data.action === 'open'
) {
menu.classList.add(
'active'
);
}
if (
data.action === 'close'
) {
menu.classList.remove(
'active'
);
}
}
);
closeButton.addEventListener(
'click',
async () => {
try {
const response =
await fetch(
`https://${GetParentResourceName()}/close`,
{
method: 'POST',
headers: {
'Content-Type':
'application/json; charset=UTF-8'
},
body:
JSON.stringify({})
}
);
const data =
await response.json();
if (!data.ok) {
console.error(
'Close request failed'
);
}
} catch (error) {
console.error(
'NUI callback error:',
error
);
}
}
);
ตอนนี้พิมพ์
/uimenu
จะเกิด
Client Command
↓
SetNuiFocus
↓
SendNUIMessage
↓
Browser เปิด Menu
↓
Player คลิกปิด
↓
fetch https://resource/close
↓
RegisterNuiCallback
↓
SetNuiFocus(false, false)
↓
SendNUIMessage close
↓
Browser ซ่อน Menu
นี่คือวงจร NUI พื้นฐานที่สมบูรณ์
59 Checklist FiveM NUI
ตรวจ
① fxmanifest.lua มีหรือไม่?
② fx_version เป็น cerulean หรือไม่?
③ ui_page ถูกหรือไม่?
④ index.html มีจริงหรือไม่?
⑤ CSS อยู่ใน files หรือไม่?
⑥ JavaScript อยู่ใน files หรือไม่?
⑦ Images/Fonts อยู่ใน files หรือไม่?
⑧ HTML Path ถูกหรือไม่?
⑨ JS ไม่มี Error หรือไม่?
⑩ SendNUIMessage ทำงานหรือไม่?
⑪ window message listener มีหรือไม่?
⑫ action ชื่อตรงกันหรือไม่?
⑬ SetNuiFocus ถูกหรือไม่?
⑭ ปิดแล้วคืน Focus หรือไม่?
⑮ Browser Callback ใช้ https หรือไม่?
⑯ ใช้ GetParentResourceName หรือไม่?
⑰ NUI Callback ชื่อตรงหรือไม่?
⑱ cb ถูกเรียกทุก Branch หรือไม่?
⑲ JSON Data ถูกต้องหรือไม่?
⑳ Client Input ถูก Validate หรือไม่?
㉑ Server Validate Action หรือไม่?
㉒ มี Generic Dangerous Event หรือไม่?
㉓ UI Responsive หรือไม่?
㉔ Animation หนักเกินไปหรือไม่?
㉕ SendNUIMessage ถี่เกินไปหรือไม่?
㉖ Timers ทำงานตอน UI ปิดหรือไม่?
㉗ Resource Stop คืน Focus หรือไม่?
㉘ NUI DevTools มี Error หรือไม่?
㉙ Hardcode Resource Name หรือไม่?
㉚ Test หลัง restart Resource หรือยัง?
⑥⓪ Architecture NUI สำหรับ Resource ใหญ่
ระบบอย่าง Inventory อาจใช้
Client
├── nui.lua
├── inventory.lua
└── events.lua
Server
├── inventory.lua
├── validation.lua
└── database.lua
Web
├── HTML
├── CSS
└── JavaScript
Flow เปิด Inventory
Player กด Key
↓
Client
↓
ขอ Inventory จาก Server
↓
Server ตรวจ Player
↓
อ่าน Runtime/Database
↓
ส่ง Inventory Data
↓
Client
↓
SendNUIMessage
↓
NUI Render
Player ใช้ Item
NUI
↓
Callback
↓
Client
↓
Server Request
↓
Server ตรวจ
↓
Inventory Update
↓
Server ส่ง State ใหม่
↓
Client
↓
NUI Update
จุดสำคัญคือ
NUI
=
Presentation + Input
ไม่ใช่
NUI
=
Authority
Fullscreen NUI กับ DUI ต่างกันอย่างไร
Fullscreen NUI คือ UI Overlay บนหน้าจอ
เช่น
Phone
Inventory
HUD
Menu
ส่วน DUI หรือ Direct-rendered UI ใช้ Browser Content ไป Render กับสิ่งอื่น เช่น Runtime Texture ตาม Use Case
สำหรับผู้เริ่มต้นควรเรียน Fullscreen NUI ก่อน เพราะเป็นรูปแบบที่พบมากที่สุดใน FiveM Scripts
NUI กับ Loading Screen เหมือนกันไหม
Loading Screen เป็น NUI Frame อีกประเภทหนึ่ง
Manifest ใช้
loadscreen 'load.html'
แทน ui_page
เหมาะกับหน้า Loading ตอน Player Join Server
จึงเป็นคนละ Use Case กับ Inventory/Menu Overlay ระหว่าง Gameplay
NUI ใช้ External Website ได้ไหม
Cfx.re รองรับ ui_page ที่เป็น External URL ได้
แต่ Resource ทั่วไปมักใช้ Local Files เพราะ
โหลดควบคุมง่าย
ไม่พึ่ง External Host
Version ตรงกับ Resource
Latency ต่ำกว่า
External UI ต้องคิดเรื่อง Availability, Security และ Network เพิ่ม
NUI ทำ HTTP Request ออก Internet ได้ไหม
ใน Browser Environment สามารถมี Web Requests ตามข้อจำกัดของ Browser/CORS/Security Context
แต่ไม่ควรเอา API Secret ไปไว้ใน Frontend
ถ้าต้องใช้ Private API
Architecture ควรเป็น
NUI
↓
Client
↓
Server
↓
Private API
และ Secret อยู่ Server-side
ห้ามใส่ API Secret ใน app.js
เช่น
const API_KEY =
'secret-key';
ใน NUI ถือว่า Client สามารถเข้าถึงได้
เช่นเดียวกับเว็บไซต์ Frontend
Secret ต้องอยู่ Backend/Server-side
NUI Performance ควรคิดอย่างไร
Performance มี 3 ส่วน
Client Lua
+
Browser JavaScript
+
Rendering CSS/GPU
ดังนั้น Resource ที่ Resmon ต่ำไม่ได้หมายความว่า UI เบาเสมอไป
Browser อาจใช้ CPU/GPU สูงจาก
JavaScript loops
DOM updates
Animations
Blur
Video
WebGL
ต้องตรวจทั้ง Lua Profiler และ Browser DevTools
วิธีลด NUI กินเครื่อง
แนวทางหลัก
ส่ง Message เมื่อข้อมูลเปลี่ยน
ลด DOM updates
หยุด Timer ตอน UI ปิด
ลด Animation ที่ไม่จำเป็น
ลด Blur/Shadow หนัก
Optimize Images
Build Frontend Production Mode
ลด Console Logging
ไม่ Render List หลายพันรายการพร้อมกัน
ถ้า Inventory มี 5,000 Items อาจใช้ Virtualization/Pagination แทน Render ทุก Element
คำถามที่พบบ่อยเกี่ยวกับ FiveM NUI
FiveM NUI คืออะไร
คือระบบ HTML-based UI ของ CitizenFX/FiveM ที่ใช้ Web Technologies สร้าง Interface ภายในเกม
NUI ใช้ภาษาอะไร
ส่วน Frontend ใช้ HTML, CSS และ JavaScript และสามารถใช้ Framework เว็บต่าง ๆ ได้
FiveM NUI ใช้ Chromium ไหม
ปัจจุบันใช้ Chromium Embedded Framework หรือ CEF
ui_page คืออะไร
กำหนดหน้า Root ของ Fullscreen NUI Resource
files มีไว้ทำอะไร
ระบุ Client-side Assets ที่ต้องเพิ่มเข้า Resource Packfile
SendNUIMessage คืออะไร
ส่งข้อมูลจาก Client Runtime ไป NUI Browser
JavaScript รับ Message อย่างไร
ใช้
window.addEventListener(
'message',
...
);
SetNuiFocus คืออะไร
กำหนด Keyboard/Mouse Focus ให้ NUI
เปิด Cursor อย่างไร
เช่น
SetNuiFocus(
true,
true
)
ปิด Cursor อย่างไร
SetNuiFocus(
false,
false
)
NUI ส่งข้อมูลกลับ Lua อย่างไร
ใช้ Browser fetch() ไปยัง NUI Callback
NUI Callback ใช้ http หรือ https
สำหรับ cerulean ใช้ https://
GetParentResourceName ใช้ทำอะไร
คืนชื่อ Resource ที่เป็น Parent ของ NUI เพื่อไม่ต้อง Hardcode Resource Name
RegisterNUICallback ยังใช้ได้ไหม
เอกสารปัจจุบันระบุว่า Legacy API ถูกเก็บไว้เพื่อ Backward Compatibility ส่วน New Code ควรใช้ API REGISTER_NUI_CALLBACK/wrapper ปัจจุบัน
ต้องเรียก cb หรือไม่
ต้องตอบ Callback เพื่อไม่ให้ Request ค้าง/Timeout
cb({}) ได้ไหม
ได้ถ้าไม่มีข้อมูลต้องส่งกลับ
NUI เชื่อ Client ได้ไหม
ไม่ Client/NUI ต้องถือเป็น Untrusted
NUI ต่อ Database ตรง ๆ ได้ไหม
ไม่ควร Database Access และ Credentials ต้องอยู่ Server-side
React ใช้กับ FiveM ได้ไหม
ได้ Build แล้วใช้ Output เป็น NUI Files
Vue ใช้ได้ไหม
ได้
Angular ใช้ได้ไหม
ได้
NUI ทำให้ FPS ตกได้ไหม
ได้ ถ้า JavaScript, DOM, CSS Animation, Blur, Video หรือ WebGL หนักเกินไป
SendNUIMessage ทุก Frame ดีไหม
ไม่ควรหากข้อมูลไม่ได้ต้อง Update ทุก Frame
ปิด Resource แล้ว Mouse ค้างแก้อย่างไร
ทำ Cleanup และเรียก SetNuiFocus(false, false) ตอน Resource Stop
UI เปิดแต่ CSS ไม่มาแก้อย่างไร
ตรวจ files, CSS Path และ Browser DevTools
UI เปิดแต่ Button ไม่กดได้แก้อย่างไร
ตรวจ Focus, CSS pointer-events, Overlay และ JavaScript Errors
Callback ไม่ทำงานแก้อย่างไร
ตรวจ https://, Resource Name, Callback Name, Browser Network และ Client Handler
nui:// ยังควรใช้ไหม
Documentation ปัจจุบันระบุว่า Scheme เก่าไม่เป็น Secure Context ใน Browser รุ่นใหม่ และใช้ https://cfx-nui-resource/... สำหรับ Resource File Scope แทน
สรุป FiveM NUI คืออะไร ใช้ HTML CSS JavaScript อย่างไร
FiveM NUI คือระบบสร้าง UI ภายในเกมด้วย Web Technologies โดย CEF ทำหน้าที่ Render HTML, CSS และ JavaScript
โครงสร้างพื้นฐานคือ
fxmanifest.lua
↓
ui_page
↓
HTML
↓
CSS + JavaScript
Client ส่งข้อมูลเข้า UI ด้วย
SendNUIMessage({
action = 'open'
})
Browser รับด้วย
window.addEventListener(
'message',
(event) => {
console.log(
event.data
);
}
);
และเมื่อ Player ต้องส่ง Action กลับ FiveM
fetch(
`https://${GetParentResourceName()}/close`,
...
);
Client รับด้วย NUI Callback
RegisterNuiCallback(
'close',
function(data, cb)
SetNuiFocus(
false,
false
)
cb({
ok = true
})
end
)
สำหรับ Interactive UI ยังต้องใช้
SetNuiFocus(
true,
true
)
และคืน Focus เมื่อปิด
SetNuiFocus(
false,
false
)
สิ่งสำคัญที่สุดคือแยกหน้าที่ให้ชัด
HTML
→ Structure
CSS
→ Appearance
JavaScript
→ Frontend Logic
Client Lua/JS/C#
→ Game ↔ UI Bridge
Server
→ Authority / Validation
Database
→ Persistence
NUI จึงไม่ควรเป็นผู้ตัดสินว่า Player มีเงินเท่าไร เป็นเจ้าของรถหรือไม่ หรือมี Permission อะไร เพราะ Browser และ Client เป็นฝั่งที่ไม่ควรเชื่อถือสำหรับข้อมูลสำคัญ
สำหรับผู้พัฒนา FiveM กับ comsiam การสร้าง NUI ที่ดีไม่ใช่เพียงทำ UI ให้สวย แต่ต้องทำให้เปิด-ปิดได้สะอาด, Focus ไม่ค้าง, Callback ตอบกลับครบ, Responsive และไม่ส่งข้อมูลหรือ Message มากเกินความจำเป็น
หลักสำคัญจาก comsiam คือให้คิด NUI เป็น Frontend ของ FiveM Resource ไม่ใช่ Backend — UI รับ Input และแสดงผล ส่วนการตรวจสิทธิ์ เงิน Inventory Vehicle Ownership และ Business Logic สำคัญต้องกลับไปตัดสินที่ Server
หัวข้อถัดไปคือ FiveM RegisterNUICallback คืออะไร ใช้อย่างไร ซึ่งจะเจาะระบบ Browser → FiveM โดยเฉพาะ ตั้งแต่ fetch(), POST JSON, data, cb, Error Handling, Response, Client→Server Flow และข้อแตกต่างระหว่าง API Legacy กับ NUI Callback API ปัจจุบัน
Comments
Post a Comment