Roblox EP3 เซิร์ฟเวอร์กับเครื่องผู้เล่น: โค้ดแต่ละแบบทำงานที่ไหน
เกม Roblox มีสองฝั่ง เซิร์ฟเวอร์ที่ทุกคนใช้ร่วมกัน และเครื่องของผู้เล่นแต่ละคน ทดลองให้เห็นกับตาว่าการเปลี่ยนแปลงฝั่งไหนใครเห็น วางโค้ดให้ถูกที่ และทำไมเรื่องนี้กันโกงได้
- เซิร์ฟเวอร์คือเกมตัวจริงที่ทุกคนใช้ร่วมกัน สิ่งที่เซิร์ฟเวอร์เปลี่ยน ทุกคนเห็น
- สิ่งที่เครื่องผู้เล่นเปลี่ยนเอง เห็นแค่เครื่องนั้นคนเดียว เหมาะกับหน้าจอและเอฟเฟกต์ ไม่เหมาะกับคะแนนหรือเงิน
- เรื่องที่มีผลกับผลแพ้ชนะ ต้องให้เซิร์ฟเวอร์ตัดสินเสมอ เพราะเครื่องผู้เล่นถูกแก้ไขได้
ใน EP2 เราเขียน Script ใส่ไว้ในพื้นแต่ละชิ้น มันทำงานได้ แต่เรายังไม่ได้ถามว่า มันทำงานอยู่ที่ไหน ตอนนี้จะตอบคำถามนั้น เพราะเป็นเรื่องที่ทำให้เกมเล่นหลายคนได้ถูกต้อง และเป็นหัวใจของการกันคนโกง ใครที่ข้ามเรื่องนี้ไป จะเจอบั๊กแปลก ๆ แบบ “ในเครื่องเราเห็น แต่เพื่อนไม่เห็น”
เกมหนึ่งเกม มีสองฝั่ง
เมื่อมีคนเข้าเกมของเรา Roblox เปิด เซิร์ฟเวอร์ หนึ่งตัวบนเครื่องของ Roblox ทุกคนที่อยู่ในห้องเดียวกันต่อเข้าเซิร์ฟเวอร์ตัวนี้ และแต่ละคนมีเกมอีกชุดรันอยู่บน เครื่องของตัวเอง (เรียกว่า client)
| เซิร์ฟเวอร์ | เครื่องผู้เล่น (client) | |
|---|---|---|
| มีกี่ตัว | หนึ่งตัวต่อห้อง | หนึ่งตัวต่อผู้เล่น |
| หน้าที่ | เกมตัวจริง ตัดสินทุกอย่าง | แสดงผล รับปุ่มกด กล้อง เสียง |
| ใครเห็นสิ่งที่เปลี่ยน | ทุกคน | เครื่องนั้นคนเดียว |
| ผู้เล่นแก้ได้ไหม | ไม่ได้ | ได้ (โปรแกรมโกงทำงานตรงนี้) |
หลักการ: เซิร์ฟเวอร์คือความจริงของเกม เครื่องผู้เล่นคือหน้าต่างที่มองเข้าไป
ทดลองให้เห็นกับตา
Studio จำลองเกมหลายคนได้บนเครื่องเดียว
- เปิดเกมจาก EP2
- ไปที่แท็บ Test หาส่วน Clients and Servers ตั้งจำนวนผู้เล่นเป็น 2 แล้วกด Start
- Studio จะเปิดสามหน้าต่าง: เซิร์ฟเวอร์หนึ่ง และผู้เล่นสองคน (Player1, Player2)
ตอนนี้เพิ่มโค้ดฝั่งผู้เล่น ปิดการทดสอบก่อน แล้วใน Explorer เปิด StarterPlayer → StarterPlayerScripts กด + เลือก LocalScript
local baseplate = workspace:WaitForChild("Baseplate")
baseplate.Color = Color3.fromRGB(255, 120, 200)
print("client: เปลี่ยนพื้นเป็นสีชมพู")
เริ่มทดสอบแบบ 2 คนอีกรอบ ทั้งสองหน้าต่างของผู้เล่นเห็นพื้นสีชมพู แต่หน้าต่าง เซิร์ฟเวอร์ยังเห็นสีเดิม เพราะ LocalScript ทำงานบนเครื่องของผู้เล่นแต่ละคน แต่ละเครื่องทาสีพื้น ของตัวเอง เซิร์ฟเวอร์ไม่รู้เรื่องเลย
ลองให้ต่างกันชัดขึ้น แก้ให้ทาสีเฉพาะผู้เล่นชื่อ Player1
local Players = game:GetService("Players")
local me = Players.LocalPlayer
if me.Name == "Player1" then
workspace:WaitForChild("Baseplate").Color = Color3.fromRGB(255, 120, 200)
end
Player1 เห็นสีชมพู Player2 เห็นสีเดิม นี่คือสิ่งที่ “เห็นแค่เครื่องเดียว” หมายถึง
สองคำใหม่ในโค้ดนี้
workspace:WaitForChild("Baseplate")โค้ดฝั่งผู้เล่นเริ่มทำงานเร็วมาก บางครั้งก่อนที่ฉากจะโหลดเสร็จ ถ้าใช้workspace.Baseplateตรง ๆ อาจเจอ error ว่าไม่มีWaitForChildจะรอจนของนั้นมาถึงก่อน กฎง่าย ๆ คือ โค้ดฝั่งผู้เล่นที่อ้างถึงของในฉาก ให้ใช้ WaitForChildPlayers.LocalPlayerผู้เล่นที่เป็นเจ้าของเครื่องนี้ ใช้ได้เฉพาะในโค้ดฝั่งผู้เล่น บนเซิร์ฟเวอร์ค่านี้เป็น nil เพราะเซิร์ฟเวอร์ไม่ได้เป็นของผู้เล่นคนไหน
โค้ดสามแบบ วางให้ถูกที่
| แบบ | ทำงานที่ | วางไว้ที่ |
|---|---|---|
| Script | เซิร์ฟเวอร์ | ServerScriptService (แนะนำ) หรือใน Workspace |
| LocalScript | เครื่องผู้เล่น | StarterPlayerScripts, StarterCharacterScripts, StarterGui |
| ModuleScript | ไม่ทำงานเอง ต้องมีคนเรียก | ReplicatedStorage ถ้าใช้ทั้งสองฝั่ง, ServerStorage ถ้าใช้แค่เซิร์ฟเวอร์ |
LocalScript ที่วางผิดที่ เช่น ใน Workspace จะไม่ทำงานเลย โดยไม่มี error บอก เป็นอีกบั๊กที่มือใหม่หาไม่เจอ
Script รุ่นใหม่มีช่อง RunContext ใน Properties ให้เลือกได้ว่าจะทำงานฝั่ง Server หรือ Client ไม่ว่าจะวางไว้ที่ไหน มีประโยชน์ในงานใหญ่ แต่ตอนเริ่มต้น ใช้ตารางข้างบนจะสับสนน้อยกว่า
ModuleScript: เขียนครั้งเดียว ใช้ได้ทั้งสองฝั่ง
ค่าที่หลายโค้ดใช้ร่วมกัน เช่น สีของลาวา หรือเวลาที่พื้นหายไป ไม่ควรกระจายไปพิมพ์ซ้ำในทุก Script ให้รวมไว้ที่ ModuleScript เดียว
ใน ReplicatedStorage กด + เลือก ModuleScript ตั้งชื่อ Config
local Config = {}
Config.LAVA_COLOR = Color3.fromRGB(255, 85, 0)
Config.FADE_SECONDS = 1
Config.GONE_SECONDS = 3
return Config
ModuleScript ต้อง return ของออกมาหนึ่งอย่าง ส่วนใหญ่เป็นตาราง (table) ที่รวมค่าหลาย ๆ ค่า แล้ว Script ไหนอยากใช้ ก็ require มัน
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Config = require(ReplicatedStorage:WaitForChild("Config"))
print(Config.GONE_SECONDS) -- 3
ทั้ง Script ฝั่งเซิร์ฟเวอร์และ LocalScript ฝั่งผู้เล่น require ตัวเดียวกันได้ เพราะ ReplicatedStorage ถูกส่งไปให้ทุกเครื่อง อยากปรับความยากของเกม แก้ที่เดียว
game:GetService("...") คือวิธีมาตรฐานในการหยิบบริการหลักของเกม เช่น Players, ReplicatedStorage ใช้แบบนี้แทน game.Players เพราะได้ของแน่นอนเสมอ
ทำไมเรื่องนี้คือการกันโกง
สมมติเราให้ LocalScript เพิ่มเหรียญเวลาผู้เล่นเก็บของ ผู้เล่นที่ใช้โปรแกรมโกงแก้โค้ดบนเครื่องตัวเองได้ เขาจะเพิ่มเหรียญให้ตัวเองเป็นล้านในพริบตา
ข่าวดีคือ ถ้าเกมเราออกแบบถูก การโกงนั้นจะ อยู่แค่บนเครื่องเขา เซิร์ฟเวอร์ไม่เชื่อ คนอื่นไม่เห็น และไม่ถูกบันทึก เพราะคะแนนตัวจริงเก็บและเปลี่ยนบนเซิร์ฟเวอร์
กฎที่ต้องจำทั้งซีรีส์: อย่าเชื่อเครื่องผู้เล่น คะแนน เงิน ไอเท็ม ด่านที่ผ่าน และการแพ้ชนะ ให้เซิร์ฟเวอร์เป็นคนตัดสินเสมอ
มีข้อยกเว้นหนึ่งที่ควรรู้ ตัวละครของเราเอง เครื่องของเราเป็นคนขยับ แล้วส่งตำแหน่งให้เซิร์ฟเวอร์ Roblox ออกแบบแบบนี้เพื่อให้การเดินไม่หน่วง ผลคือโปรแกรมโกงบางตัวทำให้วิ่งเร็วหรือบินได้ เกมที่จริงจังจึงให้เซิร์ฟเวอร์เช็กอีกชั้นว่าการเคลื่อนที่สมเหตุสมผลไหม เรื่องนี้ลึกกว่าระดับเริ่มต้น แต่ควรรู้ว่ามีอยู่
แล้วโค้ดใน EP2 ล่ะ
โค้ดพื้นลาวาและพื้นที่หายไปเป็น Script จึงทำงานบนเซิร์ฟเวอร์ ถูกต้องแล้ว ผู้เล่นทุกคนเห็นพื้นหายไปพร้อมกัน และไม่มีใครแก้ให้ลาวาไม่ฆ่าตัวเองได้
สิ่งที่ยังไม่ดีคือมันกระจายอยู่ในทุกชิ้นส่วน ถ้ามีลาวา 50 แผ่น ก็มี Script 50 ตัวที่เหมือนกัน แก้ทีต้องแก้ 50 ที่ EP4 จะย้ายทั้งหมดมาไว้ใน Script เดียวใน ServerScriptService
ลองเองใน 10 นาที
- ทำ LocalScript ทาสีพื้นเฉพาะ Player1 แล้วทดสอบแบบ 2 คน ดูว่าแต่ละหน้าต่างเห็นอะไร
- สร้าง ModuleScript
Configแล้วแก้FadePadจาก EP2 ให้อ่านConfig.GONE_SECONDSแทนเลข 3 - ลองย้าย LocalScript ไปไว้ใน Workspace แล้วทดสอบ จะเห็นว่ามันไม่ทำงานเลย
ข้อผิดพลาดที่เจอบ่อย
- ทำคะแนนหรือเงินใน LocalScript ทำให้คนอื่นไม่เห็นและโกงได้
- วาง LocalScript ใน Workspace แล้วมันเงียบ ไม่ทำงาน ไม่มี error
- โค้ดฝั่งผู้เล่นไม่ใช้ WaitForChild แล้วเจอ error เป็นบางครั้ง (เครื่องเร็วไม่เจอ เครื่องช้าเจอ)
- ใช้
Players.LocalPlayerใน Script ฝั่งเซิร์ฟเวอร์ แล้วได้ nil
ตอนหน้า
พร้อมแล้วสำหรับเกมจริง EP4 จะประกอบ obby ให้ครบ: จุดเซฟที่เกิดใหม่ตรงด่านล่าสุด ป้ายบอกด่านบนกระดานคะแนน ลาวาทั้งเกมคุมด้วย Script เดียว และเส้นชัยที่นับจำนวนครั้งที่ชนะ
คำถามที่พบบ่อย
ทำไมไม่ให้ทุกอย่างอยู่บนเซิร์ฟเวอร์ไปเลย
เพราะบางอย่างต้องตอบสนองทันทีบนเครื่องผู้เล่น เช่น กดปุ่มบนหน้าจอ กล้อง เสียง และเอฟเฟกต์ ถ้าต้องส่งไปถามเซิร์ฟเวอร์ก่อนทุกครั้ง จะหน่วงจนเล่นไม่สนุก
Script ใน Workspace กับใน ServerScriptService ต่างกันไหม
ทั้งสองที่ทำงานบนเซิร์ฟเวอร์ ต่างกันตรงที่ของใน Workspace ถูกส่งไปให้เครื่องผู้เล่นด้วย ส่วน ServerScriptService ไม่ถูกส่งไป เครื่องผู้เล่นมองไม่เห็นโค้ดข้างใน จึงเหมาะเป็นที่เก็บโค้ดหลักของเกม
คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ