Teknik Otoritas Server

*Konten ini diterjemahkan menggunakan AI (Beta) dan mungkin mengandung kesalahan. Untuk melihat halaman ini dalam bahasa Inggris, klik di sini.

Panduan ini menjelaskan berbagai teknik untuk menciptakan permainan multipemain berkualitas tinggi dan lancar menggunakan model otoritas server.

Pembuatan instance prediktif (penyambungan instance)

Penyambungan instance memungkinkan skrip klien secara prediktif membuat Instances di dalam callback RunService:BindToSimulation(). Klien membuat Instance segera tanpa menunggu perjalanan pulang pergi ke server; ketika salinan otoritatif server tiba, instance yang dibuat klien dan salinan otoritatif server digabungkan menjadi satu. Dari sudut pandang skrip Anda, Instance ada segera dan konsisten dengan server.

Penyambungan instance berguna dalam kasus di mana sebuah instance harus terlihat dan aktif di klien sesegera mungkin. Sementara server pada akhirnya akan mereplikasi setiap instance yang dibutuhkan klien (bersama dengan efek apa pun yang mereka miliki di dunia), proses ini mengakibatkan setidaknya satu perjalanan pulang pergi latensi karena komunikasi server. Contohnya termasuk menembakkan peluncur roket dan membuat batasan fisika — tanpa penyambungan, klien akan melihat roket muncul jauh dari mereka, atau beberapa goyangan ketika batasan baru direplikasi kepada mereka.

Perilaku teknis

Penyambungan instance bekerja dengan menghasilkan GUID deterministik yang sama di klien dan server. GUID dihasilkan dari empat input: tipe Instance yang sedang dibuat, identitas sumber (lihat di bawah), frame simulasi saat ini, dan penghitung panggilan per-skrip yang direset setiap frame.

Jika klien dan server sepakat tentang input, mereka menghasilkan GUID yang cocok dan penyambungan berhasil.

Implementasi

Untuk memanfaatkan penyambungan instance, panggil Instance.new(), Instance:Clone(), atau Instance.fromExisting() di dalam callback BindToSimulation() dari ModuleScript yang dibutuhkan di klien dan server. Tidak ada yang perlu Anda lakukan; sistem menangani penugasan dan rekonsiliasi GUID secara otomatis.

Anda dapat dengan bebas mengatur properti non-akses simulasi seperti Name, Size, atau Parent pada sebuah instance sebelum diparent ke dalam DataModel.

Simulasi (ModuleScript) - Buat Instance dalam 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 -- Bagian sekarang ada di DataModel; setiap perubahan non-simulasi access akan menghasilkan kesalahan setelah ini
-- Bagian ada segera di klien dan akan direkonsiliasi dengan server
end)
end
return Simulation

Instance:Clone() dan Instance.fromExisting() menyambung dengan benar ketika instance sumber telah direplikasi ke klien dan server; kedua sisi mengkloning dari GUID sumber yang cocok dan menghasilkan GUID prediksi yang cocok.

Simulasi (ModuleScript) - Kloning Instance dalam Callback BindToSimulation()

local RunService = game:GetService("RunService")
local Simulation = {}
local sourceTemplate -- instance yang direplikasi
Simulation.Initialize = function()
RunService:BindToSimulation(function(deltaTime)
local cloned = sourceTemplate:Clone()
cloned.Parent = workspace
-- Hierarki yang dikloning disambung dengan salinan otoritatif server
end)
end
return Simulation

Penghalusan posisi

Anda dapat secara visual menghaluskan posisi objek sinkronisasi yang salah diprediksi dengan merender objek yang berbeda dari yang sedang disimulasikan.

  1. Buat objek simulasi menjadi tidak terlihat.
  2. Buat objek renderer sebagai klon visual yang tidak memiliki massa, tidak dapat bertabrakan, hanya visual untuk melacak objek simulasi.
  3. Lampirkan skrip ke objek renderer yang secara halus melacak posisi objek simulasi yang tidak terlihat. Pemisahan antara rendering dan simulasi memungkinkan Anda mengubah posisi objek renderer untuk menciptakan pengalaman yang secara visual halus.

Dalam contoh Script berikut, objek yang dirender (parent) secara halus melacak objek yang disimulasikan. Objek yang dirender selalu sedikit "tertinggal" dari objek yang disimulasikan, yang biasanya baik tetapi mungkin tidak diinginkan dalam situasi tertentu.

Melacak Posisi BasePart Secara Halus dengan Bagian Renderer

local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local Workspace = game:GetService("Workspace")
-- Objek untuk dilacak secara halus
local smoothTarget:BasePart = Workspace.SimulatedPart
-- Objek visual yang akan dibuat halus
local renderer:BasePart = script.Parent
-- Waktu untuk menghaluskan; semakin kecil berarti semakin cepat
local smoothTime = 0.07
-- Simpan data yang dibutuhkan untuk menghitung posisi halus
local smoothVelocity = Vector3.new()
-- Nonaktifkan fisika objek renderer
renderer.Massless = true
renderer.Anchored = true
renderer.CanCollide = false
RunService.RenderStepped:Connect(function(deltaTime: number)
-- Melacak objek target secara halus
local smoothPosition, smoothVelocity = TweenService:SmoothDamp(
renderer.Position,
smoothTarget.Position,
smoothVelocity,
smoothTime,
math.huge,
deltaTime)
renderer.Position = smoothPosition
end)

Game contoh Sepak Bola menggunakan variasi teknik ini untuk lebih cerdas menghidupkan dan mematikan penghalusan posisi untuk bola sepak. Secara khusus, bola sepak hanya menghaluskan posisinya ketika bola yang disimulasikan telah "melompat" cukup jauh dari bola yang dirender. Pendekatan ini memberikan yang terbaik dari kedua dunia: bola sepak tidak memiliki latensi visual dalam kondisi normal, dan permainan secara halus melakukan interpolasi posisinya hanya setelah bola yang disimulasikan secara tidak terduga melompat ke lokasi baru, yang mungkin disebabkan oleh artefak jaringan atau perubahan di sisi server.

Di bawah otoritas server, simulasi klien dapat dilakukan rollback dan disimulasikan ulang ketika server memperbaiki prediksi yang salah. Selama rollback, status animasi dihentikan, yang berarti AnimationTrack yang Anda cache di frame sebelumnya mungkin tidak lagi valid.

Logika animasi cerminan

Seperti halnya logika inti permainan, logika untuk mengontrol animasi harus sinkron antara server dan klien atau mungkin ada prediksi yang salah dan perilaku goyang. Lihat sinkronisasi simulasi untuk pola yang mengikat fungsi melalui RunService:BindToSimulation() dalam ModuleScript yang diinisialisasi di klien dan server.

Hindari caching track

Pola umum dalam skrip non-otoritas server adalah menyimpan objek AnimationTrack saat waktu muat dan menggunakannya kembali tanpa batas. Pola ini gagal dalam permainan yang memiliki otoritas server ketika server memperbaiki prediksi dan klien memutar ulang simulasi dengan data yang diperbaiki. Jika skrip Anda masih memegang referensi ke track yang dihentikan atau diganti, panggilan seperti AdjustWeight() atau AdjustSpeed() akan beroperasi pada track yang tidak lagi terlihat.

Cache Tracks di Klien (Tidak Tepercaya)

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")
-- Cache track animasi
local tracks = {}
tracks["WalkForward"] = animator:LoadAnimation(walkForwardAnim)
RunService:BindToSimulation(function(dt: number)
tracks["WalkForward"]:AdjustSpeed(1 + math.cos(time()))
end)

Alih-alih memegang objek track, simpan ID animasi (atau instance Animation) dan kueri Animator untuk track langsung kapan pun Anda perlu berinteraksi dengannya. Dua API tersedia untuk ini:

  • Animator:GetTrackByAnimationId() — Mengembalikan track yang saat ini aktif untuk ID animasi tertentu, atau nil jika tidak ada animasi aktif dengan ID tersebut. Gunakan ini ketika Anda tahu animasi spesifik apa yang Anda cari.
  • Animator:GetPlayingAnimationTracks() — Mengembalikan semua track aktif (bermain, memudar, atau dijeda). Gunakan ini ketika Anda perlu iterasi semua yang aktif (misalnya, untuk menghentikan semua animasi atau menemukan track dengan kriteria tertentu).

ModuleScript bernama CustomAnimate di dalam ReplicatedStorage:

CustomAnimate

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local CustomAnimate = {}
-- Simpan referensi animasi (bukan track yang dimuat)
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

Memainkan suara dan efek visual

Dalam simulasi yang diprediksi, dimungkinkan untuk memicu efek atau suara untuk peristiwa yang diperkirakan klien akan terjadi tetapi tidak pernah terjadi di server. Sistem rendering harus siap untuk "membatalkan" efek yang salah diprediksi. Misalnya, klien mungkin memperkirakan bahwa granat meledak dan memicu efek partikel, tetapi jika pemain lain berhasil menjinakkan granat tersebut, klien harus menyembunyikan efek partikel tersebut.

Strategi yang baik untuk merender simulasi yang diprediksi adalah menyelaraskan pola mesin status di dalam loop simulasi dan merender perubahan pada status dalam fungsi langkah render. Contoh berikut mensimulasikan granat dengan pola mesin status:

Pola Mesin Status Sederhana untuk Melacak Granat (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)
-- Inisialisasi status granat kosong
local grenadeState = grenade:GetAttribute("State")
if grenadeState == nil then
grenadeState = module.GrenadeStates.Idle
grenade:SetAttribute("State", grenadeState)
grenade:SetAttribute("Timer", 0.0)
end
-- Tambah timer granat
local timer = grenade:GetAttribute("Timer")
timer = timer + deltaTime
grenade:SetAttribute("Timer", timer)
-- Ledakkan granat yang menyala
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

Dengan pola mesin status sebelumnya di tempat, Anda dapat merender efek granat dalam koneksi RunService.RenderStepped dalam skrip terpisah berdasarkan status granat yang disinkronkan:

Render Partikel dan Suara berdasarkan Status Granat yang Disinkronkan

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Simulation = require(ReplicatedStorage.Simulation)
local grenade = script.Parent
local previousGrenadeState = nil
-- Sorot instance untuk menunjukkan status granat
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")
-- Emit partikel menyala jika granat menyala
grenade.LitEmitter.Enabled = grenadeState == Simulation.GrenadeStates.Lit
-- Putar emitter ledakan jika granat baru saja meledak
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
-- Ubah warna sorotan granat berdasarkan status dan waktu
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)

Mendesain berdasarkan latensi jaringan

Tentu saja mekanik permainan tertentu lebih cocok untuk multipemain terhubung daripada mekanik lainnya. Pemain akan selalu memiliki sedikit keterlambatan antara saat pemain lain melakukan tindakan dan saat mereka menerima input pemain tersebut. Cara terbaik untuk menciptakan permainan multipemain yang sangat lancar adalah merancang permainan Anda dengan batasan ini dalam pikiran.

Sebagai contoh, permainan dengan akselerasi yang lebih lambat pada gerakan pemain akan terlihat lebih halus daripada yang memiliki akselerasi lebih tinggi karena perbedaan posisi yang disebabkan oleh latensi jaringan dari input pemain akan lebih sedikit dibandingkan dengan permainan yang memiliki akselerasi lebih tinggi.

Sebagai contoh lain, mekanik permainan di mana pemain dapat segera memicu ledakan besar dengan menekan input akan memiliki lebih banyak artefak jaringan daripada jika ledakan tersebut ditunda setelah input, seolah dengan menyalakan sumbu. Ini menempatkan simulasi ulang pada efek sumbu daripada pada efek ledakan yang merupakan artefak jaringan yang kurang kentara.

Memprediksi input pemain lain

Secara default, Roblox tidak meneruskan input dari setiap klien ke setiap klien lainnya. Apakah ini tepat untuk permainan Anda tergantung pada desainnya:

  • Untuk gerakan humanoid dasar, perilaku default berarti bahwa gerakan karakter pemain lain tidak diekstrapolasi dari status server yang otoritatif dan, sebagai hasilnya, karakter pemain lain tidak akan salah memperkirakan tetapi akan dirender sedikit di masa lalu.
  • Dalam permainan balap, sebaliknya, perilaku default berarti bahwa klien tidak akan tahu apakah pemain lain menerapkan throttle atau input lainnya, sehingga mobil lain mungkin muncul di belakang pemain lokal meskipun sebenarnya mereka di depan. Untuk mengatasi ini, Anda dapat menyimpan input pemain dalam atribut di server dan beroperasi pada atribut sinkronisasi tersebut di sisi klien menggunakan RunService:BindToSimulation() seperti yang ditunjukkan dalam contoh kode berikut dan template Balap. Pendekatan ini memungkinkan Anda menggunakan atribut sebagai input untuk simulasi Anda sehingga memiliki input pemain yang sepenuhnya direplikasi.
Menyimpan Input Pemain dalam Atribut (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)
-- Tuliskan input lainnya ke dalam atribut...
end
module.Initialize = function()
RunService:BindToSimulation(function(deltaTime)
if RunService:IsServer() then
-- Meneruskan input dari server ke semua klien
for _, player in Players:GetPlayers() do
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local inputContext:InputContext = player.PlayerGui.InputContext
module.storePlayerInput(player, humanoidRootPart)
end
else
-- Tuliskan input lokal pemain sebagai atribut
local player = Players.LocalPlayer
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local inputContext:InputContext = player.PlayerGui.InputContext
module.storePlayerInput(player, humanoidRootPart)
end
-- Gunakan atribut sebagai input untuk permainan
for _, player in Players:GetPlayers() do
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local throttle = humanoidRootPart:GetAttribute("Throttle")
if throttle then
-- Terapkan throttle ke kendaraan pemain
end
end
end)
end)
return module

Debugging

Ada beberapa alat dan teknik baru yang dapat Anda gunakan untuk melakukan debugging di permainan yang memiliki otoritas server.

Visualisasi otoritas server

Menekan CtrlShiftF6 (Windows) atau ShiftF6 (Mac) membuka visualisasi otoritas server Studio yang menunjukkan beberapa informasi kunci:

DetailDeskripsi
Tingkat keberhasilan prediksi instancePersentase instance yang diprediksi dengan benar selama 8 detik terakhir.
Tingkat penerimaan inputPersentase semua input pemain yang tiba tepat waktu di server. Input yang terlambat akan menurunkan angka ini.
Delta langkah klien-serverJumlah frame antara klien dan server, termasuk waktu bergabung klien. Stabilitas angka ini mencerminkan stabilitas koneksi Anda ke server.
FPS heartbeat RCCFrame rate simulasi di server. Jika angka ini turun di bawah 59, server tidak dapat mengikuti simulasi dan permainan akan menurun kualitasnya.
Jumlah instance yang diprediksiJumlah instance yang diprediksi klien Anda memprediksi.
Alasan penurunan input

Jumlah berapa kali server telah menjatuhkan input untuk setiap alasan:

  • [x] terlalu lama — Input tiba terlambat, berarti jaringan Anda memburuk atau klien tidak dapat mengikuti simulasi.
  • [x] salah urutan — Terjadi bug jaringan yang menyebabkan input Anda diatur ulang dan dibuang.
  • [x] buffer penuh — Server tidak dapat menampung input Anda. Entah jaringan Anda tiba-tiba membaik atau server tidak dapat mengikuti simulasi.

Radius simulasi

Saat mengandalkan prediksi otomatis (Enum.PredictionMode.Automatic), Anda dapat memvisualisasikan radius prediksi di sekitar karakter pemain Anda dengan mengaktifkan Are Regions Enabled di pengaturan Studio (AltS di Windows; S di Mac). Silinder hijau menunjukkan rentang di sekitar karakter Anda di mana instance diprediksi, dan radiusnya tumbuh dan menyusut berdasarkan karakteristik kinerja perangkat.

Radius simulasi di sekitar karakter pemain dengan otoritas server berjalan
©2026 Roblox Corporation. Roblox, logo Roblox, dan Powering Imagination termasuk dalam merek dagang kami yang terdaftar dan tidak terdaftar di AS dan negara lainnya.