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_scriptFXServerDatabase, 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

Popular posts from this blog

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

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

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