Ten przewodnik przedstawia kilka technik efektywnego i skutecznego wykorzystania strumieniowania instancji w grze. Chociaż nie ma jednego "uniwersalnego" rozwiązania w projektowaniu gry wykorzystującej strumieniowanie lub konwersji gry bez strumieniowania na strumieniową, przestrzeganie tych kroków na wysokim poziomie pomoże Ci w dużym stopniu.
Właściwości strumieniowania
Gdy StreamingEnabled jest włączone dla obiektu Workspace w Studio, ustaw jego związane właściwości na zalecane wartości:
| Właściwość | Rekomendacja |
|---|---|
| EnableSLIMAvatars | Użyj Enabled, aby renderować standardowe avatary rigowe jako lekkie, animowane zastępniki, kiedy to odpowiednie. Zobacz avatary SLIM po więcej informacji. |
| ModelStreamingBehavior | Użyj Improved, aby włączyć najefektywniejsze strumieniowanie dla Models z potomkami BasePart. |
| StreamingIntegrityMode | Użyj PauseOutsideLoadedArea, aby zrównoważyć integralność rozgrywki bez zbędnych lub zbyt częstych przerw. |
| StreamingMinRadius | Użyj domyślnej wartości 64, aby zmaksymalizować, jak bardzo silnik może zmniejszyć rozmiar gry dla urządzeń o niskiej wydajności. |
| StreamingTargetRadius | Użyj domyślnej wartości 1024, aby osiągnąć dobrą równowagę między widocznością dla graczy na urządzeniach o wysokiej wydajności a rozsądnych zużyciem pamięci. |
| StreamOutBehavior | Użyj Opportunistic, aby umożliwić klientowi agresywne zbieranie danych, znacznie zmniejszając zużycie pamięci i pomagając zapobiegać awariom z powodu braku pamięci. |
Poziom szczegółowości modelu
Model.LevelOfDetail pomaga uzupełnić nienadawane treści Model lekkimi kompozytowymi lub zamiennikami, sprawiając, że świat wygląda wizualnie pełnowartościowo. SLIM (Skalowalny Lekki Interaktywny Model) kompozytowe siatki są szczególnie skuteczne, ponieważ gracze często nie potrafią odróżnić siatki SLIM od oryginału w pełnym rozrachunku.
Aby uzyskać najlepsze rezultaty:
- Grupuj części, które są związane przestrzennie i logicznie, na przykład wszystkie części samochodu.
- Ustaw LevelOfDetail na SLIM w modelach, które zawierają statyczne siatki i części. Modele zawierające siatki skórkowe, modyfikowane w trakcie działania lub odtwarzające animacje, nie są wspierane.
- Utrzymuj wymiary przestrzenne każdego modelu poniżej ~64 sześciennych studów, aby zwiększyć prawdopodobieństwo, że cały rzeczywisty model zostanie przesłany razem. Jeśli model ma bardzo duże wymiary, podziel go na mniejsze modułowe modele i zastosuj odpowiedni LevelOfDetail do każdego z nich.
Struktura modelu
Poza ustawieniem poziomu szczegółowości modelu, struktura i ustawienia twojego Models mają znaczący wpływ na to, jak dobrze działa strumieniowanie. Gdy rozwijasz lub konwertujesz istniejącą grę:
Używaj modeli atomowych do logicznego grupowania — Kiedy skrypt potrzebuje dostępu do wszystkich części wewnątrz modelu, ustaw jego ModelStreamingMode na Atomic. Umożliwia to skryptom po stronie klienta bezpieczny dostęp do instancji wewnątrz modelu bez nadmiernego użycia WaitForChild() (chociaż takie skrypty nadal muszą używać WaitForChild() dla całego atomowego modelu).
Minimalizuj modele trwałe — Modele trwałe ładują się po dołączeniu i nigdy nie są strumieniowane, zajmując stale pamięć. Ustaw ModelStreamingMode modelu na Persistent tylko wtedy, gdy musi pozostać dostępny i dostępny dla skryptów przez cały czas.
Dekomponuj modele kontenerowe — Powszechny wzór bez strumieniowania to jeden ogromny Model, który zawiera wiele NPC, rekwizytów lub podobnych grupowań. W przypadku strumieniowania modele kontenerowe zmniejszają efektywność strumieniowania, a także nie są optymalne dla poziomu szczegółowości modelu, który działa najlepiej z blisko grupowanymi instancjami. Dekomponuj modele kontenerowe na mniejsze modele z fizycznie bliskimi lub logicznie powiązanymi częściami.
Avatary SLIM
Platformowe avatary znajdujące się poza aktualnie strumieniowanym obszarem nie są widoczne domyślnie, ale włączenie Workspace.EnableSLIMAvatars renderuje standardowe rig jako lekkie, animowane zastępniki, gdy to odpowiednie. Efektywnie, silnik:
- Renderuje wersję SLIM, gdy model rzeczywistego awatara jest strumieniowany.
- Wymienia SLIM/oryginalny model według dostępnych zasobów wewnątrz promienia strumieniowania, ponieważ modele SLIM mogą być znacznie mniej kosztowne do renderowania niż model o pełnej rozdzielczości.
- Ogranicza animacje SLIM według ważności sceny i szerokości pasma.
System poziomu szczegółowości awatara ma specyficzne optymalizacje dla awatarów. Poniżej opisano, co będzie lub nie będzie przetwarzane przez SLIM:
Wzory skryptów
Poniższe wzory skryptów najczęściej są dotknięte strumieniowaniem. Odpowiednia strategia zależy od zamierzenia kodu, dlatego każdy wzór wymienia kilka opcji, gdzie to stosowne.
Bezpośredni indeks do potomków
Indeksowanie potomków Workspace za pomocą operatora . generuje błąd, jeśli jakaś instancja w ścieżce nie jest aktualnie strumieniowana. To samo dotyczy FindFirstChild(), FindFirstChildWhichIsA(), i FindFirstChildOfClass(), które zwracają nil, jeśli dziecko nie zostało strumieniowane.
Wyszukiwanie potomkówlocal house1 = workspace:FindFirstChild("House1") -- nil, jeśli "House1" nie jest strumieniowanelocal door = workspace.House1.Door -- Uszkodzone, jeśli "House1" lub "Door" nie są strumieniowane
Podobny wzór dotyczy uzyskiwania dostępu do Humanoid lub innych potomków postaci bezpośrednio w połączeniu Player.CharacterAdded. W przypadku strumieniowania model postaci jest parentowany do Workspace zanim wszystkie jego potomkowie zostaną zreplikowane, więc bezpośrednie indeksowanie zawodzi.
Potomkowie postaci
local Players = game:GetService("Players")
local player = Players.LocalPlayer
player.CharacterAdded:Connect(function(character)
local humanoid = character.Humanoid
end)
Jeśli skrypt nie może postępować bez instancji, poczekaj na nią za pomocą WaitForChild():
Wyszukiwanie potomkówlocal house1 = workspace:WaitForChild("House1")local door = house1:WaitForChild("Door")
Instancje wysyłane zdalnie
Sygnal RemoteEvent/RemoteFunction i instancja, do której się odnosi, podróżują niezależnie, więc sygnał może dotrzeć do klienta przed tym, jak instancja będzie dostępna — lub instancja może w ogóle nie być obecna. Dwa prawdopodobne powody to:
W przypadku strumieniowania może być niewielkie opóźnienie między utworzeniem części/modelu na serwerze a jego replikacją dla klientów. Efektywnie, część referencjonowana przez RemoteEvent/RemoteFunction może po prostu jeszcze nie istnieć, nawet wewnątrz strumieniowanego obszaru.
Wysłanie referencji części/modelu z serwera do klienta przez RemoteEvent lub RemoteFunction wymaga, aby instancja była zreplikowana do odbierającego klienta. Wysłanie ścieżki instancji jako łańcuch ma ten sam problem, ponieważ ścieżka może wskazywać na nieistniejącą lokalizację na kliencie:
Skrypt Klientalocal ReplicatedStorage = game:GetService("ReplicatedStorage")local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")remoteEvent.OnClientEvent:Connect(function(data)local checkpoint = data.checkpoint -- Błąd, jeśli "checkpoint" nie jest strumieniowanelocal level = workspace.Levels[data.levelPath] -- Błąd, jeśli ścieżka nie jest strumieniowanaend)
Jeśli odbierający skrypt klienta potrzebuje instancji, aby kontynuować, dołącz WaitForChild() przed jej użyciem. Zauważ, że to może prowadzić do nieskończoności, jeśli instancja nigdy się nie strumieni.
Desynchronizacja klienta
Desynchronizacja po stronie klienta powinna być traktowana jako wyjątek, a nie standardowy wzór projektowy. Wprowadzenie lokalnych kopii tylko dla klientów lub zmiana rodziców instancji lokalnie może powodować poważne problemy. Audytuj swój kod pod kątem miejsc, które korzystają z tych typów zmian, które będą trwałe na kliencie.
Na przykład, zmiana rodzica instancji lokalnie z ReplicatedStorage do Workspace może uczynić tę instancję zdolną do bycia strumieniowaną. Podobnie, klonowanie instancji lokalnie (Instance:Clone()) z ReplicatedStorage do Workspace tworzy lokalną kopię, która nie jest już częścią pipeline'u replikacji serwera i nie otrzyma aktualizacji właściwości z oryginalnej instancji będącej własnością serwera.
Ta sama koncepcja dotyczy wywoływania Instance:Destroy() na obiekcie będącym własnością serwera. Usuwa to instancję lokalnie, ale serwer wciąż ją ma, więc znowu strumieniowana będzie w swoim oryginalnym stanie, gdy będzie to możliwe.
Proaktywne strumieniowanie
Gdy następne miejsce docelowe gracza można przewidzieć, dokonaj wywołań po stronie serwera do Player:RequestStreamAroundAsync(), aby strumieniować chwilowe obszary do tymczasowego załadowania, lub użyj Player:AddReplicationFocus() na ograniczonej podstawie dla obszarów, które powinny pozostać załadowane aż do ich wyraźnego zwolnienia.
Na przykład, gdy postać gracza ma zostać teleportowana przez zmianę CFrame do domu innego gracza w odległej lokalizacji, możesz wcześniej pobrać obszar docelowy, aby zminimalizować pojawianie się i zapewnić płynniejsze przejście. Poniższy skrypt pokazuje, jak wywołać zdarzenie zdalne, aby przenieść postać gracza za pomocą metody wcześniejszego pobierania. Jeśli żądanie wstępne jest udane, gdy funkcja zwraca, minimalny promień wokół lokalizacji docelowej powinien być obecny na kliencie.
Skrypt Serwera - Teleportacja Postaci Gracza
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local teleportEvent = ReplicatedStorage:WaitForChild("TeleportEvent")
local function teleportPlayer(player, teleportTarget)
-- Żądaj strumieniowania wokół lokalizacji docelowej
player:RequestStreamAroundAsync(teleportTarget)
-- Teleportuj postać
local character = player.Character
if character and character.Parent then
local currentPivot = character:GetPivot()
character:PivotTo(currentPivot * CFrame.new(teleportTarget))
end
end
-- Wywołaj funkcję teleportacji, gdy klient wyśle zdarzenie zdalne
teleportEvent.OnServerEvent:Connect(teleportPlayer)
Odczyty właściwości instancji
Gdy instancja zostanie strumieniowana, jej aktualizacje właściwości nie są już replikowane dla tego klienta. Odczytanie właściwości takich jak BasePart.Position nadal kończy się sukcesem, ale zwraca ostatnią zreplikowaną wartość, która może być arbitralnie stara.
local Players = game:GetService("Players")local player = Players.LocalPlayer-- Pozycja może być stara, jeśli "target" zostało strumieniowanelocal dist = (target.Position - player.Character.HumanoidRootPart.Position).Magnitude
Przenieś logikę do serwera, ponieważ skrypty po stronie serwera widzą wszystkie instancje przez cały czas. To ogólnie jest najpewniejszą opcją dla kontroli odległości i innej logiki wrażliwej na położenie.
Oczekiwanie w krytycznym ścieżce
Niektóre gry bez strumieniowania ładują swoją mapę kopiując ją z ReplicatedStorage do Workspace, a następnie czekają na nią po stronie klienta, zanim odrzucą ekran ładowania i sygnalizują gotowość. W przypadku strumieniowania wiesza to na wieki — postać klienta jeszcze nie się pojawiła, więc nie ma fokusa replikacji, a instancja mapy przestrzennej nigdy się nie strumieni.
Przenieś logikę ekranu ładowania, tak aby nie zależała od konkretnej instancji przestrzennej będącej obecnej, na przykład sygnalizując gotowość, gdy postać już się pojawi i najbliższy otaczający obszar jest już strumieniowany.
Obsługa zmian sygnału
Sygnały takie jak Instance.ChildAdded/Instance.ChildRemoved oraz sygnały CollectionService takie jak GetInstanceAddedSignal() lub GetInstanceRemovedSignal() również uruchamiają się przy strumieniowaniu, co jest nieodróżnialne od prawdziwych pojawień/zniknięć. Odbierające skrypty nie mogą rozróżnić różnicy z samego sygnału, więc logika zakładająca, że sygnał odpowiada "prawdziwemu" zdarzeniu, musi być zaktualizowana.
Zaudytuj skrypty pod kątem wszelkich słuchaczy sygnałów, które mogą się złamać lub znacząco zmieniać, gdy wywołano je za pomocą strumieniowania. Na przykład, jeśli odtwarzasz dźwięki lub efekty wizualne, gdy wrogi NPC po raz pierwszy pojawia się w świecie, przypisz każdemu wrogowi atrybut taki jak Spawned przy pierwszym pojawieniu się, i pomijaj ponowne odtwarzanie tych samych dźwięków/efektów przyszłych strumieniowania tego wroga.
Śledzenie atrybutu
local CollectionService = game:GetService("CollectionService")
local TAG_NAME = "Enemy"
CollectionService:GetInstanceAddedSignal(TAG_NAME):Connect(function(enemy)
if not enemy:GetAttribute("Spawned") then
-- Ustaw atrybut "Spawned" na wrogu przy początkowym pojawieniu
enemy:SetAttribute("Spawned", true)
-- Odtwarzaj dźwięki/efekty wizualne przy tym początkowym pojawieniu
playSpawnEffects(enemy)
end
end)
Iteracja po kolekcjach
Iteracje po kolekcjach po stronie klienta, takie jak Instance:GetChildren() i Instance:GetDescendants(), zwracają tylko strumieniowaną część potomków. Ma to zastosowanie nawet wtedy, gdy sam rodzic zawsze jest replikowany, np. Folder bezpośrednio pod Workspace, którego przestrzenni potomkowie strumieniują się.
local Players = game:GetService("Players")local player = Players.LocalPlayer-- Folder "Homes" jest zawsze replikowany, ale jego dzieci strumieniują-- Ta pętla może pominąć domy, które nie są obecnie strumieniowanefor _, home in workspace.Homes:GetChildren() doif home.Settings.Owner.Value == player.Name thenreturn homeendend
Jeśli wymagana jest pełna enumeracja, przeprowadź skanowanie po stronie serwera i przekaż wynik graczowi za pomocą RemoteEvent, jeśli to konieczne.
Zapytania przestrzenne
Zapytania przestrzenne po stronie klienta, takie jak WorldRoot:Raycast(), WorldRoot:GetPartBoundsInBox(), i Model:GetBoundingBox(), odzwierciedlają tylko strumieniowaną zawartość. To, czy to jest problem, zależy od tego, do czego zapytanie jest używane.
Użyj serwera w przypadku zapytań, które muszą odzwierciedlać pełny świat, na przykład rzutowania promieni, które sprawdza, czy gracz ma linię widzenia do odległego celu.
Inne wzory
Poniższe wzory również mogą mieć zastosowanie i powinny być starannie rozważone:
Sound lub AudioPlayer parentowany do obiektu 3D zatrzymuje się, gdy ten obiekt jest strumieniowany. Dla dźwięku ambientowego, który powinien trwać niezależnie od strumieniowania, parentuj emiter do trwałego modelu lub do kontenera, który nie stosuje strumieniowania.
W obrębie UI obiektów, takich jak BillboardGui lub SurfaceGui, jak również efekty wizualne takie jak Beams lub Highlights, których adornee lub przyczepność strumieniują, po prostu przestają się renderować. Może to być zamierzona zachowanie, ale powinieneś to zweryfikować.
Zdarzenia BasePart.Touched, ProximityPrompts, DragDetectors i ClickDetectors nie działają dla graczy, których klient nie ma powiązanego obiektu/modelu strumieniowanego. Jeśli interakcja musi być dostępna z dowolnego zasięgu, model musi być trwały, lub interakcja potrzebuje innego mechanizmu.
Dla PathfindingService i pathfindingu po stronie klienta, ścieżka widzi tylko strumieniowaną geometrię na kliencie i może prowadzić przez przeszkody, które istnieją na serwerze. Zobacz tutaj dla strategii.
Realistyczne warunki testowe
Gdy skrypty zostaną zaktualizowane, testuj grę dokładnie. Błędy strumieniowania często manifestują się tylko na krawędziach strumieniowanego obszaru lub podczas przejść, więc testowanie tylko w pobliżu spawnu lub w promieniu docelowym nie jest wystarczające.
Testuj z ustawioną minimum Workspace.StreamingTargetRadius (64). Niektóre błędy strumieniowania pojawiają się tylko, gdy strumieniowany obszar jest mały.
Przejdź przez pełne wzory przemieszczania się w grze, teleportuj się pomiędzy odległymi obszarami i odwiedzaj kilka miejsc po ich opuszczeniu. To są sytuacje, które najwięcej angażują strumieniowanie.
Użyj debugowania strumieniowania, aby monitorować aktywne ustawienia strumieniowania, obecnie załadowane obszary i stan strumieniowania w czasie działania.
Obserwuj w oknie Output i Developer Console błędy, ponieważ wiele wzorów skryptów generuje błędy zamiast cichych błędów. W szczególności zwróć uwagę na błędy w formie attempt to index nil with ..., które często wskazują na brakujące wywołanie WaitForChild().
Wyposaż i aktywuj Tools, strzelaj do broni i uruchamiaj różne interakcje w grze.

