FiveM SendNUIMessage และ SetNuiFocus ใช้อย่างไร? เปิด UI ส่งข้อมูล และจัดการ Mouse Keyboard ให้ถูกต้อง
FiveM SendNUIMessage ใช้ส่งข้อมูลจาก Client Script ไปยัง NUI Browser ส่วน SetNuiFocus ใช้กำหนดว่า NUI จะรับ Keyboard และ Mouse Cursor หรือไม่ สองคำสั่งนี้จึงมักถูกใช้คู่กันในระบบ Inventory, Phone, Garage, Shop, Banking, Character Creator และ Menu ต่าง ๆ
Flow พื้นฐานคือ
FiveM Client
↓
SetNuiFocus(true, true)
↓
SendNUIMessage(...)
↓
NUI Browser
↓
HTML / CSS / JavaScript
ตัวอย่างเปิด UI
SetNuiFocus(
true,
true
)
SendNUIMessage({
action = 'open'
})
JavaScript รับข้อมูล
window.addEventListener(
'message',
(event) => {
const data =
event.data;
if (data.action === 'open') {
document
.getElementById('menu')
.classList
.add('active');
}
}
);
เมื่อปิด
SetNuiFocus(
false,
false
)
SendNUIMessage({
action = 'close'
})
นี่คือพื้นฐานสำคัญของระบบ Client → NUI ใน FiveM
① SendNUIMessage คืออะไร
SendNUIMessage เป็น Lua Convenience Function สำหรับส่งข้อมูลจาก Client Runtime ไปยัง NUI Page ของ Resource ปัจจุบัน
รูปแบบ
SendNUIMessage({
action = 'open'
})
Cfx.re ระบุว่า Lua Wrapper จะ Encode Table เป็น JSON สำหรับ NUI ให้อัตโนมัติ
ดังนั้น Browser สามารถรับข้อมูลผ่าน event.data
② SendNUIMessage ทำงานฝั่งไหน
ใช้งานจาก Client Script
Flow คือ
client.lua
↓
SendNUIMessage
↓
NUI Browser
ไม่ใช่
server.lua
↓
SendNUIMessage
หาก Server ต้องการเปลี่ยน NUI ของ Player ต้องส่งข้อมูลมายัง Client ก่อน
③ Server จะอัปเดต NUI อย่างไร
ตัวอย่าง
Server
↓
TriggerClientEvent
↓
Client
↓
SendNUIMessage
↓
NUI
Server
TriggerClientEvent(
'bank:updateBalance',
source,
balance
)
Client
RegisterNetEvent(
'bank:updateBalance',
function(balance)
SendNUIMessage({
action = 'updateBalance',
balance = balance
})
end
)
Browser
window.addEventListener(
'message',
(event) => {
if (
event.data.action
=== 'updateBalance'
) {
console.log(
event.data.balance
);
}
}
);
④ JavaScript รับ SendNUIMessage อย่างไร
ใช้
window.addEventListener(
'message',
(event) => {
console.log(
event.data
);
}
);
ถ้า Lua ส่ง
SendNUIMessage({
action = 'hello',
name = 'Somchai'
})
Browser ได้
event.data.action
และ
event.data.name
⑤ action มีไว้ทำอะไร
action ไม่ใช่ Field ที่ FiveM บังคับ
แต่เป็น Convention ที่ดีสำหรับบอกว่า Message นี้ต้องการให้ UI ทำอะไร
เช่น
open
close
updateMoney
updateInventory
updateVehicle
showNotification
setJob
ตัวอย่าง
SendNUIMessage({
action = 'updateMoney',
cash = 5000
})
⑥ ใช้ type แทน action ได้ไหม
ได้
เอกสาร Cfx.re เองมีตัวอย่าง
SendNUIMessage({
type = 'open'
})
ดังนั้นคุณจะใช้
action
type
event
ก็ได้
สิ่งสำคัญคือต้องใช้ Naming Convention ให้สม่ำเสมอทั้ง Resource
⑦ SendNUIMessage ส่ง String ได้ไหม
โดยทั่วไปส่ง Table ที่สามารถแปลงเป็น JSON ได้
เช่น
SendNUIMessage({
message = 'Hello FiveM'
})
Browser จะได้
event.data.message
เป็น String
⑧ ส่ง Number ได้ไหม
ได้
SendNUIMessage({
action = 'money',
cash = 5000,
bank = 25000
})
Browser
const cash =
event.data.cash;
⑨ ส่ง Boolean ได้ไหม
ได้
SendNUIMessage({
action = 'setSeatbelt',
enabled = true
})
Browser
if (
event.data.enabled
) {
// enabled
}
⑩ ส่ง Lua Table ซ้อนกันได้ไหม
ได้ หากข้อมูลสามารถ Serialize เป็น JSON ได้
SendNUIMessage({
action = 'playerData',
player = {
name = 'Somchai',
cash = 5000,
job = {
name = 'police',
grade = 2
}
}
})
Browser
const player =
event.data.player;
console.log(
player.job.name
);
⑪ ส่ง Array ได้ไหม
ได้
Lua
SendNUIMessage({
action = 'vehicles',
vehicles = {
{
id = 1,
plate = 'ABC123'
},
{
id = 2,
plate = 'XYZ789'
}
}
})
Browser
const vehicles =
event.data.vehicles;
vehicles.forEach(
(vehicle) => {
console.log(
vehicle.plate
);
}
);
⑫ อย่าส่งข้อมูลที่ NUI ไม่จำเป็นต้องรู้
หาก Garage UI ต้องใช้
Vehicle ID
Plate
Model
Stored
ก็ส่งเท่านี้
ไม่จำเป็นต้องส่ง
Database password
Server secrets
Admin tokens
Internal SQL
Private server configuration
NUI อยู่ฝั่ง Client
จึงไม่ควรได้รับ Secrets
⑬ SetNuiFocus คืออะไร
SetNuiFocus เป็น Native สำหรับกำหนด Input Focus ให้ NUI Resource
พื้นฐาน
SetNuiFocus(
hasFocus,
hasCursor
)
ตัวอย่าง
SetNuiFocus(
true,
true
)
หมายถึงให้ NUI รับ Keyboard Focus และ Mouse Cursor
⑭ Parameter ตัวแรกคืออะไร
ตัวแรก
hasFocus
ควบคุม Keyboard/Input Focus
ตัวอย่าง
SetNuiFocus(
true,
false
)
NUI มี Focus แต่ไม่เปิด Mouse Cursor
เหมาะกับ UI บางประเภทที่ใช้ Keyboard เป็นหลัก
⑮ Parameter ตัวที่สองคืออะไร
ตัวที่สอง
hasCursor
ควบคุม Mouse Cursor
ตัวอย่าง
SetNuiFocus(
true,
true
)
เหมาะกับ
Inventory
Phone
Shop
Garage
Admin Menu
ที่ต้องคลิก Buttons
⑯ เปิด Menu แบบมาตรฐาน
local menuOpen =
false
local function openMenu()
menuOpen =
true
SetNuiFocus(
true,
true
)
SendNUIMessage({
action = 'open'
})
end
นี่เป็น Pattern ที่ใช้กันมาก
⑰ ปิด Menu แบบมาตรฐาน
local function closeMenu()
menuOpen =
false
SetNuiFocus(
false,
false
)
SendNUIMessage({
action = 'close'
})
end
จุดสำคัญคือ ปิดทั้ง UI และ Focus
⑱ ทำไมปิด HTML อย่างเดียวไม่พอ
สมมติ JavaScript ทำ
menu.classList.remove(
'active'
);
แต่ Client ยังมี
SetNuiFocus(
true,
true
)
Player อาจเจอ
UI หาย
แต่ Mouse ยังขึ้น
เดินไม่ได้
Keyboard ไม่กลับเข้าเกม
จึงต้องคืน Focus ฝั่ง Client
⑲ SetNuiFocus(false, false) สำคัญมาก
เมื่อ UI ปิด
SetNuiFocus(
false,
false
)
ควรเป็นส่วนหนึ่งของ Close Function เสมอ
อย่ากระจาย Logic ปิด UIหลายจุดโดยลืมคืน Focus บาง Branch
⑳ สร้าง Function กลางสำหรับ UI State
แนะนำ
local uiOpen =
false
local function setUiState(
state
)
uiOpen =
state
SetNuiFocus(
state,
state
)
SendNUIMessage({
action =
state
and 'open'
or 'close'
})
end
เปิด
setUiState(
true
)
ปิด
setUiState(
false
)
ลดโอกาส UI State กับ Focus ไม่ตรงกัน
㉑ ทำ Toggle Menu ได้อย่างไร
RegisterCommand(
'menu',
function()
setUiState(
not uiOpen
)
end,
false
)
Player พิมพ์
/menu
ครั้งแรกเปิด
ครั้งต่อไปปิด
㉒ NUI ส่งคำสั่งปิดกลับมายัง Client อย่างไร
Browser
await fetch(
`https://${GetParentResourceName()}/close`,
{
method: 'POST',
headers: {
'Content-Type':
'application/json; charset=UTF-8'
},
body:
JSON.stringify({})
}
);
Client
RegisterNuiCallback(
'close',
function(data, cb)
setUiState(
false
)
cb({
ok = true
})
end
)
㉓ ESC ควรปิดผ่าน Client ด้วย
JavaScript
document.addEventListener(
'keydown',
async (event) => {
if (
event.key !== 'Escape'
) {
return;
}
await closeUi();
}
);
closeUi() ควรเรียก NUI Callback ไป Client
ไม่ควรเพียงซ่อน DOM เพราะ Focus ยังอยู่ฝั่ง FiveM
㉔ NUI Focus Stack คืออะไร
Cfx.re ระบุว่า Fullscreen NUI มี Focus Stack แบบจำกัด
Resource ที่ถูก Focus ล่าสุดจะอยู่ด้านบนของ Focus Stack
Concept
Inventory Focus
↓
Phone Focus
↓
Admin Menu Focus ล่าสุด
Resource ล่าสุดจะอยู่ด้านบนตามระบบ Focus
จึงต้องระวัง UI หลาย Resource เปิดพร้อมกัน
㉕ NUI หลาย Resource แย่ง Focus กันได้ไหม
ได้
สมมติ
Inventory
Phone
Garage
แต่ละ Resource เรียก
SetNuiFocus(
true,
true
)
พร้อมกัน
อาจเกิด
Mouse ไปผิด UI
Keyboard Focus ผิด
UI อยู่ด้านหลัง
Input สับสน
จึงควรมี UI State Coordination บน Server ที่มีหลาย Interface
㉖ Click-through ข้าม NUI Resource ได้ไหม
Cfx.re ระบุว่า NUI Resources ปัจจุบันเป็น Fullscreen Iframes และ ไม่มี Click-through ข้าม Resources
ดังนั้น Resource ที่อยู่ด้านบนสามารถขวาง Interaction ของ Resource ที่อยู่ด้านล่างได้
นี่เป็นอีกเหตุผลที่ Focus Management สำคัญ
㉗ HUD ต้อง SetNuiFocus ไหม
ไม่จำเป็น
HUD ที่แค่แสดง
Health
Armor
Cash
Job
Speed
Fuel
ไม่ต้องให้ Player Click
จึงสามารถใช้
SendNUIMessage(...)
อย่างเดียวโดยไม่ต้อง
SetNuiFocus(
true,
true
)
㉘ Interactive Menu ถึงค่อย Focus
เช่น
Inventory
Shop
Character Creator
Banking Menu
Phone
ต้องใช้ Mouse/Keyboard
จึงค่อย
SetNuiFocus(
true,
true
)
อย่า Focus NUI หากไม่จำเป็น
㉙ SendNUIMessage ใช้อัปเดต HUD อย่างไร
Lua
SendNUIMessage({
action = 'updateHud',
health = health,
armor = armor,
cash = cash
})
Browser
if (
event.data.action
=== 'updateHud'
) {
updateHud(
event.data
);
}
แต่ต้องคิดเรื่อง Frequency ด้วย
㉚ อย่าส่ง NUI Message ทุก Frame โดยไม่มีเหตุผล
ไม่แนะนำ
CreateThread(function()
while true do
Wait(0)
SendNUIMessage({
action = 'update',
cash = cash
})
end
end)
หาก cash เปลี่ยนทุก 10 นาที ก็ไม่มีเหตุผลส่งประมาณทุก Render Tick
㉛ ส่ง Message เมื่อข้อมูลเปลี่ยนดีกว่า
เช่นเงินเปลี่ยน
RegisterNetEvent(
'player:moneyChanged',
function(cash)
SendNUIMessage({
action = 'updateCash',
cash = cash
})
end
)
Pattern นี้เป็น Event-driven
Value เปลี่ยน
↓
ส่ง Message
แทน
ถาม Value ซ้ำทั้งเวลา
㉜ ถ้าต้อง Poll ควรมี Wait
บางค่าอาจต้อง Poll
เช่น Speedometer
แต่ไม่จำเป็นต้อง Poll ทุกอย่างด้วย Wait(0)
ตัวอย่าง
CreateThread(function()
while true do
Wait(200)
local ped =
PlayerPedId()
local vehicle =
GetVehiclePedIsIn(
ped,
false
)
if vehicle ~= 0 then
local speed =
GetEntitySpeed(
vehicle
)
SendNUIMessage({
action = 'speed',
speed = speed
})
end
end
end)
Frequency ที่เหมาะขึ้นกับ UX และระบบ
㉝ Cache ค่าเดิมก่อนส่งได้ไหม
ได้ และเป็น Pattern ที่ดี
local lastCash =
nil
local function updateCash(
cash
)
if cash == lastCash then
return
end
lastCash =
cash
SendNUIMessage({
action = 'cash',
value = cash
})
end
หากค่าไม่เปลี่ยนก็ไม่ส่ง Message
㉞ ส่ง Object ใหญ่เกินไปมีผลไหม
มี Cost ด้าน
Serialization
Message transfer
Browser parsing
DOM rendering
สมมติ Inventory มี 10,000 Items
ไม่ควรส่งข้อมูลทั้งหมดใหม่ทุกครั้งที่ Item จำนวนหนึ่งเปลี่ยน
สามารถส่ง Delta Update
SendNUIMessage({
action = 'updateSlot',
slot = 5,
item = item
})
แทน Reload Inventory ทั้งชุด
㉟ Full State กับ Delta Update ต่างกันอย่างไร
Full State
ส่ง Inventory ทุก Slot ใหม่
Delta
ส่งเฉพาะ Slot 5 ที่เปลี่ยน
Full State เหมาะตอนเปิด UI ครั้งแรก
Delta เหมาะตอนมี Update เล็ก ๆ ขณะ UI เปิด
㊱ UI ปิดแล้วยังต้อง Send Message ไหม
ขึ้นกับ State Architecture
ถ้า UI ปิดและข้อมูลไม่ต้อง Update Real-time ใน Browser อาจไม่ต้องส่งทุก Update
ตอนเปิดใหม่ส่ง Full State ล่าสุดทีเดียว
ลด Work ฝั่ง Browser
㊲ ตัวอย่าง State Guard
local uiOpen =
false
local function updateInventory(
inventory
)
if not uiOpen then
return
end
SendNUIMessage({
action = 'inventory',
items = inventory
})
end
แต่ต้องมั่นใจว่าตอนเปิด UI จะส่ง State ล่าสุดใหม่เสมอ
㊳ เปิด UI ควรส่งข้อมูลพร้อมกันอย่างไร
ตัวอย่าง
local function openInventory(
inventory
)
SetNuiFocus(
true,
true
)
SendNUIMessage({
action = 'openInventory',
items = inventory
})
end
Browser ได้ทั้ง
เปิด UI
+
ข้อมูลเริ่มต้น
ใน Message เดียว
㊴ แยก open กับ setData ได้ไหม
ได้
SendNUIMessage({
action = 'setInventory',
items = inventory
})
SendNUIMessage({
action = 'open'
})
เหมาะถ้า Frontend Architecture แยก State กับ Visibility
แต่ต้องจัด Ordering ให้คาดเดาได้
㊵ Browser ต้องมี Message Router ไหม
Resource ใหญ่ควรมี
window.addEventListener(
'message',
(event) => {
const data =
event.data;
switch (
data.action
) {
case 'open':
openUi();
break;
case 'close':
closeUi();
break;
case 'updateCash':
updateCash(
data.cash
);
break;
case 'vehicles':
renderVehicles(
data.vehicles
);
break;
}
}
);
อ่านง่ายกว่า if หลายสิบชุดกระจายหลายไฟล์
㊶ ควร Validate Message ใน Browser ไหม
ควรในระดับ Frontend Robustness
เช่น
if (
typeof data.cash
!== 'number'
) {
return;
}
แต่ Browser Validation ไม่ใช่ Security Boundary
Server ยังเป็น Authority ของข้อมูลสำคัญ
㊷ SendNUIMessage ปลอดภัยไหม
มันเป็น Client→NUI Communication
จึงไม่ได้ทำให้ข้อมูลกลายเป็น Secret
ข้อมูลที่ส่งเข้า Browser ควรถือว่า Player สามารถเข้าถึงได้
อย่าส่ง
Database credential
Private API key
Server-only formulas ที่ไม่จำเป็น
Admin secret
เข้า NUI
㊸ Server ควรส่งข้อมูลอะไรเข้า NUI
ส่งเฉพาะ Presentation Data
เช่น
Player display name
Cash
Vehicle plate
Vehicle model
Inventory items
Shop display prices
โดยระวังว่า Display Price ใน Client/NUI ไม่ควรถูกใช้เป็น Authority ตอนซื้อจริง
Server ควรคำนวณ Price อีกครั้ง
㊹ SetNuiFocus กับ Security เกี่ยวไหม
ไม่
การที่ NUI ไม่มี Focus ไม่ได้ป้องกัน Player จากการ Manipulate Client
และการที่ UI ปิดไม่ได้หมายความว่า Server Event ปลอดภัย
Security ต้องอยู่ที่
Server validation
Permissions
State validation
Rate limits
Authoritative calculations
㊺ UI เปิดแล้ว Player ควรเดินได้ไหม
ขึ้นกับ Design
ถ้า Inventory เปิดและคุณไม่ต้องการให้ Player เดิน
Focus/Input Architecture อาจ Block Gameplay Input
แต่ถ้าต้องการ Overlay ที่ Interaction น้อย ก็ออกแบบต่างได้
อย่าคิดว่า NUI ทุกประเภทต้องมี Focus เหมือนกัน
㊻ Resource Restart แล้ว Focus ค้างได้ไหม
ถ้า Resource ออกแบบ Cleanup ไม่ดี Player อาจพบ Input State แปลกหลัง Development Restart
ควรทำ Cleanup
AddEventHandler(
'onClientResourceStop',
function(resourceName)
if resourceName ~=
GetCurrentResourceName() then
return
end
SetNuiFocus(
false,
false
)
end
)
นี่สำคัญมากสำหรับ Resource ที่ Restart บ่อย
㊼ Resource Stop ควรส่ง close message ไหม
สามารถทำ
AddEventHandler(
'onClientResourceStop',
function(resourceName)
if resourceName ~=
GetCurrentResourceName() then
return
end
SetNuiFocus(
false,
false
)
SendNUIMessage({
action = 'close'
})
end
)
แต่ Resource Runtime/NUI Lifecycle กำลังถูกหยุดอยู่ จึงควรมอง Focus Cleanup เป็นสิ่งสำคัญที่สุด
Frontend เองก็ควร Initialize State ใหม่ได้เมื่อ Resource Start อีกครั้ง
㊽ Resource Start ควร Reset NUI State ไหม
เป็นแนวทางที่ดี
AddEventHandler(
'onClientResourceStart',
function(resourceName)
if resourceName ~=
GetCurrentResourceName() then
return
end
SetNuiFocus(
false,
false
)
SendNUIMessage({
action = 'reset'
})
end
)
ช่วยให้ Development Restart ไม่ค้าง State จาก Logic ก่อนหน้า
㊾ JavaScript Error ทำให้ SendNUIMessage ดูเหมือนไม่ทำงานได้
ได้
Lua อาจส่ง Message ถูกต้อง
แต่ Browser Listener Error เช่น
document
.getElementById('cash')
.innerText = data.cash;
แต่ Element ไม่มี
JavaScript จะ Error
จึงควรดู NUI DevTools
㊿ Debug SendNUIMessage อย่างไร
ใส่
print(
'[NUI] sending open'
)
SendNUIMessage({
action = 'open'
})
Browser
window.addEventListener(
'message',
(event) => {
console.log(
'[NUI MESSAGE]',
event.data
);
}
);
ถ้า Lua Log ขึ้นแต่ Browser Console ไม่ขึ้น ให้ตรวจ NUI Page/Manifest/JS
51 Debug SetNuiFocus อย่างไร
ถ้า UI เปิดแต่คลิกไม่ได้ ตรวจ
SetNuiFocus(true, true)?
NUI Resource อยู่บน Focus Stack หรือไม่?
Resource อื่นแย่ง Focus หรือไม่?
CSS pointer-events?
Overlay?
z-index?
JavaScript?
อย่าสรุปว่าเป็น SetNuiFocus ทุกกรณี
52 NUI DevTools ใช้แก้อะไรได้
Cfx.re มี NUI Developer Tools ซึ่งเปิดผ่านคำสั่ง nui_devTools ใน F8 เมื่อ Developer Mode เปิด
ใช้ตรวจ
Console
DOM
CSS
JavaScript
Network
NUI callbacks
Errors
มีประโยชน์มากกว่าการเดา UI ปัญหาจาก Lua อย่างเดียว
53 SendNUIMessage กับ SendNuiMessage ต่างกันไหม
ใน Lua Convenience Wrapper ใช้
SendNUIMessage(...)
เอกสาร Fullscreen NUI ยังแสดง JavaScript Client Runtime ใช้รูปแบบที่ส่ง JSON String ผ่าน SendNuiMessage
ดังนั้น Syntax ขึ้นกับ Runtime Language
บทความนี้ใช้ Lua เป็นหลัก
54 Lua SendNUIMessage ต้อง json.encode เองไหม
ไม่ต้องสำหรับ Wrapper ปกติ
ใช้
SendNUIMessage({
action = 'open'
})
ได้เลย
Cfx.re ระบุว่า Lua Convenience Wrapper จะ Encode JSON String ให้
จึงไม่ต้องเขียน
SendNUIMessage(
json.encode({...})
)
แบบสุ่ม
55 SetNuiFocus เรียกจาก Server ได้ไหม
นี่เป็น Client-side NUI Native
การเปิด UI ของ Player จาก Server ควร
Server
↓
TriggerClientEvent
↓
Client
↓
SetNuiFocus
↓
SendNUIMessage
ตัวอย่าง Server
TriggerClientEvent(
'shop:open',
source,
shopData
)
Client
RegisterNetEvent(
'shop:open',
function(shopData)
SetNuiFocus(
true,
true
)
SendNUIMessage({
action = 'open',
shop = shopData
})
end
)
56 อย่าให้ Server เปิด UI ทุก Tick
UI State ควรเป็น Event-driven
เช่น
Player กด E
↓
Client Request
↓
Server Validate
↓
Client Open UI
ไม่ใช่ Server ส่ง Event เปิด Menu ซ้ำทุกวินาที
57 ตัวอย่าง Full Resource
โครงสร้าง
com_shop/
├── fxmanifest.lua
├── client.lua
└── html/
├── index.html
├── style.css
└── app.js
fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
author 'comsiam'
description 'FiveM NUI message 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 uiOpen =
false
local function setUi(
state,
data
)
uiOpen =
state
SetNuiFocus(
state,
state
)
SendNUIMessage({
action =
state
and 'open'
or 'close',
data = data
})
end
RegisterCommand(
'shopui',
function()
if uiOpen then
setUi(
false
)
return
end
setUi(
true,
{
title =
'FiveM Shop',
items = {
{
name = 'water',
label = 'Water',
price = 10
},
{
name = 'bread',
label = 'Bread',
price = 15
}
}
}
)
end,
false
)
RegisterNuiCallback(
'close',
function(data, cb)
setUi(
false
)
cb({
ok = true
})
end
)
AddEventHandler(
'onClientResourceStop',
function(resourceName)
if resourceName ~=
GetCurrentResourceName() then
return
end
SetNuiFocus(
false,
false
)
end
)
58 JavaScript สำหรับ Resource ตัวอย่าง
const menu =
document.getElementById(
'menu'
);
const title =
document.getElementById(
'title'
);
const items =
document.getElementById(
'items'
);
window.addEventListener(
'message',
(event) => {
const message =
event.data;
if (
message.action
=== 'open'
) {
title.textContent =
message.data?.title
?? 'Shop';
renderItems(
message.data?.items
?? []
);
menu.classList.add(
'active'
);
}
if (
message.action
=== 'close'
) {
menu.classList.remove(
'active'
);
}
}
);
function renderItems(
shopItems
) {
items.innerHTML =
'';
for (
const item
of shopItems
) {
const element =
document.createElement(
'div'
);
element.textContent =
`${item.label} - ${item.price}`;
items.appendChild(
element
);
}
}
59 Checklist SendNUIMessage และ SetNuiFocus
ตรวจ
① Resource มี ui_page หรือไม่?
② NUI files อยู่ใน Manifest หรือไม่?
③ SendNUIMessage เรียกจาก Client หรือไม่?
④ Message เป็น JSON-encodable หรือไม่?
⑤ Browser มี message listener หรือไม่?
⑥ action/type ชื่อตรงกันหรือไม่?
⑦ UI เปิดด้วย SetNuiFocus เมื่อจำเป็นหรือไม่?
⑧ HUD ที่ไม่ Interactive เปิด Focus เกินจำเป็นหรือไม่?
⑨ ปิด UI แล้ว SetNuiFocus(false,false) หรือไม่?
⑩ ESC ปิดผ่าน Client หรือไม่?
⑪ Resource Stop คืน Focus หรือไม่?
⑫ Resource Start Reset State หรือไม่?
⑬ UI หลายตัวแย่ง Focus หรือไม่?
⑭ ส่ง Secrets เข้า NUI หรือไม่?
⑮ Payload ใหญ่เกินไปหรือไม่?
⑯ ส่ง Full State ทั้งชุดทุกครั้งหรือไม่?
⑰ ใช้ Delta Update ได้หรือไม่?
⑱ SendNUIMessage ทุก Tick โดยไม่จำเป็นหรือไม่?
⑲ มี Cache ค่าเดิมก่อนส่งหรือไม่?
⑳ Browser Timers ยังทำงานตอน UI ปิดหรือไม่?
㉑ DOM Update หนักหรือไม่?
㉒ JavaScript Error หรือไม่?
㉓ CSS pointer-events ถูกหรือไม่?
㉔ Overlay/Z-index ขวาง Click หรือไม่?
㉕ NUI DevTools ตรวจแล้วหรือยัง?
㉖ Server Data ผ่าน Client ก่อน NUI หรือไม่?
㉗ Server ยังเป็น Authority หรือไม่?
㉘ Display Price ถูกนำกลับไปเชื่อโดย Server หรือไม่?
㉙ Restart Resource แล้ว Focus ปกติหรือไม่?
㉚ เปิด/ปิด UI ซ้ำหลายครั้งแล้วยังปกติหรือไม่?
⑥⓪ Architecture ที่แนะนำสำหรับ Production
Resource ใหญ่ควรแยก
Client
├── ui state
├── SendNUIMessage
├── SetNuiFocus
└── NUI callbacks
Browser
├── presentation
├── animations
├── local UI state
└── user input
Server
├── validation
├── permissions
├── business logic
└── database
ตัวอย่างเปิด Garage
Player ขอเปิด Garage
↓
Client
↓
Server ตรวจ Garage / Player
↓
Server ส่ง Vehicles
↓
Client
↓
SetNuiFocus(true,true)
↓
SendNUIMessage
↓
Browser Render Vehicles
Player เลือกรถ
Browser
↓
NUI Callback
↓
Client
↓
Server
↓
Validate Vehicle Ownership
↓
Server Create / Approve Vehicle
↓
Client
↓
SendNUIMessage Result
↓
Browser Update
นี่เป็น Flow ที่แบ่ง Client, Browser และ Server ได้ชัดเจน
SendNUIMessage ไม่ได้สร้าง UI ให้เอง
ถ้าคุณส่ง
SendNUIMessage({
action = 'open'
})
แต่ JavaScript ไม่มี
window.addEventListener(
'message',
...
);
ไม่มีอะไรเกิดขึ้น
SendNUIMessage เป็นเพียง Communication Channel
Browser Code ต้องเป็นผู้ตัดสินว่าจะทำอะไรกับ Message
SetNuiFocus ไม่ได้เปิด HTML ให้เอง
เช่นเดียวกัน
SetNuiFocus(
true,
true
)
ไม่ได้ทำให้ Menu Visible อัตโนมัติ
มันเพียงเปลี่ยน Input Focus
โดยทั่วไปจึงใช้คู่กัน
SetNuiFocus(
true,
true
)
SendNUIMessage({
action = 'open'
})
UI State ต้องมี Source of Truth ที่ชัดเจน
ตัวอย่างไม่ดี
Lua คิดว่า UI ปิด
JavaScript คิดว่า UI เปิด
Focus ยัง true
เกิด State Mismatch
ควรมี Function กลาง
local function setUi(
state
)
-- update state
-- update focus
-- notify NUI
end
เพื่อลดความไม่ตรงกัน
Browser State ไม่ควรแทน Server State
UI อาจแสดง
Cash = 100,000
แต่ผู้เล่นแก้ DOM เป็น
Cash = 999,999,999
ไม่ได้หมายความว่า Server Cash เปลี่ยน
และ Server ไม่ควรรับค่ากลับจาก Browser แล้วเชื่อ
Server ต้องถือข้อมูล Economy ของตัวเอง
Performance ของ NUI ต้องดูสองฝั่ง
อย่าดูแค่
resmon
เพราะ Client Script อาจเบาแต่ Browser ทำงานหนักจาก
DOM
CSS Blur
JavaScript
Animations
Video
Canvas
WebGL
ควรใช้ทั้ง FiveM Profiler/Resmon และ NUI DevTools ตามปัญหา
ตัวอย่างลด Message สำหรับ HUD
แบบไม่ดี
60 FPS
×
Health
×
Armor
×
Cash
×
Job
×
Food
×
Water
ส่งใหม่ทุก Frame
แบบที่ดีกว่าอาจแยก
Speed
→ update ถี่พอสำหรับ UI
Cash
→ update ตอนเปลี่ยน
Job
→ update ตอน Job เปลี่ยน
Food
→ update เมื่อค่าเปลี่ยน/ช่วงเวลาที่เหมาะ
ไม่จำเป็นต้องมี Frequency เดียวกันทุกข้อมูล
คำถามที่พบบ่อยเกี่ยวกับ SendNUIMessage และ SetNuiFocus
FiveM SendNUIMessage คืออะไร
ใช้ส่งข้อมูลจาก Client Script ไปยัง NUI Page ของ Resource
SendNUIMessage ทำงาน Server-side ไหม
ใช้กับ Client-side NUI Communication หาก Server ต้องส่งข้อมูลให้ UI ให้ส่ง Server→Client ก่อน
JavaScript รับข้อมูลอย่างไร
ใช้
window.addEventListener(
'message',
(event) => {
console.log(
event.data
);
}
);
SendNUIMessage ต้อง json.encode เองไหม
Lua Convenience Wrapper จัดการ JSON Encoding ให้
ส่ง Table ได้ไหม
ได้หาก Serialize เป็น JSON ได้
ส่ง Array ได้ไหม
ได้
ส่งข้อมูล Player ได้ไหม
ได้ แต่ส่งเฉพาะข้อมูลที่ Client/NUI ควรรู้
SetNuiFocus คืออะไร
ใช้กำหนด Keyboard Focus และ Mouse Cursor Focus ให้ NUI
เปิด Mouse Cursor อย่างไร
SetNuiFocus(
true,
true
)
ปิด Mouse Cursor อย่างไร
SetNuiFocus(
false,
false
)
HUD ต้อง SetNuiFocus ไหม
ไม่ หาก HUD มีไว้แสดงผลอย่างเดียว
Inventory ต้องใช้ Focus ไหม
โดยทั่วไปต้องใช้หาก Player ต้องคลิกหรือใช้ Keyboard กับ UI
UI ปิดแต่เมาส์ค้างแก้อย่างไร
คืน Focus ด้วย
SetNuiFocus(
false,
false
)
ปิด UI ด้วย JavaScript อย่างเดียวได้ไหม
ไม่ควรหาก NUI มี Focus เพราะต้องแจ้ง Client ให้คืน Focus ด้วย
Resource Restart แล้ว Mouse ค้างแก้อย่างไร
ทำ Cleanup ใน onClientResourceStop
SendNUIMessage ทุก Frame ได้ไหม
ทำได้ แต่ไม่ควรหากข้อมูลไม่ต้องการความถี่ระดับนั้น
SendNUIMessage ทำให้ FPS ตกได้ไหม
การส่ง Message จำนวนมากและ Browser Rendering หนักสามารถเพิ่ม Work ฝั่ง Client ได้ จึงควรส่งเท่าที่จำเป็น
Full State กับ Delta Update แบบไหนดีกว่า
ใช้ Full State ตอน Initialize และ Delta เมื่อมีการเปลี่ยนเล็ก ๆ มักเป็น Architecture ที่มีประสิทธิภาพกว่า
NUI หลาย Resource เปิดพร้อมกันได้ไหม
ได้ แต่ต้องระวัง Focus Stack และ UI ที่อยู่ด้านบน
Click ผ่าน NUI ด้านบนไป UI ด้านล่างได้ไหม
Cfx.re ระบุว่า Fullscreen NUI Resources ปัจจุบันไม่มี Click-through ข้าม Resources
SetNuiFocus เปิด UI หรือไม่
ไม่ มันกำหนด Input Focus เท่านั้น
SendNUIMessage เปิด Cursor หรือไม่
ไม่ ต้องใช้ SetNuiFocus
Server สามารถเชื่อข้อมูลจาก NUI ได้ไหม
ไม่ควร Client/NUI Input ต้อง Validate ฝั่ง Server เมื่อมีผลต่อ Gameplay, Economy หรือ Permissions
จะ Debug NUI ที่ไหน
ใช้ F8 และ NUI Developer Tools รวมถึง Browser Console/Network
nui_devTools คืออะไร
คำสั่งสำหรับเปิด NUI DevTools เมื่อ Developer Mode เปิดตาม Cfx.re Documentation
สรุป FiveM SendNUIMessage และ SetNuiFocus ใช้อย่างไร
สองคำสั่งนี้ทำหน้าที่ต่างกันชัดเจน
SendNUIMessage
→ ส่งข้อมูล Client → Browser
ส่วน
SetNuiFocus
→ กำหนด Keyboard/Mouse Focus ให้ Browser
เปิด Interactive UI
SetNuiFocus(
true,
true
)
SendNUIMessage({
action = 'open'
})
ปิด
SetNuiFocus(
false,
false
)
SendNUIMessage({
action = 'close'
})
Browser รับด้วย
window.addEventListener(
'message',
(event) => {
const data =
event.data;
if (
data.action
=== 'open'
) {
// open
}
}
);
หลักที่สำคัญคือ
UI ไม่ Interactive
→ ไม่ต้อง Focus
UI Interactive
→ Focus เมื่อเปิด
→ คืน Focus เมื่อปิด
สำหรับระบบที่ Update ข้อมูลบ่อย อย่ายิง Message ทุก Frame โดยอัตโนมัติ แต่ให้เลือก Frequency ตามข้อมูล หรือใช้ Event-driven Update
เช่น
Money
→ ส่งตอนเปลี่ยน
Job
→ ส่งตอนเปลี่ยน
Inventory
→ ส่งตอนเปิด/Item เปลี่ยน
Speed
→ ส่งเป็นช่วงที่เหมาะกับความลื่นของ UI
อีกเรื่องที่ต้องระวังคือ Fullscreen NUI มี Focus Stack ดังนั้นหาก Inventory, Phone และ Admin Menu เปิดพร้อมกันโดยแต่ละ Resource จัด Focus ของตัวเอง อาจทำให้ Input State สับสนได้
สำหรับผู้ที่พัฒนา FiveM กับ comsiam การออกแบบ NUI ที่ดีจึงต้องมี UI State กลาง ไม่กระจาย SetNuiFocus และ SendNUIMessage ไปทั่ว Resource จนไม่รู้ว่าใครเป็นคนเปิดหรือปิด Interface
หลักสำคัญจาก comsiam คือ ส่งเฉพาะข้อมูลที่เปลี่ยนและคืน Focus ทุกครั้งที่ UI ปิด สองอย่างนี้ช่วยป้องกันทั้ง NUI Message ที่มากเกินจำเป็นและปัญหา Mouse/Keyboard ค้างที่ผู้เล่นพบกันบ่อย
หัวข้อถัดไปคือ FiveM Profiler คืออะไร หา Script กิน CPU อย่างไร ซึ่งจะเริ่มเข้าสู่ Performance โดยอธิบาย Profiler, Resource Tick, Threads, Natives, Events, Database Work และวิธีหา Code ที่ทำให้ Server หรือ Client ใช้ CPU สูงแบบวัดจากข้อมูลจริง
Comments
Post a Comment