이 가이드는 인게임 인스턴스 스트리밍을 효율적이고 효과적으로 사용하는 여러 가지 기술을 설명합니다. 스트리밍 게임을 설계하거나 비스트리밍 게임을 스트리밍으로 변환하는 데 "하나의 정답"이 없지만, 이러한 고수준 단계를 따르면 대부분의 작업을 완료할 수 있습니다.
스트리밍 속성
Studio에서 StreamingEnabled가 Workspace 객체에 대해 켜지면, 관련 속성을 다음의 권장 값으로 설정합니다:
| 속성 | 추천 사항 |
|---|---|
| EnableSLIMAvatars | 적절할 때 표준 리그 아바타를 가벼운 애니메이션 스탠드인으로 렌더링하기 위해 Enabled를 사용합니다. 자세한 내용은 SLIM 아바타를 참조하십시오. |
| ModelStreamingBehavior | BasePart 자손이 있는 Models에 대해 가장 효율적인 스트리밍을 활성화하려면 Improved를 사용하십시오. |
| StreamingIntegrityMode | 불필요하게 또는 너무 자주 일시 정지하지 않고 게임 플레이 무결성을 균형 잡기 위해 PauseOutsideLoadedArea를 사용하십시오. |
| StreamingMinRadius | 저사양 장치에 맞게 게임을 최대한 축소할 수 있도록 기본값인 64를 사용하십시오. |
| StreamingTargetRadius | 고성능 장치의 플레이어가 볼 수 있는 균형을 잘 맞추기 위해 기본값 1024를 사용하고 합리적인 메모리 사용량을 유지합니다. |
| StreamOutBehavior | 클라이언트가 공격적으로 콘텐츠를 가비지 수집하도록 허용하여 메모리 사용량을 대폭 줄이고 메모리 부족으로 인한 충돌을 방지하기 위해 Opportunistic를 사용합니다. |
모델 레벨-오브-디테일
Model.LevelOfDetail은 비스트리밍 Model 콘텐츠를 가벼운 복합체 또는 대리자 메시로 채워 세상을 시각적으로 완전하게 보이도록 돕습니다. SLIM (Scalable Lightweight Interactive Model) 복합체 메시가 특히 효과적이며, 플레이어는 종종 SLIM 메시를 완전하게 스트리밍된 원본과 구별할 수 없습니다.
최상의 결과를 얻으려면:
- 공간적으로 및 논리적으로 관련된 파트를 그룹화합니다. 예를 들어 자동차의 모든 부분을 함께 그룹화합니다.
- 각 모델의 공간적 범위를 ~64 입방 스터드 이하로 유지하여 전체 실제 모델이 함께 스트리밍될 가능성을 높입니다. 모델의 범위가 매우 큰 경우, 더 작고 모듈화된 모델로 분할하고 각 모델에 적절한 LevelOfDetail을 적용합니다.
모델 구조
모델 레벨-오브-디테일을 설정하는 것 외에도, Models의 구조와 설정은 스트리밍 성능에 상당한 영향을 미칩니다. 기존 게임을 구축하거나 변환할 때:
논리적 그룹화를 위한 원자 모델 사용 — 스크립트가 모델 내 모든 파트에 액세스해야 할 경우, ModelStreamingMode를 Atomic으로 설정합니다. 이렇게 하면 클라이언트 측 스크립트가 과도한 WaitForChild() 사용 없이 모델 내 인스턴스에 안전하게 액세스할 수 있습니다(단, 그러한 스크립트는 여전히 전체 원자 모델에 대해 WaitForChild()를 사용해야 합니다).
영속적인 모델 최소화 — 영속적 모델은 참여 후 로드되며 절대 스트리밍 해제되지 않아 메모리를 지속적으로 차지합니다. 모델의 ModelStreamingMode를 Persistent로 설정하는 것은 항상 사용 가능하고 스크립트에서 접근할 수 있어야 할 경우에만 수행합니다.
컨테이너 모델 해체 — 일반적인 비스트리밍 패턴은 많은 NPC, 소품 또는 유사한 그룹을 포함하는 단일 거대한 Model입니다. 스트리밍 중에, 컨테이너 모델은 스트리밍 효율성을 감소시키며, 가까이 그룹화된 인스턴스에서 최적으로 작동하는 모델 레벨-오브-디테일에도 적합하지 않습니다. 컨테이너 모델을 물리적으로 가까운 부분이나 논리적으로 관련 있는 부분이 있는 더 작은 모델로 해체합니다.
SLIM 아바타
현재 스트리밍 영역 외부의 플랫폼 아바타는 기본적으로 표시되지 않지만 Workspace.EnableSLIMAvatars를 활성화하면 표준 리그 아바타를 적절할 때 가벼운 애니메이션 스탠드인으로 렌더링합니다. 사실상, 엔진은:
- 실제 아바타 모델이 스트리밍 해제될 때 SLIM 버전을 렌더링합니다.
- 사용 가능한 리소스에 따라 스트리밍 반경 내에서 SLIM/실제를 교환합니다. 슬림 모델은 풀 해상도 모델보다 렌더링 비용이 훨씬 적을 수 있습니다.
- 장면 중요성 및 대역폭에 따라 SLIM 애니메이션을 조절합니다.
아바타 레벨-오브-디테일 시스템은 아바타에 대한 특정 최적화를 가지고 있습니다. SLIM이 처리할 수 있는 것과 처리할 수 없는 것을 아래에 설명합니다.
스크립트 패턴
다음 스크립트 패턴은 스트리밍에 가장 일반적으로 영향을 받습니다. 올바른 전략은 코드의 의도에 따라 달라지므로 각 패턴은 적절하게 여러 가지 옵션을 나열합니다.
자손에 대한 직접 인덱스
. 연산자를 사용하여 Workspace 자손에 인덱싱하면 경로에 있는 인스턴스가 현재 스트리밍되지 않은 경우 오류가 발생합니다. 이는 FindFirstChild(), FindFirstChildWhichIsA(), 및 FindFirstChildOfClass()에도 적용되며, 자식이 스트리밍되지 않은 경우 nil을 반환합니다.
자손 조회local house1 = workspace:FindFirstChild("House1") -- "House1"이 스트리밍되지 않으면 nillocal door = workspace.House1.Door -- "House1" 또는 "Door"가 스트리밍되지 않으면 오류 발생
비슷한 패턴은 Player.CharacterAdded 연결 내부에서 Humanoid 또는 다른 캐릭터 자손에 직접 액세스하는 것입니다. 스트리밍 중에 캐릭터 모델은 모든 자손이 복제되기 전에 Workspace에 부모가 설정되므로 직접 인덱싱이 실패합니다.
캐릭터 자손
local Players = game:GetService("Players")
local player = Players.LocalPlayer
player.CharacterAdded:Connect(function(character)
local humanoid = character.Humanoid
end)
스크립트가 인스턴스 없이는 진행할 수 없는 경우, 사용하기 전에 WaitForChild()로 대기합니다:
자손 조회local house1 = workspace:WaitForChild("House1")local door = house1:WaitForChild("Door")
원격으로 전송된 인스턴스
RemoteEvent/RemoteFunction 신호와 해당 인스턴스는 독립적으로 이동하므로, 신호가 클라이언트에 도착하기 전에 인스턴스가 존재하지 않을 수 있습니다 — 또는 인스턴스가 아예 존재하지 않을 수도 있습니다. 두 가지 가능한 원인은 다음과 같습니다:
스트리밍 중에 서버에서 부분/모델이 생성되는 시점과 클라이언트에 복제되는 시점 사이에 약간의 지연이 있을 수 있습니다. 사실상, RemoteEvent/RemoteFunction에 의해 참조된 부분은 아직 존재하지 않을 수 있습니다. 심지어 스트리밍된 영역 내에서도 Same.
서버에서 클라이언트로 RemoteEvent 또는 RemoteFunction을 통해 부분/모델 참조를 보내려면 해당 인스턴스가 수신 클라이언트에 복제되어야 합니다. 문자열로 인스턴스 경로를 보내는 것도 마찬가지입니다. 클라이언트의 비존재 위치로 경로를 풀 수 있습니다:
클라이언트 스크립트local ReplicatedStorage = game:GetService("ReplicatedStorage")local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")remoteEvent.OnClientEvent:Connect(function(data)local checkpoint = data.checkpoint -- "checkpoint"이 스트리밍되지 않으면 오류 발생local level = workspace.Levels[data.levelPath] -- 경로가 스트리밍되지 않으면 오류 발생end)
수신 클라이언트 스크립트가 진행하려면 인스턴스가 필요하다면, 사용하기 전에 WaitForChild()로 포함합니다. 인스턴스가 절대 스트리밍되지 않으면 무한 대기할 수 있으므로 WaitForChild()의 두 번째 매개변수로 타임아웃을 추가하는 것을 고려하십시오.
클라이언트 비동기화
클라이언트 측 비동기화는 예외로 취급되어야 하며 표준 디자인 패턴이 아닙니다. 클라이언트 전용 복사본을 도입하거나 인스턴스를 로컬로 재부모화하면 심각한 문제가 발생할 수 있습니다. 이러한 유형의 변경 사항이 클라이언트에서 지속되는지 의존하는 코드의 위치를 감사하십시오.
예를 들어, 인스턴스를 로컬로 ReplicatedStorage에서 Workspace로 재부모화하면 해당 인스턴스는 스트리밍 해제될 수 있습니다. 마찬가지로, Instance:Clone()으로 ReplicatedStorage에서 Workspace로 인스턴스를 클론하면 서버 복제 파이프라인의 일부가 아닌 클라이언트 전용 복사본이 생성되고, 원본 서버 소유 인스턴스의 속성 업데이트를 받지 않습니다.
클라이언트에서 서버 소유 객체에 대해 Instance:Destroy()를 호출할 때도 동일한 개념이 적용됩니다. 이렇게 하면 인스턴스가 로컬에서 제거되지만 서버에는 여전히 존재하므로 대상이 적절해지면 원래 상태와 함께 다시 스트리밍됩니다.
사전 스트리밍
플레이어의 다음 목적지를 미리 예측할 수 있다면, 서버 측 호출 Player:RequestStreamAroundAsync()를 통해 일시적인 로딩을 위해 동적 영역을 스트리밍하거나 Player:AddReplicationFocus()를 제한적으로 사용하여 명시적으로 해제될 때까지 로드 상태를 유지해야 합니다.
예를 들어, 플레이어 캐릭터가 먼 위치의 다른 플레이어의 집으로 CFrame 변경으로 텔레포트할 때, 최소한의 팝인 효과를 줄이고 매끄러운 전환을 제공하기 위해 목적지 영역을 미리 가져올 수 있습니다. 다음 스크립트는 사전 가져오기 방법을 사용하여 플레이어 캐릭터를 이동시키기 위해 클라이언트-서버 원격 이벤트를 발생시키는 방법을 보여줍니다. 함수 반환 시 사전 가져오기 요청이 성공적이면 대상 위치 주변의 최소 반경이 클라이언트에 있는 것이어야 합니다.
서버 스크립트 - 플레이어 캐릭터 텔레포트
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local teleportEvent = ReplicatedStorage:WaitForChild("TeleportEvent")
local function teleportPlayer(player, teleportTarget)
-- 대상 위치 주변 스트리밍 요청
player:RequestStreamAroundAsync(teleportTarget)
-- 캐릭터 텔레포트
local character = player.Character
if character and character.Parent then
local currentPivot = character:GetPivot()
character:PivotTo(currentPivot * CFrame.new(teleportTarget))
end
end
-- 클라이언트가 원격 이벤트를 발생시킬 때 텔레포트 함수 호출
teleportEvent.OnServerEvent:Connect(teleportPlayer)
인스턴스 속성 읽기
인스턴스가 스트리밍 해제되면 해당 속성 업데이트는 더 이상 클라이언트에 복제되지 않습니다. BasePart.Position과 같은 속성을 읽는 것은 성공하더라도 마지막 복제된 값을 반환하며 이는 임의로 구식일 수 있습니다.
local Players = game:GetService("Players")local player = Players.LocalPlayer-- "target"이 스트리밍 해제된 경우 위치는 구식일 수 있습니다local dist = (target.Position - player.Character.HumanoidRootPart.Position).Magnitude
로직을 서버로 이동시키십시오. 서버 측 스크립트는 모든 인스턴스를 항상 볼 수 있습니다. 이것은 거리 확인 및 기타 위치 감敏 로직에 대해 일반적으로 가장 신뢰할 수 있는 옵션입니다.
중요한 경로에서 대기
일부 비스트리밍 게임은 ReplicatedStorage에서 Workspace로 클론하여 맵을 로드한 다음 클라이언트에서 해당 맵을 대기하고 로딩 화면을 해제합니다. 스트리밍에서는 클라이언트의 캐릭터가 아직 생성되지 않았기 때문에 무한히 대기하게 되며, 공간 맵 인스턴스는 절대 스트리밍되지 않습니다.
로딩 화면 로직을 이동하여 특정 공간 인스턴스가 존재해야 하지 않도록 합니다. 예를 들어 캐릭터가 생성되었고 즉각적인 주변 지역이 스트리밍된 후 준비 상태를 알리도록 합니다.
신호 변경 처리
Instance.ChildAdded/Instance.ChildRemoved와 CollectionService 신호인 GetInstanceAddedSignal() 및 GetInstanceRemovedSignal()은 모두 스트리밍 인/아웃 때 발생하며, 실제 생성/제거와 구별할 수 없습니다. 수신 스크립트는 신호만으로는 차이를 알 수 없으므로 신호가 "실제" 이벤트에 해당한다고 가정하는 로직을 업데이트해야 합니다.
신호 추가/제거시 스트리밍 인/아웃으로 발생할 때 깨질 수 있는 신호 리스너를 감사합니다. 예를 들어 적 NPC가 처음으로 세계에 생성될 때 오디오 또는 시각적 효과를 재생하는 경우, 각 적에게 초기 생성 시 Spawned와 같은 속성을 할당하고 적의 향후 스트리밍 인에서는 동일한 오디오/효과를 재생하지 않도록 건너뜁니다.
속성 추적
local CollectionService = game:GetService("CollectionService")
local TAG_NAME = "Enemy"
CollectionService:GetInstanceAddedSignal(TAG_NAME):Connect(function(enemy)
if not enemy:GetAttribute("Spawned") then
-- 초기 생성 시 적에 "Spawned" 속성 설정
enemy:SetAttribute("Spawned", true)
-- 이 초기 생성에 대해 오디오/시각적 효과 재생
playSpawnEffects(enemy)
end
end)
컬렉션 반복
클라이언트 측 컬렉션 반복인 Instance:GetChildren() 및 Instance:GetDescendants()는 스트리밍된 자손의 하위 집합만 반환합니다. 이는 부모가 항상 복제되더라도 적용됩니다. 예를 들어 Workspace 바로 아래의 Folder는 공간적 자손이 스트리밍되고 나갑니다.
local Players = game:GetService("Players")local player = Players.LocalPlayer-- "Homes" 폴더는 항상 복제되지만 자식들은 스트리밍 인과 아웃됩니다-- 이 루프는 현재 스트리밍되지 않은 주택을 놓칠 수 있습니다for _, home in workspace.Homes:GetChildren() doif home.Settings.Owner.Value == player.Name thenreturn homeendend
전체 열거가 필요하면 서버에서 검색을 수행하고 필요한 경우 결과를 RemoteEvent를 통해 플레이어에게 전달합니다.
공간적 쿼리
클라이언트 측 공간적 쿼리인 WorldRoot:Raycast(), WorldRoot:GetPartBoundsInBox(), 및 Model:GetBoundingBox()는 스트리밍된 콘텐츠만 반영합니다. 이 쿼리가 무엇에 사용되는지에 따라 문제가 달라질 수 있습니다.
결과가 전체 세계를 반영해야 하는 쿼리에 대해서는 서버를 사용하십시오. 예를 들어 플레이어가 먼 대상에 대한 시야를 검사하는 레이캐스트입니다.
기타 패턴
다음 패턴도 적용될 수 있으며 신중하게 고려해야 합니다:
3D 객체에 부모가 지정된 Sound 또는 AudioPlayer는 해당 객체가 스트리밍 아웃되면 중지됩니다. 스트리밍과 관계없이 유지되어야 하는 배경 음악의 경우 발산기를 영속적인 모델이나 비스트리밍 컨테이너에 부모를 지정하십시오.
게임 내 UI 객체인 BillboardGui 또는 SurfaceGui 및 Beams 또는 Highlights와 같은 시각 효과의 경우, 아도니 또는 부착물이 스트리밍 아웃되면 렌더링이 중지됩니다. 이는 의도된 동작일 수 있지만 확인해야 합니다.
BasePart.Touched 이벤트, ProximityPrompts, DragDetectors, 및 ClickDetectors는 연결된 부분/모델이 스트리밍되지 않은 클라이언트의 플레이어에 대해서는 작동하지 않습니다. 모든 범위에서 상호작용이 가능해야 하는 경우, 모델은 영속적이어야 하며 또는 상호작용을 위한 다른 메커니즘이 필요합니다.
PathfindingService 및 클라이언트 측 경로 찾기의 경우, 경로 찾기는 클라이언트에서 스트리밍된 기하학만 볼 수 있으며, 서버에서 존재하는 장애물을 통과할 수 있습니다. 여기를 참조하여 전략을 확인하십시오.
실제 테스트 조건
스크립트가 업데이트되면 게임을 철저히 테스트하십시오. 스트리밍 버그는 종종 스트리밍 영역의 가장자리나 전환 중에 나타나므로, 스폰 근처나 목표 반경에서만 테스트하는 것은 충분하지 않습니다.
Workspace.StreamingTargetRadius를 최소값(64)으로 설정하고 테스트하십시오. 일부 스트리밍 버그는 스트리밍 영역이 작을 때만 나타납니다.
게임의 전체 이동 패턴을 통해 플레이하고, 먼 영역 간 전이, 및 나간 후 다시 참조하여야 합니다. 이러한 상황이 스트리밍 인 및 아웃 작업을 가장 많이 수행합니다.
스트리밍 디버그 오버레이를 사용하여 현재 활성 스트리밍 설정, 현재 로드된 영역 및 런타임 스트리밍 상태를 모니터링합니다.
Output 윈도우 및 Developer Console에서 오류를 확인하십시오. 많은 스크립트 패턴은 조용한 잘못된 동작 대신 오류를 발생시킵니다. 누락된 WaitForChild() 호출을 나타내는 형식의 오류인 attempt to index nil with ...에 특히 주의하십시오.
Tools를 장착 및 활성화하고, 무기를 발사하고, 다양한 게임 상호작용을 트리거합니다.

