FiveM Event และ RegisterNetEvent คืออะไร

FiveM Event คือระบบสำหรับให้ Code ส่วนต่าง ๆ ส่งเหตุการณ์และสื่อสารกัน ไม่ว่าจะเป็น Resource เดียวกัน, Resource คนละตัว หรือการสื่อสารระหว่าง Client กับ Server

ตัวอย่างเช่น เมื่อผู้เล่นกดปุ่มทำงาน อาจเกิด Flow แบบนี้

ผู้เล่นกดเริ่มงาน
↓
Client
↓
TriggerServerEvent
↓
Server
↓
ตรวจ Job / ตำแหน่ง / เงื่อนไข
↓
บันทึกสถานะ
↓
TriggerClientEvent
↓
Client แสดง Notification

คำสั่งสำคัญที่ควรรู้คือ

AddEventHandler
RegisterNetEvent
TriggerEvent
TriggerServerEvent
TriggerClientEvent

สรุปให้จำง่ายก่อนว่า

TriggerEvent
= Event ภายใน Context เดียวกัน

RegisterNetEvent
= ลงทะเบียน Event ที่ต้องรับผ่าน Network ได้

TriggerServerEvent
= Client → Server

TriggerClientEvent
= Server → Client

สิ่งสำคัญที่สุดคือ Network Event ไม่ใช่ระบบรักษาความปลอดภัย หาก Client สามารถ Trigger Event ไปยัง Server ได้ Server ต้องตรวจข้อมูลเองเสมอ เช่น เงิน, Item, Job, Permission และตำแหน่งของผู้เล่น

① FiveM Event คืออะไร

Event คือกลไกที่ทำให้ Script สามารถบอกว่า

“มีเหตุการณ์บางอย่างเกิดขึ้นแล้ว ให้ Code ที่รอฟังเหตุการณ์นี้ทำงาน”

ตัวอย่าง

AddEventHandler('myScript:hello', function()
    print('Hello FiveM')
end)

จากนั้น Trigger

TriggerEvent('myScript:hello')

ผลคือ Handler ที่รอ Event ชื่อ

myScript:hello

จะทำงาน

แนวคิดคือ

Trigger
↓
Event Name
↓
Handler
↓
Execute Code

② Event ช่วยอะไรในการเขียน FiveM Script

ถ้าไม่มี Event Resource ต่าง ๆ จะเชื่อมต่อกันได้ยากขึ้น

ตัวอย่างระบบ Job อาจประกอบด้วย

Client Interaction
↓
Server Validation
↓
Inventory
↓
Database
↓
Notification

Event ช่วยเชื่อมขั้นตอนเหล่านี้โดยไม่ต้องให้ Code ทุกส่วนอยู่ในไฟล์เดียวกัน

จึงสามารถออกแบบ Script แบบแยกหน้าที่ เช่น

client.lua
server.lua
inventory integration
framework integration

แล้วให้แต่ละส่วนสื่อสารกันผ่าน Event หรือ API ที่เหมาะสม

③ Local Event คืออะไร

Local Event หมายถึง Event ที่ทำงานภายใน Context เดียวกัน

ตัวอย่าง

Client → Client

หรือ

Server → Server

สำหรับ Lua สามารถใช้

AddEventHandler('myScript:test', function()
    print('Event received')
end)

TriggerEvent('myScript:test')

หาก Code นี้อยู่ฝั่ง Server ก็เป็น Server-local Event

หากอยู่ฝั่ง Client ก็เป็น Client-local Event

Cfx.re แนะนำให้ใช้ AddEventHandler สำหรับ Event ที่ตั้งใจใช้งานภายใน Context เดียว เพื่อไม่เปิด Event นั้นเข้าสู่ Network โดยไม่จำเป็น.

④ TriggerEvent คืออะไร

TriggerEvent ใช้ Trigger Event ภายใน Context ปัจจุบัน

รูปแบบ

TriggerEvent('eventName')

ส่ง Parameter ได้ เช่น

TriggerEvent('myScript:message', 'Hello')

Handler

AddEventHandler('myScript:message', function(message)
    print(message)
end)

ถ้าทำใน Client

Client Resource A
↓
TriggerEvent
↓
Client Event

ถ้าทำใน Server

Server Resource A
↓
TriggerEvent
↓
Server Event

TriggerEvent ไม่ใช่คำสั่ง Client → Server โดยตรง.

⑤ RegisterNetEvent คืออะไร

RegisterNetEvent ใช้ลงทะเบียน Event ให้สามารถใช้งานใน Network Context ได้ เช่น

Client → Server

หรือ

Server → Client

ตัวอย่าง

RegisterNetEvent('myScript:hello', function(message)
    print(message)
end)

อีกรูปแบบหนึ่งคือ

RegisterNetEvent('myScript:hello')

AddEventHandler('myScript:hello', function(message)
    print(message)
end)

สำหรับ Lua รูปแบบแรกสั้นและอ่านง่ายกว่า

เอกสาร Cfx.re อธิบายว่า RegisterNetEvent ใช้เมื่อ Event จำเป็นต้องถูก Trigger ข้าม Context ผ่าน Network และสามารถใส่ Handler Function ในคำสั่งเดียวได้.

⑥ RegisterNetEvent กับ AddEventHandler ต่างกันอย่างไร

จำง่าย ๆ ว่า

AddEventHandler

เหมาะกับ Event ภายใน Context เดียว

AddEventHandler('myScript:localEvent', function()
    print('Local event')
end)

RegisterNetEvent

ใช้เมื่อ Event ต้องรับผ่าน Network

RegisterNetEvent('myScript:networkEvent', function()
    print('Network event')
end)

ดังนั้นไม่ควรเปลี่ยน Event ทุกตัวให้เป็น RegisterNetEvent โดยไม่มีเหตุผล

ถ้า Event ใช้เฉพาะ

Server → Server

ให้ใช้ Local Event จะจำกัดพื้นผิวการโจมตีได้ดีกว่า

⑦ RegisterNetEvent ไม่ได้หมายความว่าปลอดภัย

นี่เป็นเรื่องที่สำคัญมาก

การเขียน

RegisterNetEvent('job:reward', function()
    -- code
end)

ไม่ได้หมายความว่าเฉพาะ Client Script ของเราที่สามารถเรียก Event นี้ได้

Cfx.re เตือนว่า Client ที่ถูกดัดแปลงสามารถพยายาม Trigger Network Events ได้ ดังนั้น Server-side Event ต้องมี Validation ของตัวเอง.

หลักควรเป็น

Client ส่ง Request
↓
Server ไม่เชื่อทันที
↓
Server ตรวจข้อมูล
↓
ผ่าน
↓
ทำ Action

⑧ TriggerServerEvent คืออะไร

TriggerServerEvent ใช้จาก Client เพื่อ Trigger Event ฝั่ง Server

ตัวอย่าง Client

TriggerServerEvent('myJob:start')

Server

RegisterNetEvent('myJob:start', function()
    print('Player started the job')
end)

Flow คือ

Client
↓
TriggerServerEvent
↓
Network
↓
Server
↓
RegisterNetEvent Handler

นี่เป็นรูปแบบพื้นฐานของ Client → Server communication ใน FiveM.

⑨ TriggerServerEvent ส่งข้อมูลได้ไหม

ได้

ตัวอย่าง Client

TriggerServerEvent('myScript:selectJob', 'police')

Server

RegisterNetEvent('myScript:selectJob', function(jobName)
    print(jobName)
end)

แต่ต้องจำว่า

ข้อมูลจาก Client
=
Untrusted Input

ดังนั้นไม่ควรนำ jobName ไปเปลี่ยน Job ของผู้เล่นทันทีโดยไม่มี Validation

⑩ ตัวอย่าง Event Client → Server แบบไม่ปลอดภัย

ตัวอย่างที่ไม่ควรออกแบบ

Client

TriggerServerEvent('job:reward', 100000)

Server

RegisterNetEvent('job:reward', function(amount)
    AddMoney(source, amount)
end)

ปัญหาคือ Client เป็นคนส่ง

amount

Server เชื่อทันที

ผู้โจมตีอาจพยายามส่ง

99999999

แทนค่าปกติ

นี่คือ Error ด้าน Architecture ไม่ใช่ Error ของ FiveM

⑪ ตัวอย่าง Reward Event ที่ดีกว่า

Client ส่งเพียง

TriggerServerEvent('job:complete')

Server

RegisterNetEvent('job:complete', function()
    local src = source

    -- ตรวจ Job
    -- ตรวจสถานะงาน
    -- ตรวจตำแหน่ง
    -- ตรวจ Cooldown

    local reward = 500

    -- Server เป็นผู้กำหนด reward
    -- AddMoney(src, reward)
end)

แนวคิดคือ

Client
→ ขอ Complete

Server
→ ตรวจว่า Complete จริงหรือไม่
→ คำนวณ Reward เอง
→ จ่ายเงิน

Cfx.re แนะนำโดยตรงให้ตรวจข้อมูล Server-side เช่น เงิน, Inventory, Player State, Position, Level และ Permissions แทนการเชื่อค่าที่ Client ส่งมา.

⑫ source ใน Server Event คืออะไร

เมื่อ Client Trigger Server Event

TriggerServerEvent('myScript:test')

ใน Server Handler สามารถใช้

source

เพื่ออ้างถึงผู้เล่นที่ Trigger Event

ตัวอย่าง

RegisterNetEvent('myScript:test', function()
    local src = source

    print(('Player %s triggered event'):format(src))
end)

ในระบบจริงมักนำ src ไปใช้ดึง

Player Object
Identifier
Job
Inventory
Permissions

จาก Framework หรือ Server API

⑬ อย่าให้ Client ส่ง source เองโดยไม่จำเป็น

ตัวอย่างที่ไม่ดี

Client

TriggerServerEvent('job:reward', GetPlayerServerId(PlayerId()))

Server

RegisterNetEvent('job:reward', function(playerId)
    GiveMoney(playerId)
end)

กรณีทั่วไปไม่จำเป็น เพราะ Server มี

source

ของผู้ที่ Trigger Event อยู่แล้ว

แนวคิดที่ดีกว่าคือ

RegisterNetEvent('job:reward', function()
    local src = source
end)

ช่วยลดข้อมูลที่ Client สามารถปลอมแปลงได้

⑭ TriggerClientEvent คืออะไร

TriggerClientEvent ใช้จาก Server เพื่อ Trigger Event ไปยัง Client

รูปแบบ

TriggerClientEvent('eventName', playerId)

ตัวอย่าง

Server

TriggerClientEvent('myScript:notify', source, 'งานเสร็จแล้ว')

Client

RegisterNetEvent('myScript:notify', function(message)
    print(message)
end)

Flow คือ

Server
↓
TriggerClientEvent
↓
Network
↓
Client
↓
RegisterNetEvent

TriggerClientEvent ต้องระบุ Player ID เป้าหมาย และสามารถใช้ -1 เพื่อส่งไปยัง Clients ที่เชื่อมต่อทั้งหมดได้.

⑮ ส่ง Client Event ให้ผู้เล่นคนเดียว

ตัวอย่าง

local src = source

TriggerClientEvent('myScript:notify', src, 'Hello')

เฉพาะ Client ของ Player นั้นจะได้รับ Event

เหมาะกับ

  • Notification

  • UI

  • Player-specific Data

  • Animation

  • Menu

  • Personal State

ไม่ควร Broadcast ทุกอย่างไปทุกคนหากไม่มีความจำเป็น

⑯ ส่ง Event ให้ผู้เล่นทุกคน

ใช้

TriggerClientEvent('myScript:announcement', -1, 'Server restart soon')

Client

RegisterNetEvent('myScript:announcement', function(message)
    print(message)
end)

-1 หมายถึงส่งไปยัง Clients ทั้งหมดที่เชื่อมต่ออยู่ในบริบทนี้.

แต่ไม่ควรใช้ Broadcast สำหรับข้อมูลส่วนตัว เช่น

Bank Balance
Private Inventory
Secret Data
Player-specific Information

เพราะไม่มีเหตุผลต้องส่งให้ทุกคน

⑰ TriggerClientEvent ส่ง Parameter หลายตัวได้ไหม

ได้

Server

TriggerClientEvent(
    'myScript:updateJob',
    source,
    'police',
    3,
    true
)

Client

RegisterNetEvent('myScript:updateJob', function(job, grade, onDuty)
    print(job, grade, onDuty)
end)

แต่ไม่ควรส่งข้อมูลจำนวนมากเกินความจำเป็น

FiveM ใช้ Serialization สำหรับข้อมูล Event และ Cfx.re แนะนำให้ส่งข้อมูลให้เล็กและเรียบง่ายเท่าที่จำเป็น.

⑱ Event สามารถส่ง Table ได้ไหม

สามารถส่งข้อมูลที่ Runtime รองรับ เช่น Table

ตัวอย่าง

TriggerClientEvent('myScript:data', source, {
    job = 'police',
    grade = 3
})

Client

RegisterNetEvent('myScript:data', function(data)
    print(data.job)
    print(data.grade)
end)

แต่ไม่ควรสร้าง Table ใหญ่มากแล้วส่งซ้ำถี่ ๆ หากไม่จำเป็น

โดยเฉพาะข้อมูลหลาย KB ขึ้นไป ควรพิจารณากลไก Latent Event หรือ Architecture อื่นตามประเภทข้อมูล

⑲ TriggerLatentServerEvent คืออะไร

FiveM มี Latent Event สำหรับข้อมูลขนาดใหญ่

Client → Server ใช้

TriggerLatentServerEvent(
    'myScript:largeData',
    bps,
    data
)

Cfx.re อธิบายว่า Latent Event เหมาะกับ Transfer ขนาดใหญ่ เพราะช่วยจำกัดอัตราการส่งข้อมูลและหลีกเลี่ยงการ Block Network Channel แบบการส่งชุดข้อมูลใหญ่ใน Event ปกติ.

สำหรับ Event ทั่วไปขนาดเล็ก ไม่จำเป็นต้องใช้ Latent Event

⑳ TriggerLatentClientEvent คืออะไร

แนวคิดเดียวกันแต่เป็น

Server → Client

ใช้

TriggerLatentClientEvent(...)

เหมาะเมื่อ Server ต้องส่งข้อมูลขนาดใหญ่ไปยัง Client

ไม่ควรใช้ Latent Event เพียงเพราะคิดว่า “ใหม่กว่า” หรือ “เร็วกว่า”

ให้เลือกตามขนาดและลักษณะข้อมูล

㉑ Event Name ควรตั้งอย่างไร

ชื่อควรอ่านแล้วรู้ว่าเป็น Resource ไหนและทำอะไร

ตัวอย่างดี

mygarage:server:storeVehicle
mygarage:client:openMenu
police:server:setDuty

หลีกเลี่ยงชื่อกว้าง เช่น

event1
test
doThing
give

เพราะเมื่อ Server มี Script หลายร้อย Resource จะ Debug ยากมาก

รูปแบบหนึ่งที่นิยมใช้คือ

resource:side:action

เช่น

myjob:server:startJob

㉒ Event Name ต้องไม่ซ้ำหรือไม่

Event System ใช้ String เป็นชื่อ Identifier

ถ้า Resource หลายตัวใช้ชื่อกว้างเหมือนกัน เช่น

update

อาจสร้างความสับสนและ Collision เชิง Logic ได้

จึงควร Namespace ด้วยชื่อ Resource

เช่น

com_job:update

แทน

update

ช่วยให้ค้นหา Code และ Error ได้ง่ายกว่า

㉓ RegisterNetEvent แบบสั้นกับแบบแยกต่างกันอย่างไร

แบบสั้น

RegisterNetEvent('myScript:test', function(value)
    print(value)
end)

แบบแยก

RegisterNetEvent('myScript:test')

AddEventHandler('myScript:test', function(value)
    print(value)
end)

ทั้งสองรูปแบบใช้ได้

สำหรับ Lua Code ใหม่ รูปแบบแรกมักอ่านง่ายกว่าเมื่อ Event มี Handler เดียว

Cfx.re ระบุว่า RegisterNetEvent สามารถรับ Callback Function เพื่อ Register และเพิ่ม Handler ในขั้นตอนเดียวได้.

㉔ AddEventHandler ใช้กับ RegisterNetEvent ได้ไหม

ได้

ตัวอย่าง

RegisterNetEvent('myScript:test')

AddEventHandler('myScript:test', function()
    print('received')
end)

แต่ถ้า Event ไม่จำเป็นต้องรับจาก Network ตั้งแต่แรก ก็ไม่ควร Register เป็น Net Event

ตัวอย่าง Server-only

AddEventHandler('myScript:internalCleanup', function()
    -- server local
end)

จากนั้น

TriggerEvent('myScript:internalCleanup')

เพียงพอ

㉕ ทำไม Event ภายใน Server ไม่ควรใช้ RegisterNetEvent ทุกตัว

เพราะ RegisterNetEvent ทำให้ Event ถูก Register สำหรับ Network Context

ถ้า Event มีหน้าที่ภายใน Server เช่น

Internal Database Update
Internal Cache Refresh
Internal Reward Calculation

และไม่มีเหตุผลให้ Client เรียกได้ ให้ใช้

AddEventHandler(...)

แทน

Cfx.re ระบุว่านี่เป็นแนวทางสำคัญในการลดการเปิด Server Events สู่ Client โดยไม่จำเป็น.

㉖ ตัวอย่าง Server-only Event

AddEventHandler('myScript:internalSave', function(playerId)
    print(('Saving player %s'):format(playerId))
end)

เรียกจาก Server

TriggerEvent('myScript:internalSave', source)

เพราะ Event นี้ไม่มีเหตุผลต้องเรียกจาก Client จึงไม่ต้องใช้

RegisterNetEvent

การเลือก Event Type ให้ถูกเป็นส่วนหนึ่งของ Security Architecture

㉗ Event ที่ให้ Item ต้องตรวจอะไร

สมมติ

RegisterNetEvent('job:collectItem', function()

Server ควรตรวจข้อมูล เช่น

ผู้เล่นกำลังทำ Job จริงหรือไม่
อยู่ตำแหน่งที่ถูกต้องหรือไม่
Cooldown ผ่านหรือไม่
จำนวน Item เกิน Limit หรือไม่
Inventory รองรับหรือไม่

จากนั้น Server เป็นผู้กำหนด

Item Name
Item Count

ไม่ควรให้ Client ส่ง

item = weapon
count = 9999

แล้ว Server เชื่อทันที

㉘ Event ซื้อของต้องตรวจอะไร

Client

TriggerServerEvent('shop:buy', 'water')

Server ควรทำ

ตรวจ Item มีขายจริง
↓
หาราคาจาก Server Config
↓
ตรวจเงิน
↓
หักเงิน
↓
เพิ่ม Item

อย่าออกแบบเป็น

TriggerServerEvent('shop:buy', 'water', 1)

แล้ว Server เชื่อว่าราคา 1 เป็นราคาจริง

ราคา Authority ต้องอยู่ Server

㉙ Event Job Reward ต้องตรวจตำแหน่งไหม

ถ้า Gameplay กำหนดว่าผู้เล่นต้องอยู่จุดส่งของ Server สามารถตรวจ Player Position ตามความเหมาะสมก่อน Reward

Flow

Client แจ้งส่งงาน
↓
Server ตรวจ Player State
↓
Server ตรวจตำแหน่ง
↓
Server ตรวจ Job
↓
Server ตรวจจำนวนงานที่สะสม
↓
Reward

นี่ตรงกับแนวทาง Cfx.re ที่แนะนำให้ใช้ Server-side Data เช่น Position, Inventory และ State ตรวจ Event สำคัญ.

㉚ Event Admin ต้องตรวจ Permission ฝั่ง Server

การซ่อน Admin Menu ฝั่ง Clientไม่เพียงพอ

สมมติ Client แสดงปุ่มเฉพาะ Admin

Admin
→ เห็นปุ่ม Give Money

แต่ Server Event

RegisterNetEvent('admin:giveMoney', function(...)

ต้องตรวจ Permission ซ้ำ

เพราะผู้โจมตีอาจพยายาม Trigger Event โดยตรงโดยไม่เปิด Menu

หลักคือ

Client UI Check
= UX

Server Permission Check
= Security

㉛ Client Validation มีประโยชน์ไหม

มี

Client สามารถตรวจ

อยู่ใน Zone หรือไม่
กดปุ่มได้หรือไม่
Job ที่แสดงใน UI
Cooldown สำหรับ UX

เพื่อลด Request ที่ไม่จำเป็น

แต่ Client Validation ไม่ควรเป็น Check สุดท้ายของระบบสำคัญ

Cfx.re ระบุชัดว่า Client Checks สามารถถูก Override ได้ และค่าที่สำคัญควรถูกตรวจด้วย Server-side Methods.

㉜ TriggerServerEvent จำนวนมากทำ Server หนักไหม

Event ทุกครั้งมีต้นทุนด้าน Network และ Serialization

Script ที่ Trigger Event ทุก Frame โดยไม่มีเหตุผลสามารถสร้าง Traffic และภาระเพิ่มขึ้นได้

ตัวอย่างที่ไม่ควรทำ

CreateThread(function()
    while true do
        TriggerServerEvent('player:update')
        Wait(0)
    end
end)

ควรออกแบบให้ Trigger เมื่อ

  • State เปลี่ยน

  • Player ทำ Action

  • Interval ที่เหมาะสม

  • Server จำเป็นต้องรู้จริง

ไม่ใช่ส่งทุก Frame

㉝ ไม่ควรส่งข้อมูลที่ Server หาเองได้

สมมติ Server สามารถหา Job จาก Player Object ได้

ไม่จำเป็นให้ Client ส่ง

TriggerServerEvent(
    'job:reward',
    'police',
    4,
    1000,
    playerCoords
)

Server ควรใช้

source
↓
Player Object
↓
Job
Grade
Position
Server State

ตามข้อมูลที่ Server สามารถตรวจสอบเองได้

ยิ่ง Client ควบคุมข้อมูลสำคัญน้อยเท่าไร การ Validate ก็ง่ายขึ้น

㉞ Event Loop คืออะไร

ต้องระวัง Event ที่ Trigger กลับกันไม่รู้จบ

ตัวอย่าง

Event A
↓
Trigger Event B
↓
Event B
↓
Trigger Event A
↓
วนซ้ำ

อาจทำให้ Script ใช้ CPU สูงหรือเกิดปัญหารุนแรง

จึงต้องเข้าใจ Flow ก่อนเชื่อม Event หลายตัวเข้าด้วยกัน

การตั้งชื่อ Event ให้ชัดจะช่วย Debug Loop ได้ง่ายขึ้น

㉟ Event Error ดูตรงไหน

Client Event Error

ดู

F8

เช่น Error ใน

client/main.lua

Server Event Error

ดู Server Console หรือ txAdmin Console

เช่น

server/main.lua

หาก Event “กดแล้วไม่มีอะไรเกิดขึ้น” ให้เปิดทั้ง F8 และ Server Console แล้ว Trigger Action อีกครั้ง

จะช่วยเห็นว่า Flow หยุดอยู่ตรง Client หรือ Server

㊱ วิธี Debug Event แบบง่าย

สมมติ Client

print('Sending event')

TriggerServerEvent('job:start')

Server

RegisterNetEvent('job:start', function()
    print('Received job:start')
end)

ถ้า F8 เห็น

Sending event

แต่ Server Console ไม่เห็น

Received job:start

ให้ตรวจ

  • Event Name

  • Server Script ถูกโหลดหรือไม่

  • RegisterNetEvent มีจริงหรือไม่

  • Resource Error หรือไม่

การเพิ่ม print() ชั่วคราวช่วยตาม Flow ได้เร็วมาก

㊲ Event Name ไม่ตรงจะเกิดอะไร

Client

TriggerServerEvent('job:startJob')

Server

RegisterNetEvent('job:startjob', function()

ชื่อไม่ตรงกัน

จึงไม่มี Handler ที่ตรงกับ Event ที่ Client Trigger

ควร Copy Event Name ให้ตรงทุกตัว

รูปแบบ Namespace ชัด ๆ ช่วยลด Error เช่น

myjob:server:startJob

㊳ Resource Restart แล้ว Event ไม่ทำงาน

ให้ตรวจว่า

  • Client File โหลดใหม่หรือไม่

  • Server File โหลดใหม่หรือไม่

  • Event Registration ถูก Execute หรือไม่

  • Dependency พร้อมหรือไม่

  • Player State ถูก Initialize ใหม่หรือไม่

บาง Script ผูก Event Registration ไว้หลัง Condition ที่ไม่ถูก Execute หลัง Hot Restart

ถ้า Restart Server แล้วได้ แต่ Restart Resource แล้วไม่ได้ อาจต้องตรวจ Lifecycle ของ Resource

㊴ RegisterNetEvent ซ้ำหลายไฟล์ได้ไหม

สามารถมี Handler มากกว่าหนึ่งตัวสำหรับ Event ตาม Architecture ได้ แต่ต้องระวังไม่ให้ Action ถูกทำซ้ำโดยไม่ได้ตั้งใจ

ตัวอย่าง

Event: player:reward
↓
Handler A ให้เงิน
Handler B ให้เงินอีกครั้ง

อาจทำให้ Reward ซ้ำ

ควร Search Event Name ทั้ง Project เมื่อพบพฤติกรรมที่ถูก Execute หลายครั้ง

㊵ Event กับ Export เลือกอะไรดี

ใช้ Export เมื่อ

Resource A
ต้องการเรียก Function จาก Resource B
และต้องการ Return ค่าโดยตรง

ใช้ Event เมื่อ

เกิดเหตุการณ์บางอย่าง
แล้วต้องการแจ้ง Handler ให้ทำงาน

ตัวอย่าง Export

local count = exports['inventory']:GetItemCount('water')

ตัวอย่าง Event

TriggerEvent('myScript:playerReady')

สำหรับ Client ↔ Server ให้ใช้ Network Event หรือ Callback ตามรูปแบบการสื่อสารที่ต้องการ

㊶ Event กับ Callback ต่างกันอย่างไร

Event มักเป็นลักษณะ

ส่งข้อความ/เหตุการณ์
→ Handler ทำงาน

Callback มักเป็น

Request
↓
รอ Response
↓
ได้รับข้อมูล

ตัวอย่าง

Client ขอจำนวนเงิน
↓
Server Query
↓
ส่งจำนวนเงินกลับ

อาจเหมาะกับ Callback มากกว่า Trigger Event ไปแล้วสร้าง Event อีกตัวเพื่อส่งคำตอบกลับ

รายละเอียด Callback จะอยู่ในหัวข้อถัดไปของชุดนี้

㊷ RegisterNetEvent ใช้กับ Client ได้ไหม

ได้

ตัวอย่าง Server ส่ง

TriggerClientEvent('myScript:openMenu', source)

Client รับ

RegisterNetEvent('myScript:openMenu', function()
    -- เปิด Menu
end)

ดังนั้น RegisterNetEvent ไม่ได้หมายถึง “Server Event” เท่านั้น

ใช้ได้ทั้ง Client และ Server เมื่อ Handler ต้องรับ Network Event.

㊸ RegisterNetEvent ใช้กับ Server ได้ไหม

ได้เช่นกัน

Client

TriggerServerEvent('myScript:buyItem', 'water')

Server

RegisterNetEvent('myScript:buyItem', function(item)
    local src = source

    -- validate
end)

นี่เป็นหนึ่งในรูปแบบที่ใช้บ่อยที่สุดใน FiveM Lua

㊹ TriggerEvent ใช้ข้าม Resource ได้ไหม

ภายใน Context เดียวกัน TriggerEvent สามารถใช้สื่อสารกับ Handler ที่ Register อยู่ใน Context นั้น รวมถึง Resource อื่นได้

ตัวอย่าง Server Resource A

TriggerEvent('sharedSystem:ready')

Server Resource B

AddEventHandler('sharedSystem:ready', function()
    print('Ready event received')
end)

แต่ถ้าต้องการ Server → Client หรือ Client → Server ต้องใช้ Network Event ที่เหมาะสม

㊺ อย่า Register ทุก Event เป็น Net Event

วิธีคิดที่ดีคือถามว่า

Event นี้จำเป็นต้องข้าม Client/Server จริงหรือไม่?

ถ้าไม่จำเป็น ใช้

AddEventHandler
+
TriggerEvent

ถ้าจำเป็นต้องข้าม Network จึงใช้

RegisterNetEvent

ร่วมกับ

TriggerServerEvent
หรือ
TriggerClientEvent

นี่ช่วยให้ Architecture อ่านง่ายและลด Network-facing Events โดยไม่จำเป็น

㊻ ตัวอย่าง Job Event ที่ปลอดภัยกว่า

Client

RegisterCommand('finishjob', function()
    TriggerServerEvent('myjob:server:finish')
end)

Server

RegisterNetEvent('myjob:server:finish', function()
    local src = source

    -- ตรวจ Player
    -- ตรวจว่าเริ่มงานจริง
    -- ตรวจ Job
    -- ตรวจ Position
    -- ตรวจ Cooldown
    -- ตรวจจำนวนงานที่ Server เก็บไว้

    local reward = 500

    -- จ่าย reward จากค่าที่ Server ควบคุม
end)

จุดสำคัญคือ Client ไม่ส่ง

reward
item count
job grade
permission

ถ้า Server สามารถหาและตรวจข้อมูลเหล่านี้เองได้

㊼ ตัวอย่าง Server แจ้ง Client

หลังทำรายการสำเร็จ

TriggerClientEvent(
    'myjob:client:completed',
    src,
    500
)

Client

RegisterNetEvent('myjob:client:completed', function(reward)
    print(('You received %s'):format(reward))
end)

ในกรณีนี้ Server เป็นคนกำหนด Reward ก่อน แล้วส่งไป Client เพื่อแสดงผล

Client จึงเป็น

Presentation

ส่วน Server เป็น

Authority

㊽ Checklist FiveM Event

Local Event

ใช้

AddEventHandler
TriggerEvent

เมื่อไม่ต้องข้าม Client/Server

Client → Server

ใช้

TriggerServerEvent
RegisterNetEvent

Server → Client

ใช้

TriggerClientEvent
RegisterNetEvent

Security

Server ต้องตรวจ

  • Player

  • Job

  • Grade

  • Permission

  • Money

  • Item

  • Position

  • State

  • Cooldown

  • Quantity

  • Reward

Performance

  • ไม่ Trigger ทุก Frame

  • ส่งข้อมูลเท่าที่จำเป็น

  • หลีกเลี่ยง Payload ใหญ่

  • ใช้ Latent Events เมื่อเหมาะกับข้อมูลขนาดใหญ่

Debug

  • ตรวจ Event Name

  • ตรวจ Client F8

  • ตรวจ Server Console

  • ใช้ print() ตาม Flow

  • ตรวจ Resource/Dependency

  • ตรวจ Registration

สรุป FiveM Event และ RegisterNetEvent คืออะไร

FiveM Event คือระบบที่ช่วยให้ Code และ Resource ต่าง ๆ สื่อสารกันผ่านเหตุการณ์

จำคำสั่งหลักได้ดังนี้

TriggerEvent
= Trigger Event ภายใน Context เดียวกัน

AddEventHandler
= รับ Event ภายใน Context

RegisterNetEvent
= Register Event ที่ต้องรับผ่าน Network

TriggerServerEvent
= Client → Server

TriggerClientEvent
= Server → Client

ถ้า Event ใช้เฉพาะ Server → Server หรือ Client → Client และไม่จำเป็นต้องเปิดผ่าน Network ควรใช้ Local Event แทนการใช้ RegisterNetEvent ทุกอย่าง.

ส่วน Network Event ต้องถือว่า Client สามารถส่งข้อมูลที่ไม่น่าเชื่อถือเข้ามาได้ ดังนั้น Server ต้อง Validate ข้อมูลสำคัญด้วยตัวเองเสมอ ไม่ว่าจะเป็นเงิน, Item, Job, Permission, Position หรือ Reward.

แนวทางของ comsiam คือให้ Client ส่งเพียงข้อมูลขั้นต่ำที่ Server จำเป็นต้องรับ แล้วให้ Server ดึงและตรวจข้อมูลสำคัญจาก Server-side State เอง วิธีนี้ช่วยลดช่องโหว่และทำให้ Event API เข้าใจง่ายขึ้น

สำหรับ Server RP ขนาดใหญ่ comsiam แนะนำให้ตั้งชื่อ Event แบบมี Namespace เช่น resource:server:action และแยก Local Events ออกจาก Network Events ให้ชัดตั้งแต่ต้น เพราะเมื่อมี Scripts หลายร้อยตัว การตามหา Event Error และตรวจ Security จะง่ายกว่าการใช้ชื่อ Event สั้น ๆ ซ้ำกันทั่ว Server

Comments

Popular posts from this blog

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

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

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