Questa guida delinea diverse tecniche per utilizzare lo streaming di istanze in modo efficiente ed efficace nel gioco. Sebbene non esista una soluzione universale per progettare un gioco in streaming o convertire un gioco non streaming in streaming, seguire questi passaggi di alto livello ti porterà molto vicino a realizzarlo.
Proprietà di streaming
Una volta che StreamingEnabled è attivato per l'oggetto Workspace in Studio, imposta le sue proprietà correlate sui seguenti valori raccomandati:
| Proprietà | Raccomandazione |
|---|---|
| EnableSLIMAvatars | Utilizza Enabled per rendere gli avatar rig standard come sostituti leggeri e animati quando appropriato. Vedi avatar SLIM per maggiori informazioni. |
| ModelStreamingBehavior | Utilizza Improved per abilitare lo streaming più efficiente per Models con discendenti BasePart. |
| StreamingIntegrityMode | Utilizza PauseOutsideLoadedArea per bilanciare l'integrità del gameplay senza mettere in pausa inutilmente o troppo spesso. |
| StreamingMinRadius | Utilizza il valore predefinito di 64 per massimizzare quanto il motore può ridurre il gioco per dispositivi di bassa gamma. |
| StreamingTargetRadius | Utilizza il valore predefinito di 1024 per ottenere un buon equilibrio tra visibilità per i giocatori su dispositivi di alta gamma e un'impronta di memoria ragionevole. |
| StreamOutBehavior | Utilizza Opportunistic per consentire al client di raccogliere attivamente i contenuti, riducendo significativamente l'uso della memoria e contribuendo a prevenire crash per esaurimento della memoria. |
Livello di dettaglio dei modelli
Model.LevelOfDetail aiuta a riempire i contenuti Model non caricati con mesh composite o impostori leggeri, rendendo il mondo visivamente completo. Le mesh composite SLIM (Scalable Lightweight Interactive Model) sono particolarmente efficaci, poiché i giocatori spesso non possono distinguere una mesh SLIM dall'originale completamente caricato.
Per i migliori risultati:
- Raggruppa le parti che sono spazialmente e logicamente correlate, ad esempio tutte le parti di un'auto.
- Imposta LevelOfDetail a SLIM su modelli che contengono mesh e parti statiche. I modelli che contengono mesh scheletriche, vengono modificati a runtime o riproducono animazioni non sono supportati.
- Mantieni l'estensione spaziale di ciascun modello sotto ~64 stud cubici per aumentare la probabilità che l'intero modello reale venga caricato insieme. Se un modello ha estensioni molto grandi, suddividilo in modelli modulari più piccoli e applica un appropriato LevelOfDetail a ciascuno.
Struttura del modello
Oltre a impostare il livello di dettaglio del modello, la struttura e le impostazioni dei tuoi Models hanno un impatto significativo su quanto bene funzioni lo streaming. Mentre costruisci o converti un gioco esistente:
Utilizza modelli atomici per raggruppamenti logici — Quando uno script ha bisogno di accesso a tutte le parti all'interno di un modello, imposta il suo ModelStreamingMode a Atomic. Questo consente agli script lato client di accedere in modo sicuro alle istanze all'interno del modello senza un uso eccessivo di WaitForChild() (anche se tali script devono comunque utilizzare WaitForChild() per il modello atomico nel complesso).
Minimizza i modelli persistenti — I modelli persistenti vengono caricati dopo l'unione e non vengono mai rimossi, occupando permanentemente memoria. Imposta il ModelStreamingMode di un modello su Persistent solo se deve essere sempre disponibile e accessibile agli script.
Decomponi i modelli contenitore — Un modello Model enorme singolo che contiene molti NPC, oggetti o raggruppamenti simili è un modello di non streaming comune. Sotto lo streaming, i modelli contenitore riducono l'efficienza dello streaming e non sono ottimali per il livello di dettaglio del modello che funziona meglio con istanze strettamente raggruppate. Decompn kub er i modelli contenitore in modelli più piccoli con parti fisicamente vicine o logicamente correlate.
Appiattisci le gerarchie di modelli profondamente annidate — Annidare un modello persistente all'interno di un modello atomico costringe effettivamente il modello atomico a comportarsi come persistente. Le gerarchie piatte sono più facili da comprendere in streaming.
Avatar SLIM
Gli avatar della piattaforma al di fuori dell'area attualmente caricata non sono visibili per impostazione predefinita, ma abilitando Workspace.EnableSLIMAvatars, gli avatar standard rig vengono resi come sostituti leggeri e animati quando appropriato. In effetti, il motore:
- Rende una versione SLIM quando un modello avatar reale viene rimosso.
- Scambia SLIM/reale in base alle risorse disponibili all'interno del raggio di streaming, poiché i modelli SLIM possono essere significativamente meno costosi da rendere rispetto al modello a risoluzione completa.
- Limita le animazioni SLIM in base all'importanza della scena e alla larghezza di banda disponibile.
Il sistema di livello di dettaglio dell'avatar ha ottimizzazioni specifiche per gli avatar. Quanto segue descrive cosa verrà o non verrà elaborato da SLIM:
Modelli di script
I seguenti modelli di script sono i più comunemente influenzati dallo streaming. La strategia corretta dipende dall'intento del codice, quindi ciascun modello elenca più opzioni dove appropriato.
Indicizzazione diretta nei discendenti
Indicizzare i discendenti di Workspace con l'operatore . genera un errore se qualsiasi istanza nel percorso non è attualmente caricata. Lo stesso vale per FindFirstChild(), FindFirstChildWhichIsA() e FindFirstChildOfClass() che restituiscono nil se il figlio non è stato caricato.
Ricerca Descendentelocal house1 = workspace:FindFirstChild("House1") -- nil se "House1" non è stato caricatolocal door = workspace.House1.Door -- Rotto se "House1" o "Door" non sono stati caricati
Un modello simile è accedere al Humanoid o ad altri discendenti del personaggio direttamente all'interno di una connessione Player.CharacterAdded. Sotto lo streaming, il modello del personaggio è parentato a Workspace prima che tutti i suoi discendenti siano stati replicati, quindi l'indicizzazione diretta non riesce.
Discendenti del personaggio
local Players = game:GetService("Players")
local player = Players.LocalPlayer
player.CharacterAdded:Connect(function(character)
local humanoid = character.Humanoid
end)
Se lo script non può procedere senza un'istanza, aspetta che essa venga caricata usando WaitForChild():
Ricerca Descendentelocal house1 = workspace:WaitForChild("House1")local door = house1:WaitForChild("Door")
Istanze inviate in remoto
Un segnale RemoteEvent/RemoteFunction e l'istanza a cui si riferisce viaggiano indipendentemente, quindi il segnale può arrivare al client prima che l'istanza sia presente — o l'istanza potrebbe non essere mai presente. Due cause comuni includono:
Sotto streaming, potrebbe esserci un leggero ritardo tra quando una parte/modello viene creata sul server e quando viene replicata ai client. In effetti, una parte a cui fa riferimento un RemoteEvent/RemoteFunction potrebbe semplicemente non esistere ancora, anche all'interno di un'area caricata.
Inviare un riferimento a una parte/modello dal server al client tramite un RemoteEvent o RemoteFunction richiede che l'istanza venga replicata al client ricevente. Inviare un percorso di istanza come stringa presenta lo stesso problema, poiché il percorso potrebbe risolversi in una posizione non esistente sul client:
Script Clientlocal ReplicatedStorage = game:GetService("ReplicatedStorage")local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")remoteEvent.OnClientEvent:Connect(function(data)local checkpoint = data.checkpoint -- Errori se "checkpoint" non è stato caricatolocal level = workspace.Levels[data.levelPath] -- Errori se il percorso non è stato caricatoend)
Se lo script del client ha bisogno dell'istanza per procedere, includi WaitForChild() prima di usarla. Nota che questo può bloccare indefinitamente se l'istanza non viene mai caricata, quindi considera di aggiungere un timeout come secondo parametro di WaitForChild().
Desincronizzazione dei client
La desincronizzazione lato client dovrebbe essere trattata come un'eccezione, non come un modello di design standard. Introdurre copie solo client o riparentare le istanze localmente può creare seri problemi. Verifica il tuo codice per i punti che si basano su questi tipi di modifiche che persistono sul client.
Ad esempio, riparentare un'istanza localmente da ReplicatedStorage a Workspace può rendere quell'istanza idonea per essere rimossa. Allo stesso modo, clonare un'istanza localmente (Instance:Clone()) da ReplicatedStorage in Workspace crea una copia solo client che non fa più parte della pipeline di replicazione del server e non riceverà aggiornamenti delle proprietà dall'istanza originale di proprietà del server.
Lo stesso concetto si applica quando si chiama Instance:Destroy() sul client per un oggetto di proprietà del server. Questo rimuove l'istanza localmente ma il server la possiede ancora, quindi verrà ricaricata nuovamente con il suo stato originale quando sarà idonea.
Streaming proattivo
Quando la prossima destinazione di un giocatore può essere anticipata, esegui chiamate lato server a Player:RequestStreamAroundAsync() per caricare aree transitorie per un caricamento temporaneo, oppure utilizza Player:AddReplicationFocus() su base limitata per aree che dovrebbero rimanere caricate fino a quando non vengono esplicitamente rilasciate.
Ad esempio, quando un personaggio giocatore sta per teletrasportarsi da un cambiamento di CFrame nella casa di un altro giocatore in una posizione lontana, puoi precaricare l'area di destinazione per ridurre al minimo gli effetti di caricamento e fornire una transizione più fluida. Il seguente script mostra come un evento remoto client-server può essere inviato per spostare un personaggio giocatore utilizzando un metodo di precaricamento. Se la richiesta di precaricamento ha successo quando la funzione restituisce, il raggio minimo attorno alla posizione target dovrebbe essere presente sul client.
Script Server - Teletrasportare Personaggio Giocatore
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local teleportEvent = ReplicatedStorage:WaitForChild("TeleportEvent")
local function teleportPlayer(player, teleportTarget)
-- Richiedi streaming attorno alla posizione target
player:RequestStreamAroundAsync(teleportTarget)
-- Teletrasporta il personaggio
local character = player.Character
if character and character.Parent then
local currentPivot = character:GetPivot()
character:PivotTo(currentPivot * CFrame.new(teleportTarget))
end
end
-- Chiama la funzione di teletrasporto quando il client invia l'evento remoto
teleportEvent.OnServerEvent:Connect(teleportPlayer)
Letture delle proprietà delle istanze
Una volta che un'istanza viene rimossa, i suoi aggiornamenti di proprietà non vengono più replicati a quel client. Leggere proprietà come BasePart.Position continua a funzionare ma restituisce l'ultimo valore replicato che può essere arbitrariamente obsoleto.
local Players = game:GetService("Players")local player = Players.LocalPlayer-- La posizione può essere obsoleta se "target" è stato rimossolocal dist = (target.Position - player.Character.HumanoidRootPart.Position).Magnitude
Sposta la logica al server, poiché gli script lato server vedono tutte le istanze in qualsiasi momento. Questa è generalmente l'opzione più affidabile per verifiche di distanza e altre logiche sensibili alla posizione.
Attesa sul percorso critico
Alcuni giochi non streaming caricano la mappa clonandola da ReplicatedStorage in Workspace, quindi aspettano che venga caricata sul client prima di rimuovere uno schermo di caricamento e segnalare la prontezza. Sotto streaming questo rimane bloccato indefinitamente — il personaggio del client non è ancora stato generato, quindi non c'è focalizzazione della replicazione, e l'istanza spaziale della mappa non viene mai caricata.
Sposta la logica dello schermo di caricamento in modo che non dipenda dalla presenza di un'istanza spaziale specifica, ad esempio segnalando la prontezza una volta che il personaggio è stato generato e l'area immediata è stata caricata.
Gestione del cambiamento di segnale
Segnali come Instance.ChildAdded/Instance.ChildRemoved e segnali di CollectionService come GetInstanceAddedSignal() o GetInstanceRemovedSignal() vengono attivati anche durante lo streaming in/out, indistinguibili da spawn/rimozioni reali. Gli script riceventi non possono distinguere la differenza solo dal segnale, quindi la logica che presume che un segnale corrisponda a un evento "reale" deve essere aggiornata.
Controlla gli script per eventuali ascoltatori di segnali che potrebbero bloccarsi o cambiare significativamente quando attivati dallo streaming in e/o dallo streaming out. Ad esempio, se riproduci audio o effetti visivi quando un NPC nemico appare inizialmente nel mondo, assegna a ciascun nemico un attributo come Spawned al primo spawn, e salta la riproduzione dello stesso audio/effects negli stream-in successivi del nemico.
Tracciamento degli attributi
local CollectionService = game:GetService("CollectionService")
local TAG_NAME = "Nemico"
CollectionService:GetInstanceAddedSignal(TAG_NAME):Connect(function(enemy)
if not enemy:GetAttribute("Spawned") then
-- Imposta l'attributo "Spawned" sul nemico per lo spawn iniziale
enemy:SetAttribute("Spawned", true)
-- Riproduci audio/effetti visivi per questo spawn iniziale
playSpawnEffects(enemy)
end
end)
Iterazione su collezioni
Iterazioni client-side delle collezioni come Instance:GetChildren() e Instance:GetDescendants() restituiscono solo il sottoinsieme caricato dei discendenti. Questo vale anche quando il genitore stesso è sempre replicato, come una Folder direttamente sotto Workspace i cui discendenti spaziali vengono caricati e rimossi.
local Players = game:GetService("Players")local player = Players.LocalPlayer-- La cartella "Homes" è sempre replicata ma i suoi figli vengono caricati e rimossi-- Questo ciclo potrebbe mancare case che non sono attualmente caricatefor _, home in workspace.Homes:GetChildren() doif home.Settings.Owner.Value == player.Name thenreturn homeendend
Se è necessaria un'enumerazione completa, esegui la scansione sul server e passa il risultato al giocatore tramite un RemoteEvent se necessario.
Query spaziali
Le query spaziali client-side come WorldRoot:Raycast(), WorldRoot:GetPartBoundsInBox(), e Model:GetBoundingBox() riflettono solo i contenuti caricati. Se questo è un problema dipende da come viene utilizzata la query.
Utilizza il server per le query il cui risultato deve riflettere il mondo completo, ad esempio un raycast che verifica se il giocatore ha la linea di vista su un obiettivo lontano.
Altri modelli
I seguenti modelli possono anche applicarsi e dovrebbero essere considerati con attenzione:
Un Sound o AudioPlayer parentato a un oggetto 3D si ferma quando quell'oggetto viene rimosso. Per audio ambientale che dovrebbe persistere indipendentemente dallo streaming, parenta l'emittente a un modello persistente o a un contenitore non streaming.
Oggetti UI in-game come BillboardGui o SurfaceGui così come effetti visivi come Beams o Highlights il cui adornee o attacco viene rimosso semplicemente smettono di renderizzare. Questo può essere il comportamento previsto, ma dovresti verificarlo.
Gli eventi BasePart.Touched, ProximityPrompts, DragDetectors, e ClickDetectors non operano per i giocatori il cui client non ha la parte/modelo associato caricato. Se l'interazione deve essere possibile da qualsiasi distanza, il modello deve essere persistente oppure l'interazione deve avere un meccanismo diverso.
Per PathfindingService e pathfinding lato client, il cercatore vede solo la geometria caricata sul client e potrebbe instradare attraverso ostacoli che esistono sul server. Vedi qui per strategie.
Condizioni di test realistico
Una volta che gli script sono stati aggiornati, prova il gioco a fondo. I bug di streaming spesso si manifestano solo ai bordi dell'area caricata o durante le transizioni, quindi testare solo vicino allo spawn o al raggio target non è sufficiente.
Testa con Workspace.StreamingTargetRadius impostato al suo valore minimo (64). Alcuni bug di streaming si presentano solo quando l'area caricata è piccola.
Gioca attraverso i pieni modelli di traversata del gioco, teletrasportati tra aree lontane e rivisita aree dopo averle lasciate. Queste sono le situazioni che esercitano maggiormente lo streaming in e fuori.
Utilizza il debugger di streaming per monitorare le impostazioni di streaming attive, le regioni attualmente caricate e lo stato di streaming a runtime.
Guarda la finestra Output e la Console dello sviluppatore per errori, poiché molti dei modelli di script producono errori piuttosto che comportamenti silenziosi errati. Presta particolare attenzione agli errori della forma attempt to index nil with ... che spesso indicano una mancanza di chiamata a WaitForChild().
Equipaggia e attiva Tools, spara armi e attiva diverse interazioni di gioco.

