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

Popular posts from this blog

FiveM ยังน่าเล่นไหม? Enhanced เปลี่ยน FiveM แค่ไหน

FiveM คืออะไร เล่นอย่างไร สำหรับมือใหม่ เริ่มต้นตั้งแต่ศูนย์

วิธีตั้ง Admin Permission ด้วย add_ace และ add_principal FiveM แบบละเอียด