Roblox Server Authority: กันโกงด้วย client prediction และ rollback
Roblox เปิด Server Authority ให้ทุกเกมแล้ว เซิร์ฟเวอร์เป็นเจ้าของสถานะเกม เครื่องผู้เล่นทายผลล่วงหน้าแล้วย้อนแก้เมื่อทายผิด วิธีเปิด วิธีเขียนโค้ด และข้อจำกัด
- Server Authority ให้เซิร์ฟเวอร์เป็นเจ้าของสถานะเกม เครื่องผู้เล่นส่งแค่ input จึงกัน speedhack และ flyhack ได้ตั้งแต่โครงสร้าง
- เครื่องผู้เล่นทายผลล่วงหน้าหลายเฟรม เมื่อเซิร์ฟเวอร์ตอบไม่ตรง จะย้อนกลับไปสถานะจริงแล้วจำลองเฟรมที่เหลือใหม่
- เปิดด้วย Workspace.AuthorityMode = Server แล้วย้าย logic หลักไป RunService:BindToSimulation และอ่านปุ่มผ่าน InputAction
- ใช้ time() ไม่ใช่ tick() หรือ os.clock() เพราะต้องย้อนกลับได้ตอนจำลองใหม่
ถ้าเกม Roblox ของคุณให้เครื่องผู้เล่นคุมตัวละครตัวเอง โปรแกรมโกงก็คุมได้เหมือนกัน วิ่งเร็วเกิน บินได้ ทะลุกำแพง เพราะเซิร์ฟเวอร์เชื่อตำแหน่งที่เครื่องผู้เล่นส่งมา Server Authority ที่ Roblox เปิดให้ทุกเกมเมื่อ 9 กรกฎาคม 2026 กลับด้านความเชื่อนี้ เซิร์ฟเวอร์เป็นเจ้าของสถานะ เครื่องผู้เล่นส่งแค่ input และใช้ client prediction ซ่อนความหน่วง บทความนี้อธิบายกลไก วิธีเปิด วิธีเขียนโค้ดให้เข้ากับมัน และกับดัก
ข้อมูลตรวจกับเอกสาร Server Authority บน Creator Hub, ประกาศ Full Release บน DevForum (9 ก.ค. 2026) และประกาศ BindToSimulation (29 เม.ย. 2026) ณ ตุลาคม 2026
ถ้ายังไม่คุ้นว่าโค้ดฝั่งไหนรันที่ไหน อ่าน Roblox EP3 เซิร์ฟเวอร์กับเครื่องผู้เล่น ก่อน บทนี้ต่อยอดจากตรงนั้น
เดิมเป็นยังไง และทำไมโกงได้
แบบเดิมของ Roblox ตัวละครผู้เล่นมี network ownership อยู่ที่เครื่องผู้เล่น เครื่องผู้เล่นคำนวณฟิสิกส์ของตัวเองแล้วส่งผลขึ้นเซิร์ฟเวอร์ ข้อดีคือกดแล้วตัวละครขยับทันที ข้อเสียคือเซิร์ฟเวอร์ต้อง “เชื่อ” ผลนั้น ทางกันโกงแบบเก่าจึงเป็นการเขียนโค้ดตรวจย้อนหลัง เช่น วัดระยะที่ขยับต่อวินาทีแล้วดึงกลับ ซึ่งเขียนยากและพลาดง่าย
Server Authority เปลี่ยนเป็น เซิร์ฟเวอร์คือความจริงเดียว เครื่องผู้เล่นส่งแค่ input (กดเดินหน้า กดกระโดด) เซิร์ฟเวอร์จำลองผลเอง โปรแกรมโกงที่แก้ความเร็วบนเครื่องตัวเองจึงไม่มีผลกับสถานะจริง
Prediction, misprediction, rollback
ถ้ารอเซิร์ฟเวอร์ตอบทุกครั้ง ผู้เล่นปิง 100 ms จะรู้สึกหน่วงทุกปุ่ม Roblox จึงให้เครื่องผู้เล่นทายผลล่วงหน้า
- Prediction เครื่องผู้เล่นจำลองล่วงหน้าไปหลายเฟรมจากสถานะล่าสุดที่เซิร์ฟเวอร์ยืนยัน ใช้ input ของตัวเอง จำนวนเฟรมขึ้นกับความหน่วง
- เทียบผล เมื่อสถานะจริงของเฟรมหนึ่งมาถึง เครื่องผู้เล่นเทียบกับที่ตัวเองทายไว้ของเฟรมเดียวกัน
- Misprediction ถ้าไม่ตรง (ปิงเปลี่ยน ผู้เล่นอื่นชน หรือมี logic ที่รันบนเซิร์ฟเวอร์อย่างเดียว)
- Rollback + resimulation ย้อนกลับไปใช้สถานะของเซิร์ฟเวอร์ แล้วจำลองเฟรมที่เหลือใหม่จนถึงเฟรมปัจจุบัน โดยใส่ input ของตัวเองซ้ำ
ตัวอย่างในเอกสาร: เครื่องผู้เล่นนำอยู่สองเฟรม ทายเฟรม 3 ผิด ก็จำลองเฟรม 4 ถึง 6 ใหม่ก่อนไปเฟรม 7 ผู้เล่นอาจเห็นตัวละครกระตุกไปตำแหน่งที่ถูก
เครื่องผู้เล่นทายได้ แต่ไม่มีสิทธิ์ตัดสิน ทุกครั้งที่ทายผิด ความจริงจากเซิร์ฟเวอร์ชนะ
ตัวอย่างคำนวณ: rollback กินเวลาเท่าไร
ตัวเลขเฟรมที่นำและเวลาต่อ step ด้านล่างเป็นการประมาณเพื่อให้เห็นภาพ เอกสารไม่ได้ระบุสูตร
BindToSimulationที่Enum.StepFrequency.Hz60→ หนึ่ง step = 1000 ÷ 60 ≈ 16.7 ms- ผู้เล่นปิง (round trip) 100 ms → เครื่องผู้เล่นนำราว 100 ÷ 16.7 ≈ 6 เฟรม
- สมมติ callback ของเราใช้ 1 ms ต่อ step
- ทายผิดหนึ่งครั้ง → จำลองใหม่ 6 step × 1 ms = 6 ms ในเฟรมเดียว จากงบ 16.7 ms
ถ้า callback ใช้ 3 ms ก็กลายเป็น 18 ms เกินงบหนึ่งเฟรมไปแล้ว ผู้เล่นเครื่องช้าเห็นเฟรมตก บทเรียนคือ ทุกอย่างใน BindToSimulation จะถูกรันซ้ำ เก็บไว้เฉพาะ logic ที่กำหนดผลเกม ของตกแต่ง (เสียง เอฟเฟกต์ แอนิเมชันประกอบ) ย้ายไป RenderStepped
เปิด Server Authority
ใน Studio เปิด Properties ของ Workspace หมวด Server Authority ตั้ง AuthorityMode = Server ค่าอื่นจะถูกตั้งตามให้เอง
| property | ค่าที่ถูกตั้ง | ผลที่ต้องรู้ |
|---|---|---|
NextGenerationReplication | Enabled | ระบบ replicate แบบใหม่ |
PlayerScriptsUseInputActionSystem | Enabled | PlayerScripts ใช้ InputAction |
SignalBehavior | Deferred | event ไม่ยิงทันที แต่ถูกเลื่อนไปจุดถัดไป |
UseFixedSimulation | Enabled | ระบบภายใน (Humanoid, animation, ControllerManager) เดินด้วยความถี่คงที่ |
StreamingEnabled | Enabled | ส่วนที่ไกลจากผู้เล่นอาจยังไม่โหลดบนเครื่อง |
แล้ว Save และ Publish ถ้าเกมเดิมไม่ได้เปิด StreamingEnabled นี่คือการเปลี่ยนที่ใหญ่สุด โค้ดที่ workspace.Map.Door ตรง ๆ บนเครื่องผู้เล่นต้องเปลี่ยนเป็นรอโหลด
UseFixedSimulation คือแนวเดียวกับ game loop แบบ fixed timestep ให้ทุกเครื่องเดินเวลาเท่ากัน ผลคำนวณจึงเทียบกันได้
เขียนโค้ดให้เข้ากับ simulation
หลักจากเอกสาร: logic หลักอยู่ใน ModuleScript ที่เริ่มทำงานทั้งฝั่งเซิร์ฟเวอร์และเครื่องผู้เล่น อ่านปุ่มผ่าน InputAction เก็บค่าของเราเองใน attribute และเขียน attribute เฉพาะใน BindToSimulation
โครงด้านล่างทำปุ่ม Boost ที่กิน stamina (สร้าง InputContext ชื่อ GameplayContext มี InputAction แบบ Bool ชื่อ Boost ไว้ใน ReplicatedStorage)
-- ServerScriptService/GiveInputContext (Script)
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
Players.PlayerAdded:Connect(function(player)
-- InputContext ต้องอยู่ใต้ Player
ReplicatedStorage.GameplayContext:Clone().Parent = player
end)
-- ReplicatedStorage/Simulation (ModuleScript)
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local MAX_STAMINA = 100
local DRAIN_PER_SEC = 40
local Simulation = {}
function Simulation.Initialize()
RunService:BindToSimulation(function(dt)
-- เซิร์ฟเวอร์คำนวณทุกคน เครื่องผู้เล่นทายเฉพาะตัวเอง (ตัวละครที่ถูก predict)
local list = if RunService:IsServer() then Players:GetPlayers() else { Players.LocalPlayer }
for _, player in list do
local character = player.Character
local context = player:FindFirstChild("GameplayContext")
if not (character and context) then continue end
local boosting = context.Boost:GetState() == true
local stamina = character:GetAttribute("Stamina") or MAX_STAMINA
if boosting and stamina > 0 then
stamina = math.max(0, stamina - DRAIN_PER_SEC * dt) -- ตรวจขอบเขตเสมอ
end
character:SetAttribute("Stamina", stamina)
end
end, Enum.StepFrequency.Hz60)
end
return Simulation
เรียก require(ReplicatedStorage.Simulation).Initialize() จากทั้ง Script ใน ServerScriptService และ LocalScript ใน StarterPlayerScripts เครื่องผู้เล่นได้ผลทันที เซิร์ฟเวอร์ได้ค่าจริง ถ้า attribute สองฝั่งไม่ตรง ระบบ rollback ให้เอง
นี่คือโครงตามรูปแบบในเอกสาร ก่อนใช้จริงเทียบกับ template Racing, Soccer หรือ Laser Tag ที่ Roblox เตรียมไว้
กฎที่ต้องจำ
- เวลา: ใช้
time()ซึ่งเป็นเวลา simulation แบบ fixed step ย้อนและเล่นซ้ำได้tick(),os.time(),os.clock()ไม่ sync และไม่ย้อน - Input: ทุก input ที่มีผลกับ simulation ใช้
InputActionห้ามใช้UserInputService.InputBeganและตรวจค่าก่อนใช้ - Attribute: replicate เฉพาะ 64 ตัวแรกต่อ instance ชื่อไม่เกิน 50 ตัวอักษร ค่าสตริงไม่เกิน 50 ตัวอักษร
- API ใน simulation: เรียกได้เฉพาะ property/method ที่ติดป้าย “Simulation Access”
task.spawn()จาก callback ยังติดข้อจำกัดนี้task.defer()หลุดออกไป - กล้อง: Camera InputAction เลิก sync แล้ว ใช้
Player:GetCameraState()ที่คืนCFrame,FieldOfView,ViewportSize
ข้อผิดพลาดที่เจอบ่อย
- เล่นเสียงหรือเอฟเฟกต์ใน BindToSimulation โดน rollback แล้วเล่นซ้ำ ย้ายไป
RenderStepped - ใช้
os.clock()จับเวลาคูลดาวน์ ตอนจำลองใหม่เวลาไม่ย้อน คูลดาวน์เพี้ยน ใช้time() - คิดว่า RemoteEvent อยู่ใน timeline เดียวกัน ลำดับของ RemoteEvent เทียบกับ property/attribute ไม่รับประกัน และมีรายงานในกระทู้ว่าคลาดราว 40-50 ms ใช้กับเหตุการณ์แยกครั้งเท่านั้น
- เปิดแล้ว publish เลย ทดสอบด้วย Network Simulation ใน Studio ตั้งปิงสูงและ packet loss ก่อน
- ลืมข้อจำกัดช่วงแรก ประกาศ Full Release ระบุว่า Animator เล่นพร้อมกันได้ 8 track และการเปลี่ยน Tool ระหว่าง misprediction อาจลบ Weld ของ Tool บนเครื่องผู้เล่น ตรวจรายการ known limitations ล่าสุดก่อนเปิด
แหล่งอ้างอิง
คำถามที่พบบ่อย
เปิด Server Authority แล้วเกมเก่าพังไหม
อาจพัง เพราะการเปิดจะบังคับ StreamingEnabled, SignalBehavior แบบ Deferred และ input แบบ InputAction ไปพร้อมกัน โค้ดที่พึ่ง UserInputService หรือคิดว่าทุกส่วนของแผนที่โหลดอยู่ตลอด ต้องแก้ ลองใน place สำเนาก่อนเสมอ
ยังต้องตรวจ RemoteEvent เองไหม
ต้อง Server Authority คุมเฉพาะสิ่งที่อยู่ใน simulation RemoteEvent สำหรับการกระทำแยกครั้ง เช่น ซื้อของหรือกดปุ่ม ยังต้องตรวจสิทธิ์และค่าที่ส่งมาบนเซิร์ฟเวอร์เหมือนเดิม และไม่ได้เรียงลำดับร่วมกับ timeline ของ simulation
เซิร์ฟเวอร์จะหนักขึ้นไหม
Roblox บอกตรง ๆ ว่าการใช้ทรัพยากรเซิร์ฟเวอร์จะเพิ่มขึ้น มากน้อยขึ้นกับว่าเกมใกล้เพดานอยู่แล้วแค่ไหน วัดใน Studio ก่อนเปิดจริง
คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ