เทคนิคการควบคุมเซิร์ฟเวอร์

*เนื้อหานี้แปลโดยใช้ AI (เวอร์ชัน Beta) และอาจมีข้อผิดพลาด หากต้องการดูหน้านี้เป็นภาษาอังกฤษ ให้คลิกที่นี่

คู่มือนี้สรุปเทคนิคต่างๆ สำหรับการสร้างเกมมัลติเพลเยอร์ที่มีคุณภาพสูงและราบรื่นโดยใช้ โมเดลการควบคุมเซิร์ฟเวอร์

การสร้างอินสแตนซ์ที่พยากรณ์ (instance stitching)

การ ติดตั้งอินสแตนซ์ ช่วยให้สคริปต์ของไคลเอนต์สามารถสร้าง Instances โดยคาดการณ์ภายในการเรียกคืนของ RunService:BindToSimulation() ไคลเอนต์จะสร้าง Instance ขึ้นทันทีโดยไม่ต้องรอเซิร์ฟเวอร์รอบ‑ทริป; เมื่อลอกลอกสำเนาที่มีอำนาจของเซิร์ฟเวอร์เข้ามา อินสแตนซ์ที่สร้างขึ้นโดยไคลเอนต์และสำเนาที่มีอำนาจของเซิร์ฟเวอร์จะถูกรวมเป็นหนึ่งเดียว จากมุมมองของสคริปต์ของคุณ Instance จะมีอยู่ทันทีและสอดคล้องกับเซิร์ฟเวอร์

การติดตั้งอินสแตนซ์มีความจำเป็นในกรณีที่อินสแตนซ์ต้องมองเห็นและสามารถทำงานบนไคลเอนต์ได้โดยเร็วที่สุด ในขณะที่เซิร์ฟเวอร์จะส่งซ้ำอินสแตนซ์ที่ไคลเอนต์ต้องการ (พร้อมกับผลกระทบใด ๆ ที่พวกเขามีต่อโลก) กระบวนการนี้จะมีค่าล่าช้ารอบนึงเนื่องจากการสื่อสารกับเซิร์ฟเวอร์ ตัวอย่างเช่นการยิงอาวุธจรวดและสร้างข้อจำกัดทางฟิสิกส์ — โดยไม่ต้องติดตั้ง อินสแตนซ์ที่สร้างขึ้นจะปรากฏให้ไคลเอนต์เห็นจากระยะไกล หรืออาจเกิดการสั่นเมื่อข้อจำกัดใหม่ถูกส่งซ้ำไปยังพวกเขา

พฤติกรรมทางเทคนิค

การติดตั้งอินสแตนซ์ทำงานโดยการสร้าง GUID ที่เหมือนกันที่ทั้งไคลเอนต์และเซิร์ฟเวอร์ GUID จะถูกดึงมาจากข้อมูลสี่ชิ้น: ประเภทของ Instance ที่ถูกสร้าง เอกลักษณ์ของแหล่งที่มา (ดูด้านล่าง) เฟรมจำลองปัจจุบัน และเคาน์เตอร์การเรียกต่อสคริปต์ต่อหนึ่งเฟรมที่รีเซ็ตทุกเฟรม

  • สำหรับ Instance.new() — แหล่งมาจากสคริปต์นั้นเอง (สองสคริปต์ที่มีข้อความเดียวกันจะถือว่าแตกต่างกัน)
  • สำหรับ Instance.fromExisting() — แหล่งที่มาคือ Instance ที่คุณเรียก Instance.fromExisting() บน
  • สำหรับ Instance:Clone() — อินสแตนซ์ที่ถูกคัดลอกแต่ละอันจะใช้ GUID ของอินสแตนซ์ต้นแบบเป็นต้นแบบในการสร้าง

หากไคลเอนต์และเซิร์ฟเวอร์เห็นด้วยในข้อมูลนำเข้า มันจะสร้าง GUID ที่ตรงกันและการติดตั้งจะสำเร็จ

การใช้งาน

ในการใช้การติดตั้งอินสแตนซ์ ให้เรียกใช้ Instance.new(), Instance:Clone() หรือ Instance.fromExisting() ภายในการเรียกคืน BindToSimulation() จาก ModuleScript ที่เรียกใช้ทั้งบนไคลเอนต์ และ เซิร์ฟเวอร์ ก็ไม่มีอะไรที่คุณต้องทำเพิ่มเติมอีกแล้ว; ระบบจะจัดการกับการตั้งค่า GUID และการปรับให้เข้ากันโดยอัตโนมัติ

คุณสามารถตั้งค่าคุณสมบัติที่ไม่ใช่ สิทธิ์การจำลอง ได้อย่างอิสระ เช่น Name, Size, หรือ Parent บนอินสแตนซ์ก่อนที่มันจะถูกรวมเป็นลำดับใน DataModel

การจำลอง (ModuleScript) - สร้างอินสแตนซ์ใน Callback ของ BindToSimulation()

local RunService = game:GetService("RunService")
local Simulation = {}
Simulation.Initialize = function()
RunService:BindToSimulation(function(deltaTime)
local part = Instance.new("Part")
part.Name = "PredictedPart"
part.Size = Vector3.new(2, 2, 2)
part.Parent = workspace -- ส่วนนี้อยู่ใน DataModel; การเปลี่ยนแปลงที่ไม่ใช่สิทธิ์การจำลองจะผิดพลาดหลังจากนี้
-- ส่วนนี้จะมีอยู่ทันทีบนไคลเอนต์และจะต้องปรับให้เข้ากับเซิร์ฟเวอร์
end)
end
return Simulation

Instance:Clone() และ Instance.fromExisting() ติดตั้งได้ถูกต้องเมื่ออินสแตนซ์ต้นแบบถูกส่งซ้ำไปยังทั้งไคลเอนต์และเซิร์ฟเวอร์; ทั้งสองด้านทำการคัดลอกจาก GUID ต้นแบบที่ตรงกันและสร้าง GUID ที่คาดการณ์ตรงกัน

การจำลอง (ModuleScript) - คัดลอกอินสแตนซ์ใน Callback ของ BindToSimulation()

local RunService = game:GetService("RunService")
local Simulation = {}
local sourceTemplate -- เป็นอินสแตนซ์ที่ถูกส่งซ้ำ
Simulation.Initialize = function()
RunService:BindToSimulation(function(deltaTime)
local cloned = sourceTemplate:Clone()
cloned.Parent = workspace
-- โครงสร้างที่ถูกคัดลอกถูกรวมกับสำเนาที่มีอำนาจของเซิร์ฟเวอร์แล้ว
end)
end
return Simulation

การทำให้ตำแหน่งราบรื่น

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

  1. ทำให้วัตถุที่ จำลอง มองไม่เห็น
  2. สร้างวัตถุ เรนเดอร์ เป็นโคลนที่ไม่มีมวล ไม่มีการชน ฯลฯ เพื่อติดตามสิ่งของที่จำลอง
  3. แนบสคริปต์ไปกับวัตถุเรนเดอร์ที่ติดตามตำแหน่งของวัตถุที่จำลองอย่างราบรื่น การแยกส่วนระหว่างการเรนเดอร์และการจำลองช่วยให้คุณเปลี่ยนตำแหน่งของวัตถุเรนเดอร์เพื่อสร้างประสบการณ์ที่ราบรื่นในทางสายตา

ในตัวอย่าง Script ด้านล่าง วัตถุที่เลือกไว้ (พ่อแม่) จะติดตามวัตถุที่จำลองอย่างราบรื่น วัตถุที่เลือกไว้จะอยู่ "ด้านหลัง" วัตถุที่จำลองอยู่เล็กน้อยซึ่งเป็นปกติแต่ในบางกรณีอาจไม่ต้องการ

ติดตามตำแหน่ง BasePart อย่างราบรื่นด้วย Renderer Part

local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local Workspace = game:GetService("Workspace")
-- วัตถุที่จะติดตามอย่างราบรื่น
local smoothTarget:BasePart = Workspace.SimulatedPart
-- วัตถุที่เป็นภาพที่จะถูกทำให้ราบรื่น
local renderer:BasePart = script.Parent
-- เวลาในการทำให้ราบรื่น; ค่าน้อยหมายถึงรวดเร็วกว่า
local smoothTime = 0.07
-- เก็บข้อมูลที่จำเป็นในการคำนวณตำแหน่งราบรื่น
local smoothVelocity = Vector3.new()
-- ปิดฟิสิกส์ของวัตถุเรนเดอร์
renderer.Massless = true
renderer.Anchored = true
renderer.CanCollide = false
RunService.RenderStepped:Connect(function(deltaTime: number)
-- ติดตามวัตถุเป้าหมายอย่างราบรื่น
local smoothPosition, smoothVelocity = TweenService:SmoothDamp(
renderer.Position,
smoothTarget.Position,
smoothVelocity,
smoothTime,
math.huge,
deltaTime)
renderer.Position = smoothPosition
end)

ตัวอย่างเกม Soccer ใช้เทคนิคนี้ในรูปแบบที่มีการปิดและเปิดตำแหน่งอย่างชาญฉลาดสำหรับลูกฟุตบอล โดยเฉพาะ ลูกฟุตบอลจะทำให้ตำแหน่งราบรื่นก็ต่อเมื่อลูกบอลที่จำลองได้ "กระโดด" ไปไกลกว่าที่เรนเดอร์ โดยวิธีนี้จะให้ประโยชน์ที่ดีที่สุด โดยลูกฟุตบอลไม่มีความล่าช้าทางสายตาภายใต้สภาวะปกติ และเกมจะทำให้ตำแหน่งของมันราบรื่นหลังจากที่ลูกบอลที่จำลองกระโดดไปที่ตำแหน่งใหม่ ซึ่งมักจะเกิดจากอาร์ติแฟคทางเครือข่ายหรือการเปลี่ยนแปลงทางเซิร์ฟเวอร์

การเขียนโค้ดอนิเมชัน

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

การสะท้อนตรรกะอนิเมชัน

เช่นเดียวกับตรรกะหลักของเกมเพลย์ ตรรกะสำหรับการควบคุมอนิเมชันจะต้องตรงกันระหว่างเซิร์ฟเวอร์และไคลเอนต์หรืออาจมีการคาดการณ์ที่ผิดพลาดและพฤติกรรมที่สั่นสะเทือน ดูที่ การซิงค์การจำลอง สำหรับรูปแบบที่เชื่อมฟังก์ชันผ่าน RunService:BindToSimulation() ใน ModuleScript ที่ถูกสร้างขึ้นในทั้งไคลเอนต์และเซิร์ฟเวอร์

หลีกเลี่ยงการเก็บข้อมูลอนิเมชัน

รูปแบบทั่วไปในสคริปต์ที่ไม่ใช่การควบคุมเซิร์ฟเวอร์คือการเก็บ AnimationTrack ไว้ที่เวลาโหลดและใช้งานซ้ำตลอดไป รูปแบบนี้จะล้มเหลวในเกมที่มีการควบคุมเซิร์ฟเวอร์เมื่อเซิร์ฟเวอร์แก้ไขการคาดการณ์ที่ผิดพลาดและไคลเอนต์ย้อนกลับ/เล่นใหม่การจำลองของมันด้วยข้อมูลที่ถูกต้อง หากสคริปต์ของคุณยังเก็บอ้างอิงถึงแทร็กที่หยุดหรือถูกแทนที่ การเรียกใช้เช่น AdjustWeight() หรือ AdjustSpeed() จะทำงานกับแทร็กที่ไม่มีการแสดงผลแล้ว

เก็บแทร็กบนไคลเอนต์ (ไม่น่าเชื่อถือ)

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoid = character:WaitForChild("Humanoid")
local animator = humanoid:WaitForChild("Animator")
-- เก็บแทร็กอนิเมชัน
local tracks = {}
tracks["WalkForward"] = animator:LoadAnimation(walkForwardAnim)
RunService:BindToSimulation(function(dt: number)
tracks["WalkForward"]:AdjustSpeed(1 + math.cos(time()))
end)

แทนที่จะถือว่าแทร็กวัตถุ ให้เก็บ ไอดีอนิเมชัน (หรือ Animation อินสแตนซ์) และถาม Animator สำหรับแทร็กที่มีชีวิตอยู่เมื่อใดก็ตามที่คุณต้องการมีส่วนร่วมด้วย Two API ที่สามารถใช้ได้สำหรับสิ่งนี้:

  • Animator:GetTrackByAnimationId() — คืนแทร็กที่ใช้งานอยู่ในปัจจุบันสำหรับไอดีอนิเมชันเฉพาะ หรือ nil หากไม่มีอนิเมชันที่ใช้งานอยู่ด้วยไอดีนี้ ใช้เมื่อคุณรู้ว่าอนิเมชันเฉพาะที่คุณต้องการ
  • Animator:GetPlayingAnimationTracks() — คืนแทร็กทั้งหมดที่ใช้งานอยู่ (กำลังเล่น, กำลัง fade out หรือหยุดพัก) ใช้เมื่อคุณต้องการวนซ้ำไปยังทุกอย่างที่ใช้งานอยู่ (ตัวอย่างเช่นเพื่อหยุดการเล่นอนิเมชันทั้งหมดหรือค้นหาแทร็กตามเกณฑ์บางอย่าง)

ModuleScript ชื่อ CustomAnimate ใน ReplicatedStorage:

CustomAnimate

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local CustomAnimate = {}
-- เก็บอ้างอิงอนิเมชัน (ไม่ใช่แทร็กที่โหลดแล้ว)
local animations = {
WalkForward = ReplicatedStorage.Animations.WalkForward,
}
local function getOrLoadTrack(animator: Animator, animation: Animation): AnimationTrack
local track = animator:GetTrackByAnimationId(animation.AnimationId)
if not track then
track = animator:LoadAnimation(animation)
end
return track
end
CustomAnimate.SyncAnimations = function(character)
local humanoid = character:WaitForChild("Humanoid")
local animator = humanoid:WaitForChild("Animator")
RunService:BindToSimulation(function(dt: number)
local walkTrack = getOrLoadTrack(animator, animations.WalkForward)
if not walkTrack.isPlaying then
walkTrack.Looped = true
walkTrack.Priority = Enum.AnimationPriority.Core
walkTrack:Play()
end
walkTrack:AdjustSpeed(1 + math.cos(time()))
end)
end
return CustomAnimate

การเล่นเสียงและเอฟเฟกต์ภาพ

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

กลยุทธ์ที่ดีสำหรับการเรนเดอร์จำลองที่คาดการณ์ได้คือการซิงค์รูปแบบของสถานะ machine ภายในวงจรการจำลองและทำการเรนเดอร์การเปลี่ยนแปลงในฟังก์ชั่นการเรนเดอร์ ตัวอย่างด้านล่างจำลองระเบิดมือด้วยรูปแบบสถานะแมชชีน:

เครื่องจักรสถานะง่ายๆ для ติดตามระเบิดมือ (ModuleScript)

local Workspace = game:GetService("Workspace")
local module = {}
module.GrenadeStates = {
Idle = 0,
Lit = 1,
Exploded = 2,
Defused = 3,
}
module.GrenadeExplodeTime = 3.0
module.Initialize = function(grenade)
RunService:BindToSimulation(function(deltaTime)
-- เริ่มการตั้งค่าความว่างเปล่าของระเบิดมือ
local grenadeState = grenade:GetAttribute("State")
if grenadeState == nil then
grenadeState = module.GrenadeStates.Idle
grenade:SetAttribute("State", grenadeState)
grenade:SetAttribute("Timer", 0.0)
end
-- เพิ่มเวลาของระเบิดมือ
local timer = grenade:GetAttribute("Timer")
timer = timer + deltaTime
grenade:SetAttribute("Timer", timer)
-- ระเบิดระเบิดมือที่ติดไฟ
if grenadeState == module.GrenadeStates.Lit then
if timer >= module.GrenadeExplodeTime then
grenadeState = module.GrenadeStates.Exploded
grenade:SetAttribute("State", grenadeState)
grenade:SetAttribute("Timer", 0.0)
end
end
end)
end
return module

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

เรนเดอร์อนุภาคและเสียงตามสถานะระเบิดมือที่ซิงค์กัน

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Simulation = require(ReplicatedStorage.Simulation)
local grenade = script.Parent
local previousGrenadeState = nil
-- ไฮไลท์อินสแตนซ์เพื่อบ่งบอกสถานะระเบิดมือ
local highlight = Instance.new("Highlight")
highlight.Parent = grenade
highlight.FillTransparency = 1
highlight.OutlineTransparency = 1
highlight.DepthMode = Enum.HighlightDepthMode.Occluded
RunService.RenderStepped:Connect(function(deltaTime: number)
local grenadeState = grenade:GetAttribute("State")
local grenadeTimer = grenade:GetAttribute("Timer")
-- ปล่อยอนุภาคที่ติดไฟออกมา
grenade.LitEmitter.Enabled = grenadeState == Simulation.GrenadeStates.Lit
-- เล่นเอฟเฟกต์ระเบิดถ้าระเบิดมือเพิ่งระเบิดขึ้น
if previousGrenadeState ~= grenadeState then
if grenadeState == Simulation.GrenadeStates.Exploded and grenadeTimer < 0.2 then
grenade.ExplosionEmitter:Emit(100)
grenade.ExplosionSound:Play()
end
previousGrenadeState = grenadeState
end
-- เปลี่ยนสีของไฮไลท์ระเบิดมือขึ้นอยู่กับสถานะและเวลา
if grenadeState == Simulation.GrenadeStates.Lit then
highlight.FillColor = Color3.fromRGB(255, 0, 0)
highlight.FillTransparency = 1 - (grenadeTimer / Simulation.GrenadeExplodeTime)
elseif grenadeState == Simulation.GrenadeStates.Idle then
highlight.FillTransparency = 1
elseif grenadeState == Simulation.GrenadeStates.Exploded then
highlight.FillTransparency = 1
elseif grenadeState == Simulation.GrenadeStates.Defused then
highlight.FillColor = Color3.fromRGB(0, 255, 125)
highlight.FillTransparency = 0.5
end
end)

การออกแบบตาม ความล่าช้า ของเครือข่าย

กลไกการเล่นบางอย่างทำงานได้ดีกว่าในการเล่นมัลติเพลเยอร์แบบเครือข่ายPlayers จะมีความล่าช้าระหว่างการกระทำของผู้เล่นคนอื่นและเมื่อพวกเขาได้รับการป้อนข้อมูลจากผู้เล่นนั้น วิธีที่ดีที่สุดในการสร้างเกมมัลติเพลเยอร์ที่ราบรื่นมากคือการออกแบบเกมของคุณโดยคำนึงถึงข้อจำกัดเหล่านี้

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

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

การคาดการณ์การป้อนข้อมูลของผู้เล่นอื่น

ตามค่าเริ่มต้น Roblox จะไม่ส่งต่อการป้อนข้อมูลจากแต่ละไคลเอนต์ไปยังไคลเอนต์อื่น ๆ ขึ้นอยู่กับการออกแบบของเกมของคุณว่าจะถูกต้องหรือไม่:

  • สำหรับการเคลื่อนที่ของมนุษย์ขั้นพื้นฐาน พฤติกรรมเริ่มต้นหมายความว่าการเคลื่อนที่ของตัวละครผู้เล่นคนอื่นจะไม่ได้รับการคาดการณ์จากสถานะเซิร์ฟเวอร์ที่มีอำนาจ และผลลัพธ์คือตัวละครผู้เล่นอื่นจะไม่พยากรณ์ผิด แต่จะเรนเดอร์ในอดีตเล็กน้อย
  • ในเกมแข่งรถ ในทางตรงข้าม พฤติกรรมเริ่มต้นหมายความว่าไคลเอนต์จะไม่ทราบว่าผู้เล่นคนอื่น ๆ กดปุ่มหรือป้อนข้อมูลอื่น ๆ ทำให้รถอื่น ๆ ดูเหมือนอยู่หลังผู้เล่นท้องถิ่นแม้ว่าพวกเขาจะอยู่ข้างหน้า เพื่อบรรเทาสิ่งนี้ คุณสามารถเก็บข้อมูลการป้อนข้อมูลของผู้เล่นใน คุณลักษณะ ในเซิร์ฟเวอร์และดำเนินการกับคุณลักษณะที่ซิงค์ไปยังด้านไคลเอนต์โดยใช้ RunService:BindToSimulation() ตามที่แสดงในตัวอย่างโค้ดด้านล่างและแม่แบบ Racing โดยวิธีนี้คุณจะใช้คุณลักษณะต่างๆ เพื่อป้อนข้อมูลในการจำลองของคุณเพื่อให้การป้อนข้อมูลจากผู้เล่นถูกจำลองทุกอย่าง
การเก็บข้อมูลผู้เล่นในคุณลักษณะ (ModuleScript)

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local module = {}
module.storePlayerInput = function(player:Player, humanoidRootPart:BasePart)
local inputContext:InputContext = player.PlayerGui.InputContext
local throttle = inputContext.DefuseAction:GetState()
humanoidRootPart:SetAttribute("Throttle", throttle)
-- เขียนข้อมูลการป้อนข้อมูลอื่น ๆ ลงในคุณลักษณะ...
end
module.Initialize = function()
RunService:BindToSimulation(function(deltaTime)
if RunService:IsServer() then
-- ส่งต่อการป้อนข้อมูลจากเซิร์ฟเวอร์ไปยังไคลเอนต์ทั้งหมด
for _, player in Players:GetPlayers() do
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local inputContext:InputContext = player.PlayerGui.InputContext
module.storePlayerInput(player, humanoidRootPart)
end
else
-- เขียนการป้อนข้อมูลของผู้เล่นท้องถิ่นเป็นคุณลักษณะ
local player = Players.LocalPlayer
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local inputContext:InputContext = player.PlayerGui.InputContext
module.storePlayerInput(player, humanoidRootPart)
end
-- ใช้คุณลักษณะต่างๆ เป็นข้อมูลนำเข้าของเกม
for _, player in Players:GetPlayers() do
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local throttle = humanoidRootPart:GetAttribute("Throttle")
if throttle then
-- นำความเร็วไปยังยานพาหนะของผู้เล่น
end
end
end)
end)
return module

การดีบัก

มีเครื่องมือใหม่ ๆ และเทคนิคที่คุณสามารถใช้เพื่อตรวจสอบเกมที่มีการควบคุมโดยเซิร์ฟเวอร์

ตัวแสดงภาพการควบคุมเซิร์ฟเวอร์

การกด CtrlShiftF6 (Windows) หรือ ShiftF6 (Mac) จะเปิด ตัวแสดงภาพการควบคุมเซิร์ฟเวอร์ ของ Studio ซึ่งจะแสดงข้อมูลสำคัญหลายประการ:

รายละเอียดคำอธิบาย
อัตราความสำเร็จในการคาดการณ์อินสแตนซ์เปอร์เซ็นต์ของอินสแตนซ์ที่คาดการณ์ถูกต้องในช่วงแปดวินาทีที่ผ่านมา
อัตราการยอมรับของการป้อนข้อมูลเปอร์เซ็นต์ของการป้อนข้อมูลของผู้เล่นทั้งหมดที่มาถึงทันเวลาในเซิร์ฟเวอร์ การป้อนข้อมูลที่นานเกินไปจะลดตัวเลขนี้
ช่วงเวลาระหว่างไคลเอนต์และเซิร์ฟเวอร์จำนวนเฟรมระหว่างไคลเอนต์และเซิร์ฟเวอร์ รวมถึงเวลาที่เข้าร่วมของไคลเอนต์เสถียรภาพของตัวเลขนี้แสดงถึงเสถียรภาพของการเชื่อมต่อของคุณกับเซิร์ฟเวอร์
RCC เร็วอัตราเฟรมของการจำลองในเซิร์ฟเวอร์ หากตัวเลขนี้ลดต่ำกว่า 59 เซิร์ฟเวอร์จะไม่สามารถรักษาจังหวะของการจำลองได้และเกมจะตกต่ำลงในคุณภาพ
จำนวนอินสแตนซ์ที่คาดการณ์ได้จำนวนอินสแตนซ์ที่ไคลเอนต์ของคุณ đang คาดการณ์
การนับเหตุผลในการยกเลิกการป้อนข้อมูล

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

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

รัศมีการจำลอง

เมื่อใช้การคาดการณ์โดยอัตโนมัติ (Enum.PredictionMode.Automatic) คุณสามารถมองเห็นรัศมีการคาดการณ์ที่อยู่รอบตัวละครผู้เล่นของคุณได้โดยเปิดใช้งาน Are Regions Enabled ในการตั้งค่าของ Studio (AltS บน Windows; S บน Mac) กระบอกสีเขียวจะแสดงช่วงรอบตัวละครของคุณที่อินสแตนซ์ถูกคาดการณ์ และรัศมีจะเปลี่ยนขนาดขึ้นลงตามลักษณะการแสดงผลของอุปกรณ์

รัศมีการจำลองรอบตัวละครผู้เล่นที่ทำงานด้วยเซิร์ฟเวอร์ควบคุม
©2026 Roblox Corporation. Roblox, โลโก้ Roblox และ Powering Imagination เป็นส่วนหนึ่งของเครื่องหมายการค้าที่จดทะเบียน และไม่ได้จดทะเบียนของเราในสหรัฐฯ และประเทศอื่นๆ