Tecniche di autorità del server

*Questo contenuto è tradotto usando AI (Beta) e potrebbe contenere errori. Per visualizzare questa pagina in inglese, clicca qui.

Questa guida delinea varie tecniche per creare giochi multiplayer di alta qualità e fluidi utilizzando il modello di autorità del server.

Creazione predittiva di istanze (stitching delle istanze)

Lo stitching delle istanze consente agli script client di creare predittivamente Instances all'interno dei callback RunService:BindToSimulation(). Il client crea immediatamente la Instance senza attendere un round-trip con il server; quando arriva la copia autorevole del server, l'istanza creata dal client e la copia autorevole del server vengono unite in una sola. Dalla prospettiva del tuo script, la Instance esiste immediatamente ed è coerente con il server.

Lo stitching delle istanze è utile nei casi in cui un'istanza deve essere visibile e attiva sul client il prima possibile. Sebbene il server replichi eventualmente qualsiasi istanza di cui il client ha bisogno (insieme a qualsiasi effetto che hanno avuto sul mondo), questo processo comporta almeno un round-trip di latenza a causa della comunicazione con il server. Esempi includono sparare con un lanciatore di razzi e creare vincoli fisici — senza stitching, il client vedrà il razzo materializzarsi lontano da loro o avrà qualche jitter quando i nuovi vincoli vengono replicati a loro.

Comportamento tecnico

Lo stitching delle istanze funziona generando lo stesso GUID deterministico sia sul client che sul server. Il GUID è derivato da quattro input: il tipo di Instance che viene creato, l'identità della sorgente (vedi sotto), il frame di simulazione attuale e un contatore di chiamate per script che viene ripristinato ogni frame.

Se client e server concordano sugli input, producono GUID corrispondenti e lo stitching ha successo.

Implementazione

Per utilizzare lo stitching delle istanze, chiamata Instance.new(), Instance:Clone() o Instance.fromExisting() all'interno di un callback BindToSimulation() da un ModuleScript che viene richiesto sia sul client che sul server. Non è richiesta nient'altro da parte tua; il sistema gestisce automaticamente l'assegnazione e la riconciliazione dei GUID.

Puoi liberamente impostare proprietà non di accesso alla simulazione come Name, Size o Parent su un'istanza prima che venga parentata nel DataModel.

Simulazione (ModuleScript) - Crea Istanza in un 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 -- La parte è ora nel DataModel; eventuali modifiche di accesso non di simulazione genereranno un errore dopo questo
-- La parte esiste immediatamente sul client e verrà riconciliata con il server
end)
end
return Simulation

Instance:Clone() e Instance.fromExisting() funzionano correttamente quando l'istanza sorgente è stata replicata sia al client che al server; entrambi i lati clonano da GUID sorgente corrispondenti e producono GUID predittivi corrispondenti.

Simulazione (ModuleScript) - Clona Istanza in un Callback BindToSimulation()

local RunService = game:GetService("RunService")
local Simulation = {}
local sourceTemplate -- un'istanza replicata
Simulation.Initialize = function()
RunService:BindToSimulation(function(deltaTime)
local cloned = sourceTemplate:Clone()
cloned.Parent = workspace
-- Gerarchia clonata è stitched con la copia autorevole del server
end)
end
return Simulation

Smussamento della posizione

Puoi smussare visivamente la posizione di oggetti sincronizzati mal predetti rendendo un oggetto diverso da quello che viene simulato.

  1. Rendi invisibile l'oggetto simulato.
  2. Crea un oggetto renderer come una clone visivo, non collidabile e senza massa per tracciare l'oggetto simulato.
  3. Attacca uno script all'oggetto renderer che segue dolcemente la posizione dell'oggetto invisibile e simulato. Questa separazione tra rendering e simulazione ti consente di alterare la posizione dell'oggetto renderer per creare un'esperienza visivamente fluida.

Nel seguente esempio Script, l'oggetto renderizzato (genitore) segue dolcemente l'oggetto simulato. L'oggetto renderizzato è sempre leggermente "dietro" l'oggetto simulato, il che è tipicamente accettabile ma può essere indesiderabile in alcune situazioni.

Segui dolcemente la posizione di BasePart con l'oggetto Renderer

local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local Workspace = game:GetService("Workspace")
-- Oggetto da seguire dolcemente
local smoothTarget:BasePart = Workspace.SimulatedPart
-- Oggetto visivo che verrà smussato
local renderer:BasePart = script.Parent
-- Tempo per smussare; più piccolo significa più veloce
local smoothTime = 0.07
-- Memorizza i dati necessari per calcolare la posizione smussata
local smoothVelocity = Vector3.new()
-- Disabilitare la fisica dell'oggetto renderer
renderer.Massless = true
renderer.Anchored = true
renderer.CanCollide = false
RunService.RenderStepped:Connect(function(deltaTime: number)
-- Segui dolcemente l'oggetto target
local smoothPosition, smoothVelocity = TweenService:SmoothDamp(
renderer.Position,
smoothTarget.Position,
smoothVelocity,
smoothTime,
math.huge,
deltaTime)
renderer.Position = smoothPosition
end)

Il gioco di esempio Soccer utilizza una variazione di questa tecnica per attivare e disattivare in modo più intelligente il smussamento della posizione per la palla da calcio. Specificamente, la palla da calcio smussa la sua posizione solo quando la palla simulata ha "saltato" abbastanza lontano dalla palla renderizzata. Questo approccio fornisce il meglio di entrambi i mondi: la palla da calcio non presenta latenza visiva in condizioni normali, e il gioco smussa la sua posizione solo dopo che la palla simulata è inaspettatamente saltata in una nuova posizione, probabilmente a causa di un artefatto di rete o di un cambiamento lato server.

Scrivere codice per le animazioni

Sotto l'autorità del server, la simulazione del client può essere ripristinata e risimulata quando il server corregge una mispredizione. Durante il rollback, lo stato di animazione viene riavvolto, il che significa che AnimationTrack gestisce che hai memorizzato nei frame precedenti potrebbe non essere più valido.

Speculare la logica delle animazioni

Come per qualsiasi logica di gioco centrale, la logica per controllare le animazioni deve essere sincronizzata tra server e client, altrimenti potrebbero verificarsi mispredizioni e comportamenti tremolanti. Vedi sincronizzazione della simulazione per un modello che lega funzioni tramite RunService:BindToSimulation() in un ModuleScript che viene inizializzato sia sul client che sul server.

Evitare la memorizzazione della traccia

Un modello comune negli script non autoritativi è memorizzare gli oggetti AnimationTrack al momento del caricamento e riutilizzarli indefinitamente. Questo modello fallisce in un gioco con autorità del server quando il server corregge una mispredizione e il client riavvolge/riproduce la sua simulazione con dati corretti. Se il tuo script mantiene ancora un riferimento a una traccia fermata o sostituita, chiamate come AdjustWeight() o AdjustSpeed() opererà su una traccia che non è più rappresentata visivamente.

Memorizza Tracce sul Client (Inaffidabile)

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

Invece di mantenere oggetti traccia, memorizza i codici delle animazioni (o istanze di Animation) e interroga il Animator per la traccia attiva ogni volta che devi interagire con essa. Due API sono disponibili per questo:

  • Animator:GetTrackByAnimationId() — Restituisce la traccia attualmente attiva per un ID di animazione specifico, o nil se non ci sono animazioni attive con quell'ID. Usa questo quando sai quale specifica animazione stai cercando.
  • Animator:GetPlayingAnimationTracks() — Restituisce tutte le tracce attive (in riproduzione, in dissolvenza, o messe in pausa). Usa questo quando devi iterare su tutto ciò che è attivo (per esempio, per fermare tutte le animazioni o trovare tracce in base a qualche criterio).

ModuleScript chiamato CustomAnimate in ReplicatedStorage:

CustomAnimate

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local CustomAnimate = {}
-- Memorizza i riferimenti alle animazioni (non tracce caricate)
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

Riproduzione di suoni ed effetti visivi

In una simulazione predittiva, è possibile attivare effetti o suoni per eventi che il client ha previsto sarebbero accaduti ma che in realtà non si sono verificati sul server. Il sistema di rendering dovrebbe essere pronto a "annullare" eventuali effetti mal predetti. Ad esempio, un client potrebbe prevedere che una granata sia esplosa e attivare un effetto particellare, ma se un altro giocatore ha disinnescato la granata, il client dovrebbe nascondere l'effetto particellare.

Una buona strategia per il rendering di una simulazione predittiva è sincronizzare un modello a stati all'interno del ciclo di simulazione e renderizzare le modifiche allo stato in una funzione di rendering. Il seguente esempio simula una granata con un modello a stati:

Semplice Macchina a Stati per Tracciare una Granata (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)
-- Inizializza lo stato vuoto della granata
local grenadeState = grenade:GetAttribute("State")
if grenadeState == nil then
grenadeState = module.GrenadeStates.Idle
grenade:SetAttribute("State", grenadeState)
grenade:SetAttribute("Timer", 0.0)
end
-- Incrementa il timer della granata
local timer = grenade:GetAttribute("Timer")
timer = timer + deltaTime
grenade:SetAttribute("Timer", timer)
-- Esplodi granate accese
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

Con la macchina a stati precedente in atto, puoi renderizzare effetti della granata in una connessione RunService.RenderStepped all'interno di uno script separato basato sullo stato della granata sincronizzato:

Renderizza Particelle e Suoni in base allo stato sincronizzato della Granata

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Simulation = require(ReplicatedStorage.Simulation)
local grenade = script.Parent
local previousGrenadeState = nil
-- Evidenzia l'istanza per indicare lo stato della granata
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")
-- Emessi le particelle se la granata è accesa
grenade.LitEmitter.Enabled = grenadeState == Simulation.GrenadeStates.Lit
-- Riproduci l'emettitore di esplosioni se la granata è appena esplosa
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
-- Cambia il colore dell'evidenziazione della granata in base allo stato e al tempo
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)

Progettare attorno alla latenza di rete

Certain gameplay mechanics lend themselves better to networked multiplayer than other mechanics. I giocatori avranno sempre un certo ritardo tra quando un altro giocatore esegue un'azione e quando ricevono l'input di quel giocatore. Il modo migliore per creare un gioco multiplayer super fluido è progettare il tuo gioco tenendo presenti queste limitazioni.

Ad esempio, un gioco con una accelerazione più lenta nel movimento del giocatore apparirà più fluido di uno con una accelerazione maggiore perché la differenza nella posizione causata dalla latenza di rete dell'input del giocatore sarà minore rispetto a un gioco con una maggiore accelerazione.

Come altro esempio, una meccanica di gioco in cui i giocatori possono istantaneamente attivare un'emozione esplosiva premendo un input avrà più artefatti di rete rispetto a se l'esplosione è ritardata dopo l'input, come se accendessero una miccia. Questo mette la risimulazione sull'effetto della miccia invece che sull'effetto dell'esplosione, che è un artefatto di rete meno evidente.

Prevedere gli input degli altri giocatori

Per impostazione predefinita, Roblox non inoltra gli input da ogni client a ogni altro client. Se questo è giusto per il tuo gioco dipende dal suo design:

  • Per il movimento umanoide di base, il comportamento predefinito significa che i movimenti degli altri personaggi giocatori non vengono estrapolati dallo stato autorevole del server e, di conseguenza, gli altri personaggi giocatori non prevederanno errori ma verranno renderizzati leggermente nel passato.
  • In un gioco di corse, al contrario, il comportamento predefinito significa che i client non sapranno se altri giocatori stanno applicando il gas o altri input, quindi altre macchine potrebbero apparire dietro al giocatore locale anche se in realtà sono avanti. Per alleviare questo, puoi memorizzare gli input dei giocatori in attributi sul server e operare su quegli attributi sincronizzati sul lato client utilizzando RunService:BindToSimulation() come dimostrato nel seguente campione di codice e nel modello Racing. Questo approccio ti consente di utilizzare gli attributi come input per la tua simulazione per avere input completamente replicati dai giocatori.
Memorizzare Input dei Giocatori in Attributi (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)
-- Scrivi qualsiasi altro input negli attributi...
end
module.Initialize = function()
RunService:BindToSimulation(function(deltaTime)
if RunService:IsServer() then
-- Inoltra input dal server a tutti i client
for _, player in Players:GetPlayers() do
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local inputContext:InputContext = player.PlayerGui.InputContext
module.storePlayerInput(player, humanoidRootPart)
end
else
-- Scrivi input del giocatore locale come attributi
local player = Players.LocalPlayer
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local inputContext:InputContext = player.PlayerGui.InputContext
module.storePlayerInput(player, humanoidRootPart)
end
-- Utilizza gli attributi come input per il gioco
for _, player in Players:GetPlayers() do
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local throttle = humanoidRootPart:GetAttribute("Throttle")
if throttle then
-- Applica il gas al veicolo del giocatore
end
end
end)
end)
return module

Debugging

Ci sono alcuni nuovi strumenti e tecniche che puoi utilizzare per debuggare un gioco con autorità del server.

Visualizzatore di autorità del server

Premendo CtrlShiftF6 (Windows) o ShiftF6 (Mac) si apre il visualizzatore di autorità del server di Studio, che mostra diversi pezzi chiave di informazioni:

DettagliDescrizione
Tasso di successo della previsione delle istanzeLa percentuale delle istanze correttamente previste negli ultimi 8 secondi.
Tasso di accettazione dell'inputLa percentuale di tutti gli input dei giocatori che sono arrivati in tempo sul server. Input tardivi ridurranno questo numero.
Delta del passo client-serverIl numero di frame tra il client e il server, inclusi i tempi di accesso del client. La stabilità di questo numero rappresenta la stabilità della tua connessione con il server.
FPS del battito cardiaco RCCIl frame rate della simulazione sul server. Se questo numero scende sotto 59, il server non riesce a tenere il passo con la simulazione e la qualità del gioco degraderà.
Conteggio delle istanze previsteIl numero di istanze che il tuo client sta prevedendo.
Conteggi del motivo di abbandono dell'input

Il numero di volte in cui il server ha abbandonato un input per ciascun motivo:

  • [x] troppo vecchio — Gli input sono arrivati in ritardo, il che significa che la tua rete è peggiorata o il client non è riuscito a tenere il passo con la simulazione.
  • [x] fuori ordine — Si è verificato un bug di rete che ha causato la riorganizzazione e la cancellazione dei tuoi input.
  • [x] buffer pieno — Il server non è riuscito a bufferizzare il tuo input. O la tua rete è migliorata improvvisamente o il server non è riuscito a tenere il passo con la simulazione.

Raggio di simulazione

Quando ci si affida alla previsione automatica (Enum.PredictionMode.Automatic), puoi visualizzare il raggio di previsione intorno al tuo personaggio giocatore abilitando Are Regions Enabled nelle impostazioni di Studio (AltS su Windows; S su Mac). Il cilindro verde indica il raggio attorno al tuo personaggio in cui le istanze vengono previste, e il suo raggio cresce e si restringe in base alle caratteristiche di prestazione del dispositivo.

Raggio di simulazione intorno al personaggio giocatore con autorità del server attiva
© 2026 Roblox Corporation. Roblox, il logo Roblox e Powering Imagination sono tra i nostri marchi registrati e non registrati negli Stati Uniti. e altri paesi.