FiveM client_script, server_script และ shared_script ต่างกันอย่างไร
client_script, server_script และ shared_script ใน FiveM คือ Directive ภายใน fxmanifest.lua ที่ใช้กำหนดว่าไฟล์ Script แต่ละไฟล์จะถูกโหลดและทำงาน ฝั่งผู้เล่น, ฝั่ง Server หรือทั้งสองฝั่ง
สรุปให้เข้าใจก่อนคือ
client_script
= ทำงานบนเครื่องผู้เล่น
server_script
= ทำงานบน FXServer
shared_script
= โหลดทั้ง Client และ Server
ตัวอย่าง
fx_version 'cerulean'
game 'gta5'
shared_script 'config.lua'
client_script 'client.lua'
server_script 'server.lua'
จากตัวอย่างนี้
config.lua
→ Client + Server
client.lua
→ Client เท่านั้น
server.lua
→ Server เท่านั้น
การแบ่งให้ถูกสำคัญมาก เพราะนอกจากเกี่ยวข้องกับการทำงานของ Script แล้ว ยังเกี่ยวข้องกับ Security, Performance และการป้องกันการโกง
โดยเฉพาะข้อมูลลับ เช่น Password, Secret Key หรือ Logic สำคัญ ไม่ควรใส่ใน Client หรือ Shared File เพียงเพราะต้องการเขียน Script ให้ง่าย
① client_script คืออะไร
client_script หมายถึงไฟล์ Script ที่ถูกโหลดไปทำงาน ฝั่ง Client หรือเครื่องของผู้เล่น
ตัวอย่าง
client_script 'client.lua'
โครงสร้าง Resource อาจเป็น
my-script/
├── fxmanifest.lua
├── client.lua
└── server.lua
โดยทั่วไป Client Script เหมาะกับงานที่เกี่ยวข้องกับสิ่งที่เกิดขึ้นบนเกมของผู้เล่น เช่น
Marker
Blip
NPC/Ped
Vehicle
Animation
Camera
Controls
Keybind
Target
Menu
NUI
Notification
Gameplay Interaction
ตัวอย่างเชิงแนวคิด
CreateThread(function()
print('Client script loaded')
end)
เมื่อ Resource ถูกโหลด Code นี้จะทำงานใน Client Runtime ของผู้เล่น
② client_scripts ใช้หลายไฟล์อย่างไร
ถ้ามี Client File หลายไฟล์ สามารถใช้
client_scripts {
'client/main.lua',
'client/menu.lua',
'client/target.lua'
}
หรือใช้ Globbing
client_scripts {
'client/*.lua'
}
ตัวอย่างโครงสร้าง
client/
├── main.lua
├── menu.lua
├── target.lua
└── vehicle.lua
จากนั้นใช้
client_scripts {
'client/*.lua'
}
ได้
อย่างไรก็ตาม หากไฟล์มี Dependency ต่อกันและต้องโหลดตามลำดับ ควรระบุชื่อไฟล์ให้ชัดเจนแทนการใช้ Wildcard แบบกว้างเกินไป
③ server_script คืออะไร
server_script คือ Script ที่ถูกโหลดและทำงาน เฉพาะบน Server
ตัวอย่าง
server_script 'server.lua'
Server Script เหมาะสำหรับงาน เช่น
Database
เงินผู้เล่น
Item
Job
Permission
Server Events
Server Callbacks
Logging
Validation
Player Data
Business Logic
Anti-cheat Validation
ตัวอย่าง
print('Server script loaded')
ข้อความนี้จะปรากฏที่ Server Console ไม่ใช่ F8 ของผู้เล่น
④ server_scripts ใช้หลายไฟล์อย่างไร
ตัวอย่าง
server_scripts {
'server/database.lua',
'server/functions.lua',
'server/main.lua'
}
หรือ
server_scripts {
'server/*.lua'
}
Resource ใหญ่มักแยก Server Code ออกเป็นหลายส่วน เช่น
server/
├── database.lua
├── callbacks.lua
├── events.lua
├── permissions.lua
└── main.lua
การแยกแบบนี้ช่วยให้ Code ดูแลง่ายกว่าเก็บ Server Logic หลายพันบรรทัดในไฟล์เดียว
⑤ shared_script คืออะไร
shared_script คือไฟล์ที่ FiveM โหลด ทั้งฝั่ง Client และ Server
ตัวอย่าง
shared_script 'config.lua'
เหมาะกับข้อมูลหรือ Function ที่ทั้งสองฝั่งต้องใช้ร่วมกัน เช่น
Config = {}
Config.Locale = 'th'
Config.InteractionDistance = 2.0
ทั้ง Client และ Server จึงสามารถเข้าถึงค่าเหล่านี้ได้
ตัวอย่าง Manifest
fx_version 'cerulean'
game 'gta5'
shared_script 'config.lua'
client_script 'client.lua'
server_script 'server.lua'
⑥ shared_scripts ใช้หลายไฟล์อย่างไร
สามารถใช้
shared_scripts {
'shared/config.lua',
'shared/constants.lua',
'shared/functions.lua'
}
หรือ
shared_scripts {
'shared/*.lua'
}
ตัวอย่างโครงสร้าง
shared/
├── config.lua
├── constants.lua
└── functions.lua
แต่ต้องแน่ใจว่า Code ที่อยู่ใน Shared Files สามารถทำงานได้ทั้ง Client และ Server
ถ้า Shared File เรียก Native หรือ Function ที่มีเฉพาะฝั่งเดียว อาจ Error ในอีกฝั่ง
⑦ ตารางเปรียบเทียบ Client, Server และ Shared
จำแบบง่ายได้ดังนี้
| ประเภท | ทำงานที่ไหน | เหมาะกับอะไร |
|---|---|---|
client_script | เครื่องผู้เล่น | UI, Marker, NPC, Animation, Controls |
server_script | FXServer | Database, Money, Items, Validation |
shared_script | ทั้งสองฝั่ง | Config, Constants, Shared Functions |
ตัวอย่าง
Marker
→ Client
เปิด Animation
→ Client
ตรวจเงิน
→ Server
บันทึก Database
→ Server
ราคาที่ทั้ง Client/Server ต้องอ่าน
→ Shared
แต่มีข้อสำคัญคือ ข้อมูลที่ Client อ่านได้ต้องไม่ถูกใช้เป็น Security Authority
⑧ Client Script เชื่อถือได้หรือไม่
ในเรื่อง Security ควรคิดว่า
Client = ไม่ควรเชื่อถือ
เพราะ Client อยู่ฝั่งผู้เล่น
ข้อมูลที่ส่งมาจาก Client เช่น
จำนวนเงิน
จำนวน Item
Job
ราคา
Reward
Permission
ไม่ควรถูก Server เชื่อโดยไม่ตรวจสอบ
ตัวอย่างแนวคิดที่อันตราย
Client:
"ให้เงินฉัน 100,000"
Server:
รับค่ามาแล้วเพิ่มเงินทันที
ถ้าไม่มี Validation ผู้โจมตีอาจพยายาม Trigger Event ด้วยค่าที่ดัดแปลงเอง
หลักที่ดีกว่าคือ
Client
↓
ขอทำ Action
↓
Server
↓
ตรวจเงื่อนไข
↓
Server คำนวณ Reward
↓
Server เปลี่ยนข้อมูล
⑨ Logic เงินควรอยู่ Client หรือ Server
ระบบเงินควรให้ Server เป็นผู้ตัดสิน
ตัวอย่างไม่ควรทำ
-- client.lua
local reward = 50000
TriggerServerEvent('job:reward', reward)
แล้ว Server เชื่อ reward โดยตรง
แนวทางที่ปลอดภัยกว่าคือ Client ส่งเพียง Action ที่จำเป็น
TriggerServerEvent('job:complete')
จากนั้น Server เป็นผู้กำหนด Reward
local reward = 500
-- ตรวจว่าผู้เล่นทำงานสำเร็จจริงก่อน
แนวคิดคือ
Client แจ้งเหตุการณ์
Server ตรวจสอบ
Server คำนวณ
Server ให้ Reward
ไม่ใช่ให้ Client เป็นคนตัดสินข้อมูลสำคัญ
⑩ ระบบ Item ควรอยู่ฝั่งไหน
การ Add/Remove Item ที่มีผลจริงกับข้อมูลผู้เล่นควรทำและตรวจสอบฝั่ง Server
ตัวอย่าง Flow
Client
↓
ผู้เล่นกด Craft
Server
↓
ตรวจว่ามีวัตถุดิบหรือไม่
↓
Remove Item
↓
Add Crafted Item
ไม่ควรพึ่งเพียง Client ว่า
ผู้เล่นบอกว่ามีวัตถุดิบครบ
เพราะ Client สามารถถูกดัดแปลงหรือส่งข้อมูลที่ Server ไม่ควรเชื่อถือ
⑪ Database ต้องอยู่ Server หรือไม่
โดยทั่วไป Code ที่เชื่อม Database ควรอยู่ Server-side
ตัวอย่าง
server_script 'server/database.lua'
Flow ควรเป็น
Client
↓
Request Data
↓
Server
↓
Database
↓
Server
↓
Response
↓
Client
ไม่ควรเปิด Database Credential หรือ Connection Logic ให้ Client
ระบบอย่าง oxmysql จึงถูกใช้งานในบริบท Server เพื่อ Query Database
⑫ Password ใส่ใน client_script ได้ไหม
ไม่ควร
อย่าเก็บ
Database Password
API Secret
Private Token
License Secret
Webhook Secret
ไว้ใน Client File
ตัวอย่างที่ไม่ควรทำ
local secretKey = 'MY-SECRET-KEY'
หากข้อมูลถูกส่งให้ Client ก็ไม่ควรถูกถือว่าเป็น Secret อีกต่อไป
Secret ควรอยู่ใน Server-controlled environment
⑬ Password ใส่ใน shared_script ได้ไหม
ไม่ควรเช่นกัน
เพราะตามเอกสาร Cfx.re shared_script จะถูกโหลดทั้ง Client และ Server และถูกเพิ่มเข้า Resource Packfile ที่ Client ใช้งานด้วย
ดังนั้น
shared_script 'config.lua'
หมายความว่า config.lua ไม่ควรถูกใช้เก็บ Secret
ตัวอย่างที่ควรหลีกเลี่ยง
Config.DatabasePassword = 'password'
Config.ApiSecret = 'secret'
ใน Shared Config
ให้แยก Public Config กับ Server-only Config ออกจากกัน
⑭ แยก Config Client/Server อย่างไร
แทนที่จะมีไฟล์เดียว
config.lua
ที่มีทุกอย่าง อาจแบ่งเป็น
shared/config.lua
server/config.lua
Manifest เช่น
shared_script 'shared/config.lua'
server_scripts {
'server/config.lua',
'server/main.lua'
}
ใน Shared Config เก็บเฉพาะข้อมูลที่ไม่เป็นความลับ
Config.Locale = 'th'
Config.DrawDistance = 20.0
ส่วนข้อมูล Server-only เก็บอีกไฟล์
ServerConfig = {}
ServerConfig.Reward = 500
และไม่โหลดไฟล์นั้นเป็น Shared Script
⑮ Reward ควรซ่อนไว้ Server อย่างเดียวไหม
ถ้า Reward ต้องถูกแสดงใน UI เช่น
งานนี้ได้เงิน 500 ดอลลาร์
Client อาจต้องรู้ราคาเพื่อแสดงผล
แต่ Server ต้องคำนวณและตรวจ Reward จริงของตัวเองอีกครั้ง
แนวคิดคือ
Client Display = 500
ไม่ได้แปลว่า Server ต้องเชื่อ
Client ส่ง reward = 500
Server ควรมี Source of Truth ของตนเอง
ตัวอย่าง
local serverReward = 500
แล้วใช้ค่านั้นเมื่อจ่ายเงินจริง
⑯ Coordinates ควรอยู่ Shared ได้ไหม
ได้ หากทั้ง Client และ Serverจำเป็นต้องใช้และข้อมูลไม่ได้เป็น Secret
ตัวอย่าง
Config.Garages = {
vector3(100.0, 200.0, 30.0)
}
สามารถอยู่ Shared Config ได้
แต่ถ้า Server ไม่ต้องใช้ Coordinates เหล่านี้เลย สามารถเก็บเฉพาะ Client ก็ได้
หลักคือ
Shared เฉพาะข้อมูลที่ทั้งสองฝั่งต้องใช้จริง
ไม่จำเป็นต้องย้าย Config ทุกอย่างไป Shared เพราะสะดวก
⑰ client_script ใช้ Native ของ GTA V ได้
Client Script เป็นพื้นที่หลักสำหรับ Native ที่เกี่ยวข้องกับ Game World ของผู้เล่น
เช่นแนวคิด
Create Vehicle
Create Ped
Play Animation
Draw Marker
Camera
Player Controls
Native หลายตัวมีบริบทฝั่ง Client
หากนำ Client-only Native ไปเรียกใน Server Script อาจไม่ทำงานหรือเกิด Error
จึงต้องอ่าน Native Reference และดู API Set ของ Native ที่ใช้ด้วย
⑱ server_script เรียก Client Function ได้โดยตรงไหม
ไม่ใช่เพียงเรียก Function Local ของ client.lua ตรง ๆ เพราะ Client และ Server ทำงานคนละ Runtime
โดยทั่วไปการสื่อสารจะใช้กลไก เช่น
Events
Callbacks
Exports
State
Framework APIs
ตัวอย่างแนวคิด
Server
↓
Trigger Client Event
↓
Client
↓
เปิด Menu / Animation / Notification
หรือ
Client
↓
Trigger Server Event
↓
Server
↓
ตรวจข้อมูล / Database
การเข้าใจว่าทั้งสองฝั่งแยก Runtime กันเป็นพื้นฐานสำคัญของ FiveM Development
⑲ Client กับ Server มีตัวแปรร่วมกันหรือไม่
ไม่
สมมติใน client.lua
local money = 1000
แล้วใน server.lua
print(money)
Server ไม่ได้เห็นตัวแปร Local ของ Client
เพราะทั้งสอง Script ทำงานคนละ Runtime
ถ้าต้องการส่งข้อมูลต้องใช้ระบบสื่อสารที่เหมาะสม
เช่น
Event
Callback
State Bag
Export
Framework API
ตามประเภทของข้อมูล
⑳ shared_script ทำให้ตัวแปร Shared แบบ Real-time ไหม
นี่เป็นสิ่งที่มือใหม่สับสนมาก
สมมติ
shared_script 'config.lua'
ไม่ได้หมายความว่า Client และ Server ใช้ Memory ก้อนเดียวกัน
ทั้งสองฝั่งจะโหลด Code ของไฟล์นั้นใน Runtime ของตัวเอง
แนวคิดคือ
config.lua
↓
โหลด Client Copy
+
โหลด Server Copy
ไม่ใช่
Client ↔ ตัวแปร Memory เดียวกัน ↔ Server
ดังนั้นถ้า Client เปลี่ยนตัวแปรบางค่า ไม่ได้แปลว่าตัวแปรฝั่ง Server จะเปลี่ยนตามโดยอัตโนมัติ
㉑ shared_script เหมาะกับอะไรที่สุด
สิ่งที่เหมาะ เช่น
Configuration ทั่วไป
Config.Locale = 'th'
Constants
Config.MaxDistance = 5.0
Tables ที่ทั้งสองฝั่งต้องอ่าน
Config.Jobs = {
police = true,
ambulance = true
}
Utility Functions ที่ใช้ได้ทั้งสอง Runtime
function Clamp(value, min, max)
return math.max(min, math.min(max, value))
end
โดยต้องไม่มีการเรียก API ที่ใช้ได้เฉพาะ Client หรือ Server อยู่ภายใน Function นั้น
㉒ shared_script ไม่เหมาะกับอะไร
ไม่ควรใช้ Shared กับ
Database Credentials
API Secrets
Private Tokens
Server-only Validation
Anti-cheat Detection Logic ที่ไม่ควรถูกเปิดเผยโดยไม่จำเป็น
Server Permission Logic
Critical Economy Logic
Server-only Business Rules
ถ้าทั้ง Client และ Server ไม่จำเป็นต้องใช้ อย่า Shared เพียงเพราะเขียนสะดวก
หลักการที่ดีคือ
ต้องใช้ทั้งสองฝั่งจริง
→ Shared
ใช้ Client อย่างเดียว
→ Client
ใช้ Server อย่างเดียว
→ Server
㉓ ox_lib init ทำไมมักเป็น shared_script
Resource ที่ใช้ ox_lib มักพบ
shared_script '@ox_lib/init.lua'
หมายถึง Resource นี้โหลด init.lua จาก ox_lib ทั้งสองฝั่ง
รูปแบบ @resource/file เป็นการอ้าง File จาก Resource อื่น
ตัวอย่าง
@ox_lib/init.lua
จึงต้องมี Resource ชื่อ
ox_lib
อยู่จริง
ถ้า Rename ox_lib หรือไม่ได้ติดตั้ง Resource อาจเกิดปัญหาตามมา
㉔ server_script สามารถโหลดไฟล์จาก Resource อื่นได้ไหม
FiveM รองรับรูปแบบอ้าง Resource อื่น เช่น
server_script '@resource_name/script.lua'
แต่การออกแบบ Resource สมัยใหม่มักใช้ APIs, Exports หรือ Libraries ตามที่ Project นั้นกำหนด
ไม่ควรดึง Internal File จาก Resource อื่นโดยพลการ เพราะ Update Resource ต้นทางอาจเปลี่ยนโครงสร้างและทำให้ Script พังได้
ให้ทำตาม Public API และ Documentation ของ Dependency เป็นหลัก
㉕ client_script ถูกดาวน์โหลดไปยัง Client หรือไม่
ตามเอกสาร Resource Manifest client_script ถูกโหลดบน Client และถูกเพิ่มเข้า Resource Packfile
ดังนั้น Code ฝั่ง Client ไม่ควรถูกถือว่าเป็น Secret
แม้ Resource บางตัวจะใช้ Asset Escrow หรือ Protection อื่น การออกแบบ Security ก็ยังไม่ควรพึ่งสมมติฐานว่า Client จะไม่มีทางรู้ Logic หรือส่งข้อมูลปลอมกลับ Server
Server ต้อง Validate ข้อมูลสำคัญเสมอ
㉖ shared_script ถูกดาวน์โหลดไป Client หรือไม่
ใช่
เอกสาร Cfx.re ระบุว่า shared_script ถูกโหลดทั้งสองฝั่งและเพิ่มเข้า Resource Packfile
ดังนั้น Config แบบ
shared_script 'config.lua'
ควรถูกมองว่า Client มีสิทธิ์ได้รับไฟล์นั้น
อย่าเก็บ Secret ไว้ใน Shared Config
นี่เป็นเหตุผลด้าน Security ที่สำคัญกว่าการจัด Folder ให้สวยเสียอีก
㉗ server_script ถูกส่งไป Client หรือไม่
server_script ถูกกำหนดให้โหลดบน Server
ดังนั้น Server-only Logic ควรอยู่ฝั่งนี้
แต่ต้องเข้าใจว่า การเก็บ Logic ไว้ Server ไม่ได้ช่วย หาก Server ยังคงเชื่อ Input จาก Client อย่างไม่มี Validation
ตัวอย่าง Server Code ที่ยังเสี่ยง
Client ส่ง Reward = 999999
↓
Server เชื่อทันที
↓
เพิ่มเงิน
แม้ Code การเพิ่มเงินอยู่ Server ก็ยังออกแบบไม่ปลอดภัย
จุดสำคัญคือ Server ต้องเป็น Authority และตรวจข้อมูลด้วย
㉘ Event ระหว่าง Client กับ Server ทำงานอย่างไร
แนวคิดพื้นฐานคือ
Client
↓
Network Event
↓
Server
และ
Server
↓
Network Event
↓
Client
ตัวอย่างสถานการณ์
ผู้เล่นกดเก็บปลา
↓
Client แจ้ง Server
↓
Server ตรวจตำแหน่ง/สถานะ/เงื่อนไข
↓
Server เพิ่ม Item
↓
Server แจ้ง Client
↓
Client แสดง Notification
นี่เป็น Architecture ที่ดีกว่าการให้ Client เพิ่ม Item ด้วยตัวเอง
㉙ RegisterNetEvent เกี่ยวข้องกับ Client/Server อย่างไร
RegisterNetEvent ใช้ลงทะเบียน Event ที่สามารถใช้ใน Network Context
ตัวอย่างเชิงแนวคิด
Client
TriggerServerEvent('job:complete')
Server
RegisterNetEvent('job:complete', function()
-- Validate และทำงานฝั่ง Server
end)
หรือ Server สามารถส่ง Event กลับ Client
เรื่อง Event และ RegisterNetEvent จะมีรายละเอียดโดยตรงในหัวข้อถัด ๆ ไป แต่สิ่งสำคัญตอนนี้คือ Client/Server Code ไม่ได้ทำงานในพื้นที่เดียวกัน
㉚ Script ไหนควรตรวจ Permission
Permission ที่มีผลต่อสิทธิ์จริงควรตรวจฝั่ง Server
ตัวอย่าง
Admin Menu
Boss Menu
Give Money
Give Item
Ban
Kick
Delete Vehicle Ownership
แม้ Client จะซ่อนปุ่มจาก User ที่ไม่มีสิทธิ์ แต่ Server ต้องตรวจ Permission ซ้ำ
เหตุผลคือ
ซ่อน Menu
≠
ป้องกัน Event
ผู้โจมตีอาจพยายามเรียก Server Event โดยตรงโดยไม่ผ่าน UI
㉛ ซ่อนปุ่มใน Client เพียงพอไหม
ไม่เพียงพอสำหรับ Security
ตัวอย่าง Client
if job == police
→ แสดงปุ่ม Armory
เป็นเรื่องดีสำหรับ UX
แต่ Server Event สำหรับรับอาวุธก็ควรตรวจ
Player Job = police?
Permission ถูก?
เงื่อนไขครบ?
อีกครั้ง
ดังนั้น Architecture ที่ดีคือ
Client
→ ควบคุม UX
Server
→ ควบคุม Authority
㉜ Client ส่งราคาให้ Server ได้ไหม
ส่งได้ในเชิงเทคนิค แต่ Server ไม่ควรเชื่อราคานั้นเป็น Source of Truth
ตัวอย่างที่ไม่ควรใช้
TriggerServerEvent('shop:buy', item, price)
แล้ว Server หัก price ตาม Client
แนวคิดที่ดีกว่า
TriggerServerEvent('shop:buy', item)
แล้ว Server หา Price จาก Server-controlled Config
Item
↓
Server Price Table
↓
ตรวจเงิน
↓
หักเงิน
↓
ให้ Item
ช่วยลดโอกาสที่ Client จะปรับราคาเอง
㉝ Shared Config ราคาใช้ได้ไหม
ใช้เพื่อ Display ได้
ตัวอย่าง Shared Config
Config.Items = {
water = {
label = 'Water',
displayPrice = 10
}
}
Client สามารถนำไปแสดง Menu
แต่เมื่อซื้อจริง Server ควรมีค่าที่เชื่อถือได้ของตัวเอง หรือ Validate ตามระบบที่ออกแบบไว้
อย่าให้ “ค่าที่ Client มองเห็น” เป็น Security Boundary
㉞ Framework Object ควรอยู่ไฟล์ไหน
ขึ้นอยู่กับ Framework และ API รุ่นที่ Script ใช้
Client Code อาจต้องเข้าถึง Client Framework APIs
Server Code อาจต้องเข้าถึง Server Framework APIs
ไม่ควร Copy Code จาก Tutorial เก่ามาวางทั้งสองฝั่งโดยไม่ดู Documentation ปัจจุบัน
ตัวอย่าง Architecture
client/framework.lua
→ Client Framework Integration
server/framework.lua
→ Server Framework Integration
วิธีนี้ช่วยแยก Logic ชัดเจนมากขึ้น
㉟ Database Query ไม่ควรอยู่ shared_script
ถ้า Shared File มี Database Query Function ที่เรียก API ฝั่ง Server อาจ Error เมื่อไฟล์ถูกโหลดฝั่ง Client
ตัวอย่างแนวคิดไม่ดี
-- shared.lua
function LoadPlayerDatabase()
-- Server-only database call
end
Shared File จะถูกโหลด Client ด้วย
หาก Code รันโดยไม่มีการแยก Context ก็อาจเกิด Error
Database Logic ควรอยู่
server/
เป็นหลัก
㊱ Native ฝั่ง Client ไม่ควรใส่ shared โดยไม่ตรวจ Context
ตัวอย่าง Function ใน Shared File
function CreateMyPed()
-- client native
end
หาก Server เรียกหรือ Execute Code ที่ใช้ Client-only Native อาจเกิด Error
ถ้าจำเป็นต้องมี Shared File ที่มี Code แตกต่างตาม Context ต้องออกแบบอย่างระมัดระวัง
สำหรับมือใหม่ วิธีง่ายกว่าคือแยก
client/
server/
shared/
ให้ชัดเจน
㊲ โครงสร้าง Script FiveM ที่แนะนำ
ตัวอย่าง
my-script/
├── fxmanifest.lua
├── shared/
│ ├── config.lua
│ └── constants.lua
├── client/
│ ├── main.lua
│ ├── target.lua
│ └── ui.lua
└── server/
├── main.lua
├── database.lua
└── permissions.lua
Manifest
fx_version 'cerulean'
game 'gta5'
shared_scripts {
'shared/config.lua',
'shared/constants.lua'
}
client_scripts {
'client/main.lua',
'client/target.lua',
'client/ui.lua'
}
server_scripts {
'server/database.lua',
'server/permissions.lua',
'server/main.lua'
}
โครงสร้างแบบนี้ช่วยให้เห็นทันทีว่า Code แต่ละส่วนทำงานที่ไหน
㊳ ลำดับไฟล์สำคัญหรือไม่
สำคัญหาก Code มี Dependency ต่อกัน
ตัวอย่าง
server_scripts {
'server/functions.lua',
'server/main.lua'
}
ถ้า main.lua เรียก Function จาก functions.lua ตั้งแต่เริ่มต้น ก็ควรให้ Function พร้อมก่อน
เช่นเดียวกับ Shared
shared_scripts {
'shared/config.lua',
'shared/functions.lua'
}
ถ้า functions.lua ใช้ Config ให้โหลด Config ก่อน
ไม่ควรพึ่ง Wildcard โดยไม่รู้ว่าลำดับมีผลกับ Script หรือไม่
㊴ client_script กับ files ต่างกันอย่างไร
client_script
client_script 'client.lua'
บอก FiveM ว่าไฟล์นี้เป็น Script ที่ต้อง Execute ฝั่ง Client
ส่วน
files {
'html/index.html',
'html/style.css'
}
หมายถึงไฟล์ที่จะรวมให้ Client ใช้งาน แต่ไม่ได้แปลว่าทุกไฟล์จะถูก Execute เป็น Client Script
เช่น HTML, CSS และ Images เป็น Resource Files ไม่ใช่ Lua Client Scripts
อย่าสับสนสอง Directive นี้
㊵ server_script กับ server_only ต่างกันอย่างไร
server_script
server_script 'server.lua'
กำหนดว่าไฟล์ใดทำงานบน Server
ส่วน
server_only 'yes'
กำหนดว่า Resource ทั้งตัวเป็น Server-only เพื่อไม่ให้ Clients ดาวน์โหลด Content ของ Resource
ถ้า Resource มี Client Script จริงก็ไม่ควรตั้ง server_only 'yes' แบบสุ่ม
ใช้เมื่อ Resource นั้นออกแบบให้ทำงานฝั่ง Server อย่างเดียวจริง ๆ
㊶ Script Server-only ตัวอย่าง
โครงสร้าง
server-logger/
├── fxmanifest.lua
└── server.lua
Manifest
fx_version 'cerulean'
game 'gta5'
server_only 'yes'
server_script 'server.lua'
เหมาะกับ Resource ที่ไม่มี Client Component
แต่หากภายหลังเพิ่ม
client.lua
ก็ต้องประเมิน Architecture ใหม่
㊷ ทำไม Client Error ดูใน F8
เพราะ client_script ทำงานใน Client Runtime
หากเกิด Error เช่น
@my-script/client/main.lua:42
ผู้ดูแลควรตรวจ
F8
ของ Client ที่เกิดปัญหา
อาการที่มักเกี่ยวกับ Client ได้แก่
Menu ไม่เปิด
Target ไม่ขึ้น
Marker ไม่แสดง
NPC ไม่ Spawn
Animation ไม่เล่น
Server Console อาจไม่มี Error เพราะปัญหาไม่ได้เกิดใน Server Runtime
㊸ ทำไม Server Error ดู Server Console
เพราะ server_script ทำงานบน FXServer
ปัญหา เช่น
SQL Error
Player Data Error
Permission Error
Server Event Error
มักปรากฏใน Server Console หรือระบบ Console ที่ Server ใช้อยู่
ดังนั้นเวลาหา Bug ต้องดูก่อนว่า Error Path เป็น
client/
หรือ
server/
จะช่วยจำกัดพื้นที่ค้นหาได้เร็วมาก
㊹ shared_script Error อาจขึ้นทั้งสองฝั่ง
เพราะ Shared Script ถูกโหลดทั้ง Client และ Server
ตัวอย่าง
shared_script 'shared/functions.lua'
หาก File มี Code ที่ Client ใช้ไม่ได้ อาจ Error ใน F8
หากมี Code ที่ Server ใช้ไม่ได้ ก็อาจ Error ใน Server Console
นี่เป็นเหตุผลที่ Shared Code ควรเป็น Code ที่ไม่ผูกกับ Runtime ใด Runtime หนึ่งโดยไม่ตั้งใจ
㊺ Script Started แต่ Client ไม่ทำงาน
ให้ตรวจ fxmanifest.lua
ตัวอย่าง
client_script 'client/main.lua'
จากนั้นดูว่า
client/main.lua
มีจริงหรือไม่
ถ้า Manifest โหลดเฉพาะ
server_script 'server/main.lua'
แต่ลืมประกาศ Client File Resource อาจ Start ได้ แต่ระบบ Client จะไม่ทำงาน
จึงเกิดอาการ
Started resource
แต่ไม่มี Marker/Menu/Target
㊻ Script Started แต่ Server Logic ไม่ทำงาน
หลักเดียวกัน
Manifest อาจมี
client_script 'client/main.lua'
แต่ลืม
server_script 'server/main.lua'
Client UI อาจเปิดได้
แต่
ซื้อ Item ไม่ได้
Database ไม่บันทึก
Reward ไม่เข้า
เพราะ Server Logic ไม่ถูกโหลด
ดังนั้น Resource Started ไม่ได้หมายความว่า Client และ Server Files ถูกประกาศครบ
㊼ วิธีเลือกว่าจะใช้ client, server หรือ shared
ถาม 3 คำถาม
Code นี้เกี่ยวกับ Game World ของผู้เล่นเป็นหลักหรือไม่
ถ้าใช่ มักเป็น
Client
Code นี้ควบคุมข้อมูลสำคัญหรือ Server Authority หรือไม่
เช่น
Money
Items
Database
Permissions
Rewards
ควรอยู่
Server
ทั้ง Client และ Server ต้องใช้ Code/Data เดียวกันจริงหรือไม่
ถ้าใช่และไม่มี Secret
Shared
หลักนี้ช่วยตัดสินใจได้ดีใน Script ส่วนใหญ่
㊽ ตัวอย่างระบบ Job แบ่ง Client/Server/Shared
Shared
Job Locations
Labels
บาง Config ที่ไม่เป็น Secret
Client
Marker
NPC
Animation
Interaction
UI
Server
ตรวจ Job
ตรวจ Item
ตรวจ Cooldown
คำนวณ Reward
เพิ่มเงิน
บันทึก Database
Flow คือ
Shared Config
↓
Client แสดงจุดทำงาน
↓
ผู้เล่นทำ Action
↓
Server Validate
↓
Server ให้ Reward
↓
Client แสดงผล
นี่เป็น Architecture ที่เข้าใจง่ายและปลอดภัยกว่าการให้ Client ตัดสินทุกอย่าง
㊾ ตัวอย่าง Shop Script
Shared/Client
อาจใช้แสดง
ชื่อสินค้า
รูป
หมวดหมู่
ตำแหน่งร้าน
Client
ทำหน้าที่
เปิด Menu
Target
NUI
Controls
Server
ทำหน้าที่
ตรวจสินค้า
ตรวจราคาจริง
ตรวจเงิน
หักเงิน
ให้ Item
แม้ Client จะแสดงราคา 100 แต่ Server ควรมี Validation ของตัวเอง
Client UI ไม่ควรถูกใช้เป็นกลไก Security
㊿ ข้อผิดพลาดที่พบบ่อยในการแยก Client/Server/Shared
ใส่ทุกอย่างไว้ Shared
ทำให้ Client ได้รับ Code/Config ที่ไม่จำเป็น และอาจมี Server-only Code Error
ใส่ Database ใน Client
ผิดแนวทางด้าน Architecture และ Security
ให้ Client คำนวณ Reward
Server สูญเสีย Authority
ซ่อน Menu แล้วคิดว่าปลอดภัย
ผู้โจมตีอาจเรียก Event โดยตรง
ไม่ประกาศ Client File ใน Manifest
Resource Started แต่ Gameplay ไม่แสดง
ไม่ประกาศ Server File
UI ทำงาน แต่ Data/Reward/Database ไม่ทำงาน
เก็บ Secret ใน Shared Config
ข้อมูลถูกส่งไปยัง Client
การแยก Runtime ให้ถูกจึงไม่ใช่เพียงเรื่องการจัด Folder แต่เป็นพื้นฐานของ FiveM Security ด้วย
Checklist client_script, server_script และ shared_script
Client
ควรใช้กับ
UI
NUI Interaction
Marker
Blip
NPC
Camera
Animation
Controls
Client Target
ไม่ควรเป็น Authority ของ
Money
Items
Permission
Reward
Database
Server
ควรใช้กับ
Database
Economy
Player Data
Inventory Validation
Permissions
Rewards
Sensitive Business Logic
และต้อง Validate ข้อมูลจาก Client
Shared
เหมาะกับ
Public Config
Constants
Shared Utility Functions
Data ที่ทั้ง Client/Server ต้องอ่าน
ไม่ควรเก็บ
Password
Secret Key
Private Token
Server-only Security Logic
Manifest
ตรวจว่า
shared_scripts {
...
}
client_scripts {
...
}
server_scripts {
...
}
ชี้ไปยัง File ที่มีอยู่จริงทั้งหมด
สรุป FiveM client_script, server_script และ shared_script ต่างกันอย่างไร
ความแตกต่างจำง่ายที่สุดคือ
client_script
→ รันบนเครื่องผู้เล่น
server_script
→ รันบน FXServer
shared_script
→ รันทั้ง Client และ Server
ถ้าเป็น Marker, UI, Animation, NPC หรือ Controls โดยทั่วไปเป็นงานฝั่ง Client
ถ้าเป็น Database, Money, Items, Permissions หรือ Reward ควรควบคุมจาก Server และต้องตรวจสอบข้อมูลที่ Client ส่งเข้ามา
ส่วน shared_script เหมาะกับ Config หรือ Function ที่ทั้งสองฝั่งต้องใช้ แต่ ไม่ควรเก็บ Password, Token หรือ Secret เนื่องจาก Shared Script ถูกโหลดให้ Client ด้วย
สิ่งสำคัญอีกข้อคือ Shared ไม่ได้หมายความว่า Client และ Server ใช้ Memory เดียวกัน แต่หมายถึง Code ไฟล์เดียวกันถูกโหลดแยกเข้าสู่ Runtime ทั้งสองฝั่ง
แนวทางของ comsiam คือให้ Server เป็น Source of Truth สำหรับระบบสำคัญ โดย Client มีหน้าที่เกี่ยวกับการแสดงผลและ Interaction เป็นหลัก วิธีนี้ช่วยให้ Script เข้าใจง่ายและลดช่องโหว่จากการเชื่อข้อมูล Client มากเกินไป
สำหรับคนที่กำลังเริ่มเขียน FiveM Script comsiam แนะนำให้แยก Folder client, server และ shared ตั้งแต่เริ่มโปรเจกต์ แม้ Script ยังเล็ก เพราะเมื่อระบบใหญ่ขึ้นจะจัด Dependency, Debug Error และตรวจ Security ได้ง่ายกว่าการรวมทุกอย่างไว้ในไฟล์เดียว
Comments
Post a Comment