บทความ › พัฒนาเกม

Roblox EP3 เซิร์ฟเวอร์กับเครื่องผู้เล่น: โค้ดแต่ละแบบทำงานที่ไหน

เกม Roblox มีสองฝั่ง เซิร์ฟเวอร์ที่ทุกคนใช้ร่วมกัน และเครื่องของผู้เล่นแต่ละคน ทดลองให้เห็นกับตาว่าการเปลี่ยนแปลงฝั่งไหนใครเห็น วางโค้ดให้ถูกที่ และทำไมเรื่องนี้กันโกงได้

เริ่มต้น อ่าน 7 นาที 16 ตุลาคม 2569 ทำเกม Roblox เกมแรก · บทที่ 3/5

อ่านจบแล้วจะรู้
  • เซิร์ฟเวอร์คือเกมตัวจริงที่ทุกคนใช้ร่วมกัน สิ่งที่เซิร์ฟเวอร์เปลี่ยน ทุกคนเห็น
  • สิ่งที่เครื่องผู้เล่นเปลี่ยนเอง เห็นแค่เครื่องนั้นคนเดียว เหมาะกับหน้าจอและเอฟเฟกต์ ไม่เหมาะกับคะแนนหรือเงิน
  • เรื่องที่มีผลกับผลแพ้ชนะ ต้องให้เซิร์ฟเวอร์ตัดสินเสมอ เพราะเครื่องผู้เล่นถูกแก้ไขได้

ใน EP2 เราเขียน Script ใส่ไว้ในพื้นแต่ละชิ้น มันทำงานได้ แต่เรายังไม่ได้ถามว่า มันทำงานอยู่ที่ไหน ตอนนี้จะตอบคำถามนั้น เพราะเป็นเรื่องที่ทำให้เกมเล่นหลายคนได้ถูกต้อง และเป็นหัวใจของการกันคนโกง ใครที่ข้ามเรื่องนี้ไป จะเจอบั๊กแปลก ๆ แบบ “ในเครื่องเราเห็น แต่เพื่อนไม่เห็น”

เกมหนึ่งเกม มีสองฝั่ง

เมื่อมีคนเข้าเกมของเรา Roblox เปิด เซิร์ฟเวอร์ หนึ่งตัวบนเครื่องของ Roblox ทุกคนที่อยู่ในห้องเดียวกันต่อเข้าเซิร์ฟเวอร์ตัวนี้ และแต่ละคนมีเกมอีกชุดรันอยู่บน เครื่องของตัวเอง (เรียกว่า client)

เซิร์ฟเวอร์เครื่องผู้เล่น (client)
มีกี่ตัวหนึ่งตัวต่อห้องหนึ่งตัวต่อผู้เล่น
หน้าที่เกมตัวจริง ตัดสินทุกอย่างแสดงผล รับปุ่มกด กล้อง เสียง
ใครเห็นสิ่งที่เปลี่ยนทุกคนเครื่องนั้นคนเดียว
ผู้เล่นแก้ได้ไหมไม่ได้ได้ (โปรแกรมโกงทำงานตรงนี้)

หลักการ: เซิร์ฟเวอร์คือความจริงของเกม เครื่องผู้เล่นคือหน้าต่างที่มองเข้าไป

ทดลองให้เห็นกับตา

Studio จำลองเกมหลายคนได้บนเครื่องเดียว

  1. เปิดเกมจาก EP2
  2. ไปที่แท็บ Test หาส่วน Clients and Servers ตั้งจำนวนผู้เล่นเป็น 2 แล้วกด Start
  3. 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 เห็นสีเดิม นี่คือสิ่งที่ “เห็นแค่เครื่องเดียว” หมายถึง

เซิร์ฟเวอร์ (เกมตัวจริง)Script · คะแนน · แพ้ชนะทุกคนเห็นเครื่อง Player1LocalScript · หน้าจอ · กล้องเห็นคนเดียวเครื่อง Player2LocalScript · หน้าจอ · กล้องเห็นคนเดียว
เซิร์ฟเวอร์เปลี่ยน → ทุกคนเห็น · เครื่องผู้เล่นเปลี่ยน → เห็นคนเดียว

สองคำใหม่ในโค้ดนี้

  • workspace:WaitForChild("Baseplate") โค้ดฝั่งผู้เล่นเริ่มทำงานเร็วมาก บางครั้งก่อนที่ฉากจะโหลดเสร็จ ถ้าใช้ workspace.Baseplate ตรง ๆ อาจเจอ error ว่าไม่มี WaitForChild จะรอจนของนั้นมาถึงก่อน กฎง่าย ๆ คือ โค้ดฝั่งผู้เล่นที่อ้างถึงของในฉาก ให้ใช้ WaitForChild
  • Players.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 นาที

  1. ทำ LocalScript ทาสีพื้นเฉพาะ Player1 แล้วทดสอบแบบ 2 คน ดูว่าแต่ละหน้าต่างเห็นอะไร
  2. สร้าง ModuleScript Config แล้วแก้ FadePad จาก EP2 ให้อ่าน Config.GONE_SECONDS แทนเลข 3
  3. ลองย้าย LocalScript ไปไว้ใน Workspace แล้วทดสอบ จะเห็นว่ามันไม่ทำงานเลย

ข้อผิดพลาดที่เจอบ่อย

  • ทำคะแนนหรือเงินใน LocalScript ทำให้คนอื่นไม่เห็นและโกงได้
  • วาง LocalScript ใน Workspace แล้วมันเงียบ ไม่ทำงาน ไม่มี error
  • โค้ดฝั่งผู้เล่นไม่ใช้ WaitForChild แล้วเจอ error เป็นบางครั้ง (เครื่องเร็วไม่เจอ เครื่องช้าเจอ)
  • ใช้ Players.LocalPlayer ใน Script ฝั่งเซิร์ฟเวอร์ แล้วได้ nil

ตอนหน้า

พร้อมแล้วสำหรับเกมจริง EP4 จะประกอบ obby ให้ครบ: จุดเซฟที่เกิดใหม่ตรงด่านล่าสุด ป้ายบอกด่านบนกระดานคะแนน ลาวาทั้งเกมคุมด้วย Script เดียว และเส้นชัยที่นับจำนวนครั้งที่ชนะ

คำถามที่พบบ่อย

ทำไมไม่ให้ทุกอย่างอยู่บนเซิร์ฟเวอร์ไปเลย

เพราะบางอย่างต้องตอบสนองทันทีบนเครื่องผู้เล่น เช่น กดปุ่มบนหน้าจอ กล้อง เสียง และเอฟเฟกต์ ถ้าต้องส่งไปถามเซิร์ฟเวอร์ก่อนทุกครั้ง จะหน่วงจนเล่นไม่สนุก

Script ใน Workspace กับใน ServerScriptService ต่างกันไหม

ทั้งสองที่ทำงานบนเซิร์ฟเวอร์ ต่างกันตรงที่ของใน Workspace ถูกส่งไปให้เครื่องผู้เล่นด้วย ส่วน ServerScriptService ไม่ถูกส่งไป เครื่องผู้เล่นมองไม่เห็นโค้ดข้างใน จึงเหมาะเป็นที่เก็บโค้ดหลักของเกม

คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ

อ่านต่อ

#เกมกลาง

Roblox EP5 เกมลาวาขึ้น: สร้างทั้งด่านด้วยโค้ด และทดสอบกฎเกมนอก Studio

เกมที่สองของซีรีส์ เกมเป็นรอบที่ลาวาค่อย ๆ ขึ้น ใครถึงยอดหอคอยได้แต้ม ทั้งด่านสร้างจากโค้ดด้วย Rojo และทดสอบกฎว่ากระโดดถึงทุกขั้นด้วย Lune

อ่าน 13 นาที
#เกมเริ่มต้น

Roblox EP1 เริ่มทำเกมแรก: ติดตั้ง Roblox Studio และสร้างฉากแรกที่กด Play ได้

ทำเกม Roblox ตั้งแต่ศูนย์ ติดตั้ง Roblox Studio รู้จักหน้าจอ Explorer และ Properties วางพื้น ทำบันได กด Play ทดสอบ และเข้าใจว่าเกมหนึ่งเกมประกอบด้วยอะไรบ้าง

อ่าน 6 นาที
#เกมเริ่มต้น

Roblox EP4 ประกอบ obby ให้ครบเกม: จุดเซฟ กระดานด่าน ลาวาทั้งเกม และเส้นชัย

รวมทุกอย่างเป็นเกม obby ที่เล่นได้จริง จุดเซฟด้วย SpawnLocation กระดานคะแนน leaderstats ลาวาทั้งเกมคุมด้วย Script เดียวผ่าน Tag และเส้นชัยที่นับครั้งชนะ พร้อมโค้ดเต็ม

อ่าน 13 นาที