이 페이지에서는 일반적인 성능 문제와 이를 완화하기 위한 모범 사례를 설명합니다.
스크립트 계산
Luau 코드의 비싼 연산은 처리하는 데 더 많은 시간이 걸리며, 따라서 프레임 속도에 영향을 미칠 수 있습니다. 병렬로 실행되지 않는 한, Luau 코드는 동기적으로 실행되며, 스레드를 양보하는 함수를 만나기 전까지는 주 스레드를 차단합니다.
공통 문제
테이블 구조에 대한 집중적인 연산 - 직렬화, 역직렬화 및 깊은 클로닝과 같은 복잡한 연산은 특히 큰 테이블 구조에서 높은 성능 비용을 초래합니다. 이러한 연산이 재귀적이거나 아주 큰 데이터 구조를 반복하는 경우 특히 그렇습니다.
높은 빈도의 이벤트 - RunService의 프레임 기반 이벤트에 비싼 연산을 연결하고 빈도를 제한하지 않으면 이들 연산이 매 프레임 반복되며, 이는 불필요한 계산 시간 증가로 이어질 수 있습니다. 이러한 이벤트에는 다음이 포함됩니다:
완화
- RunService 이벤트에서 코드를 드물게 호출하고, 높은 빈도의 호출이 필수적인 경우(예: 카메라 업데이트)에만 사용을 제한합니다. 대부분의 다른 코드는 다른 이벤트에서 실행하거나 루프 내에서 덜 자주 실행할 수 있습니다.
- task.wait()를 사용하여 큰 또는 비싼 작업을 나누어 작업을 여러 프레임에 걸쳐 분산합니다.
- 불필요하게 비싼 연산을 식별하고 최적화하며, 데이터 모델에 접근할 필요가 없는 계산 집약적 작업에 대해 다중 스레딩을 사용합니다.
- 특정 서버 측 스크립트는 네이티브 코드 생성의 이점을 누릴 수 있으며, 이는 스크립트를 바이트코드가 아닌 기계어로 컴파일하는 간단한 플래그입니다.
마이크로 프로파일러 범위
| 범위 | 관련 계산 |
| RunService.PreRender | PreRender 이벤트에서 실행되는 코드 |
| RunService.PreSimulation | Stepped 이벤트에서 실행되는 코드 |
| RunService.PostSimulation | Heartbeat 이벤트에서 실행되는 코드 |
| RunService.Heartbeat | Heartbeat 이벤트에서 실행되는 코드 |
마이크로 프로파일러를 사용하여 스크립트를 디버깅하는 방법에 대한 자세한 내용은 특정 코드를 태그하고 세부 사항을 더욱 증가시키기 위한 함수를 포함하는 debug 라이브러리를 참조하십시오. 스크립트에서 호출된 많은 Roblox API 메서드는 유용한 신호를 제공할 수 있는 고유한 마이크로 프로파일러 태그를 가지고 있습니다.
스크립트 메모리 사용
메모리 누수는 스크립트를 작성할 때 발생할 수 있으며, 이 경우 가비지 수집기가 더 이상 사용되지 않는 메모리를 적절히 해제하지 못하게 됩니다. 누수는 서버에서 특히 만연할 수 있으며, 서버는 여러 날 동안 온라인 상태일 수 있는 반면 클라이언트 세션은 훨씬 짧습니다.
개발자 콘솔의 다음 메모리 값은 추가 조사가 필요한 문제를 나타낼 수 있습니다:
- LuaHeap - 높은 소비량 또는 증가하는 소비량은 메모리 누수를 나타냅니다.
- InstanceCount - 인스턴스 수가 지속적으로 증가하는 경우, 코드에서 일부 인스턴스에 대한 참조가 가비지 수집되지 않음을 나타냅니다.
- PlaceScriptMemory - 스크립트별 메모리 사용량을 제공합니다.
공통 문제
연결을 그대로 두기 - 엔진은 인스턴스에 연결된 이벤트 및 연결된 콜백 내의 참조된 값에 대한 가비지 수집을 수행하지 않습니다. 따라서 연결된 인스턴스의 활성 이벤트와 코드 및 참조된 값들은 사건이 발생한 뒤에도 가비지 수집기에서 범위를 벗어납니다.
인스턴스가 파괴되면 이벤트가 연결이 끊어지긴 하지만, Player 객체에 대해 이 규칙이 적용된다고 가정하는 것은 일반적인 실수입니다. 사용자가 게임을 떠난 후, 엔진은 그들의 대표 Player 객체와 캐릭터 모델을 자동으로 파괴하지 않으므로, Player 객체 및 캐릭터 모델 아래의 인스턴스(예: CharacterAdded)에 대한 연결은 스크립트 내에서 끊지 않으면 여전히 메모리를 소비합니다. 이로 인해 수백 명의 사용자가 게임에 들어오고 나갈 때 서버에서 시간이 지남에 따라 매우 큰 메모리 누수가 발생할 수 있습니다.
테이블 - 더 이상 필요하지 않은 경우 테이블에 객체를 삽입하면서 제거하지 않으면 불필요한 메모리 소비가 발생합니다, 특히 사용자가 가입할 때 사용자 데이터를 추적하는 테이블의 경우. 예를 들어, 다음 코드는 사용자가 가입할 때마다 사용자 정보를 추가하여 테이블을 생성합니다:
예시
local playerInfo = {}
Players.PlayerAdded:Connect(function(player)
playerInfo[player] = {} -- some info
end)
이 항목을 더 이상 필요하지 않을 때 제거하지 않으면, 테이블의 크기는 계속 증가하여 세션에 더 많은 사용자가 가입할수록 더 많은 메모리를 소모합니다. 이 테이블을 순회하는 코드는 테이블의 크기가 증가함에 따라 계산 비용이 더 많이 듭니다.
완화
메모리 누수를 방지하기 위해 모든 사용된 값을 정리하려면:
모든 연결 끊기 - 코드베이스를 검사하고 각 연결이 다음 경로 중 하나를 통해 정리되는지 확인합니다:
- Disconnect() 함수를 사용하여 수동으로 연결을 끊습니다.
- Destroy() 함수를 사용하여 이벤트가 속한 인스턴스를 파괴합니다.
- 연결이 추적되는 스크립트 객체를 파괴합니다.
사용자가 떠난 후 플레이어 객체 및 캐릭터 제거 - 사용자가 떠난 후 플레이어 객체 및 캐릭터 모델을 자동으로 파괴하도록 Workspace.PlayerCharacterDestroyBehavior를 활성화합니다. 원하신다면 수동으로 정리할 수도 있습니다:
플레이어 및 캐릭터 정리 예시
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
player.CharacterRemoving:Connect(function(character)
task.defer(character.Destroy, character)
end)
end)
Players.PlayerRemoving:Connect(function(player)
task.defer(player.Destroy, player)
end)
물리 계산
과도한 물리 시뮬레이션은 서버와 클라이언트 모두에서 프레임당 계산 시간을 증가시키는 주요 원인이 될 수 있습니다.
공통 문제
과도한 물리 시간 단계 빈도 - 기본적으로 스탭 동작은 적응 모드로 설정되어 있으며, 물리는 물리 메커니즘의 복잡성에 따라 60Hz, 120Hz 또는 240Hz로 스탭을 수행합니다.
향상된 물리 정확도로 고정 모드를 사용할 수 있으며, 이는 모든 물리 조립체가 240Hz(프레임당 네 번)로 스텝을 수행하도록 강제합니다. 이는 각 프레임에서 계산을 상당히 증가시킵니다.
시뮬레이션된 객체의 수 및 복잡성 - 시뮬레이션되는 3D 조립체가 많을수록 각 프레임의 물리 계산에 걸리는 시간이 더 길어집니다. 종종 게임에는 시뮬레이션이 필요 없는 객체가 포함되며, 더 많은 제약과 관절을 가진 메커니즘이 있을 수 있습니다.
지나치게 정밀한 충돌 탐지 - 메시 파트는 다양한 성능 영향을 받는 충돌 탐지를 위한 CollisionFidelity 속성을 가지고 있습니다. 메시 파트에 대한 정밀 충돌 탐지 모드는 가장 비싼 성능 비용을 발생시키며, 엔진이 계산하는 데 더 오랜 시간이 걸립니다.
완화
시뮬레이션이 필요하지 않은 부품 고정 - 물리학의 영향을 받지 않아도 되는 모든 부품을 고정하십시오. 이는 정적 NPC의 경우입니다.
적응형 물리 동작 사용 - 적응형 스텝핑은 물리 메커니즘에 대한 물리 계산 비율을 동적으로 조정하므로, 어떤 경우에는 물리 업데이트를 덜 자주 진행할 수 있습니다.
메커니즘 복잡도 줄이기
- 가능하다면 조립체의 물리 제약 또는 관절 수를 최소화합니다.
- 기계 내에서 자가 충돌의 양을 줄입니다. 예를 들어, 물체가 서로 충돌하지 않도록 리미트를 적용하거나 충돌하지 않도록 설정을 할 수 있습니다.
메시의 정밀 충돌 충실도 사용 줄이기
사용자가 거의 차이를 느끼지 않는 작은 객체나 비상호작용 객체의 경우 박스 충실도를 사용하십시오.
소형-중형 객체의 경우 모양에 따라 박스 또는 헐 충실도를 사용하십시오.
대형 및 매우 복잡한 객체의 경우, 가능하다면 보이지 않는 부품을 사용하여 사용자 지정 충돌을 생성하십시오.
충돌이 필요하지 않은 객체의 경우 충돌을 비활성화하고 박스 또는 헐 충실도를 사용하십시오. 충돌 기하학은 여전히 메모리에 저장될 수 있습니다.
스튜디오에서 디버그 목적으로 충돌 기하학을 표시하려면 3D 뷰포트 오른쪽 상단의 비 visualization Options 위젯에서 Collision fidelity를 켜십시오.
대신에, Explorer에서 CollisionFidelity=PreciseConvexDecomposition 필터를 적용하면 정밀 충실도를 가진 모든 메시 부분의 개수를 보여 주며, 쉽게 선택할 수 있습니다.
충돌 충실도 옵션을 선택하는 방법에 대한 심층적인 안내는 물리 및 렌더링 매개변수 설정을 참조하십시오.
마이크로 프로파일러 범위
| 범위 | 관련 계산 |
| physicsStepped | 전체 물리 계산 |
| worldStep | 각 프레임에서 수행된 이산 물리 스텝 |
물리 메모리 사용
물리 이동 및 충돌 탐지는 메모리를 소비합니다. 메시 파트는 메시의 충돌 경계를 평가하는 데 사용되는 접근 방식을 결정하는 CollisionFidelity 속성을 가지고 있습니다.
공통 문제
기본 및 정밀 충돌 탐지 모드는 더 낮은 충돌 모양을 가진 두 가지 다른 모드보다 메모리를 상당히 많이 소모합니다.
PhysicsParts 아래에서 메모리 소비가 높으면 게임의 객체 충돌 충실도를 줄이는 방법을 탐색해야 할 수 있습니다.
완화 방법
충돌 충실도에 대한 메모리를 줄이려면:
- 충돌이 필요 없는 부품의 경우 BasePart.CanCollide, BasePart.CanTouch 및 BasePart.CanQuery를 false로 설정하여 충돌을 비활성화합니다.
- CollisionFidelity 설정을 사용하여 충돌의 충실도를 줄입니다. Box는 가장 낮은 메모리 오버헤드를 가지며, Default와 Precise는 일반적으로 더 비쌉니다.
- 일반적으로 작고 고정된 부품의 충돌 충실도를 Box로 설정하는 것은 안전합니다.
- 매우 복잡한 대형 메시의 경우 더 작은 객체로 충돌 메시를 구성할 수 있습니다.
휴머노이드
Humanoid는 플레이어 및 비 플레이어 캐릭터(NPC)에 광범위한 기능을 제공합니다. 강력하지만 Humanoid는 상당한 계산 비용을 동반합니다.
공통 문제
- NPC에 모든 HumanoidStateTypes를 활성화한 상태로 두기 - 특정 HumanoidStateTypes를 활성화하는 것은 성능 비용이 있습니다. NPC에 필요하지 않은 것을 비활성화하십시오. 예를 들어, NPC가 사다리를 오를 계획이 아니라면 Climbing 상태를 비활성화하는 것이 안전합니다.
- 필요하지 않은 곳에서 Humanoid 사용 - 움직이지 않는 정적 NPC는 일반적으로 Humanoid 클래스가 필요하지 않습니다.
- 서버에서 다수의 NPC 애니메이션 재생 - 서버에서 실행되는 NPC 애니메이션은 서버에서 시뮬레이션되고 클라이언트로 복제되야 합니다. 이는 불필요한 오버헤드를 초래할 수 있습니다.
- 불필요한 크기 및 배율 변화 수행 - 크기/배율 변화는 FastCluster를 재구성하게 하는 원인이 됩니다. FastCluster와 관련된 성능 문제를 보고 있다면, 게임 플레이 중 이러한 변화를 줄이도록 하십시오. 또한 다른 속성 변경이 FastCluster 재구성을 유발할 수 있습니다. 일반적으로 이러한 변경을 가능한 한 많이 줄이십시오.
완화
- NPC 애니메이션을 클라이언트에서 재생 - 많은 NPC가 있는 게임에서는 클라이언트에서 Animator를 생성하고 애니메이션을 로컬에서 실행하도록 고려하십시오. 이렇게 하면 서버의 부하를 줄이고 불필요한 복제를 최소화할 수 있습니다. 추가 최적화도 가능해집니다(예: NPC가 캐릭터에 가까운 경우에만 애니메이션을 재생).
- 휴머노이드에 대한 성능 친화적인 대안을 사용 - NPC 모델은 반드시 휴머노이드 객체를 포함할 필요는 없습니다.
- 정적 NPC의 경우, 단순한 AnimationController를 사용하십시오. NPC는 이동할 필요가 없고, 단지 애니메이션을 재생하기만 하면 됩니다.
- 움직이는 NPC의 경우, NPC의 복잡성에 따라 자체 이동 제어기를 구현하고 애니메이션을 위해 AnimationController를 사용하는 것을 고려하십시오.
- 사용하지 않는 인간 상태 비활성화 - Humanoid:SetStateEnabled()를 사용하여 각 휴머노이드에 필요한 상태만 활성화하십시오.
- 빈번히 재생되는 NPC 모델 풀링 - NPC를 완전히 파괴하는 대신 비활성 NPC 풀로 보내십시오. 이렇게하면 새로운 NPC가 필요할 때 빠르게 비활성 NPC 중 하나를 활성화할 수 있습니다. 이 프로세스를 풀링이라고 하며, 캐릭터를 인스턴스화해야 할 필요성을 최소화합니다.
- 사용자가 가까운 경우에만 NPC 생성 - 사용자가 범위에 없을 때 NPC를 생성하지 말고, 사용자가 범위를 떠날 때 해당 NPC를 제거하십시오.
- 인스턴스화된 후 아바타 계층 구조에 변경 사항을 적용하지 않도록 하십시오 - 아바타 계층 구조에 대한 특정 수정은 성능에 상당한 영향을 미칠 수 있습니다. 몇 가지 최적화를 사용할 수 있습니다:
- 사용자 정의 절차적 애니메이션의 경우 JointInstance.C0 및 JointInstance.C1 속성을 업데이트하지 마십시오. 대신 Motor6D.Transform 속성을 업데이트하십시오.
마이크로 프로파일러 범위
| 범위 | 관련 계산 |
| stepHumanoid | 휴머노이드 제어 및 물리학 |
| stepAnimation | 휴머노이드 및 애니메이터 애니메이션 |
| updateInvalidatedFastClusters | 아바타를 인스턴스화하거나 수정하는 것과 관련됨 |
렌더링
클라이언트가 매 프레임마다 소요하는 시간의 상당 부분은 현재 프레임의 장면을 렌더링하는 데 사용됩니다. 서버는 렌더링을 수행하지 않으므로 이 섹션은 클라이언트 전용입니다.
드로우 호출
드로우 호출은 무엇을 렌더링할지에 대한 엔진에서 GPU로의 명령 세트입니다. 드로우 호출은 상당한 오버헤드를 발생시킵니다. 일반적으로 프레임당 드로우 호출 수가 적을수록 프레임을 렌더링하는 데 소요되는 컴퓨팅 시간이 줄어듭니다.
스튜디오의 렌더 통계 ⟩ 타이밍 항목에서 현재 발생하고 있는 드로우 호출 수를 확인할 수 있습니다. 클라이언트에서는 ShiftF2를 눌러 렌더 통계를 확인할 수 있습니다.
주어진 프레임의 장면에서 그려야 할 객체가 많을수록 GPU로의 드로우 호출 수가 많아집니다. 그러나 Roblox 엔진은 _인스턴싱_이라는 프로세스를 사용하여 동일한 질감 특성을 가진 동일한 메시를 단일 드로우 호출로 통합합니다. 특히, 동일한 MeshContent를 가진 여러 메시가 다음 조건에서 단일 드로우 호출로 처리됩니다:
- SurfaceAppearances가 동일한 경우(존재하는 경우), 그렇지 않으면 TextureContents가 동일할 경우.
- SurfaceAppearance와 MeshPart.TextureID가 둘 다 존재하지 않을 경우, 재료가 동일함.
기타 공통 문제
과도한 객체 밀도 - 많은 수의 객체가 높은 밀도로 집중되어 있을 경우, 장면의 이 영역을 렌더링하는 데 더 많은 드로우 호출이 필요합니다. 특정 맵의 부분을 볼 때 프레임 속도가 떨어지는 경우, 해당 영역의 객체 밀도가 너무 높은 신호일 수 있습니다.
데칼, 텍스처 및 파티클과 같은 객체는 일괄 처리에 잘 적용되지 않으며 추가 드로우 호출을 발생시킵니다. 장면의 이러한 객체 유형에 특히 주의를 기울이십시오. 특히, ParticleEmitters에 대한 속성 변경은 성능에 극적인 영향을 미칠 수 있습니다.
눈치채지 못한 인스턴싱 기회 - 종종, 장면에는 동일한 메시가 여러 번 복제되지만, 각 복제본의 메시 또는 텍스처 자산 ID가 다릅니다. 이는 인스턴싱을 방해하고 불필요한 드로우 호출로 이어질 수 있습니다.
이 문제의 일반적인 원인은 전체 장면이 한 번에 가져와지는 것이며, 개별 자산이 Roblox로 가져온 후 복제되어 장면이 조립되는 것입니다.
다음과 같은 간단한 스크립트를 사용하여 서로 다른 메시 ID를 사용하는 동일한 이름의 메시 부분을 식별할 수 있습니다:
for _,descendant in workspace:GetDescendants() doif descendant:IsA("MeshPart") thenprint(descendant.Name .. ", " .. descendant.MeshId)endend
출력( 스택 라인 활성화 상태) 형식은 다음과 같을 수 있습니다. 반복적인 라인은 동일한 메시의 재사용을 나타내며 매우 좋습니다. 고유한 라인은 반드시 나쁜 것은 아니지만, 명명 규칙에 따라 게임 내 중복 메시를 나타낼 수 있습니다:
LargeRock, rbxassetid://106420009602747 (x144) -- goodLargeRock, rbxassetid://120109824668127LargeRock, rbxassetid://134460273008628LargeRock, rbxassetid://139288987285823LargeRock, rbxassetid://71302144984955LargeRock, rbxassetid://90621205713698LargeRock, rbxassetid://113160939160788LargeRock, rbxassetid://135944592365226 -- all possible duplicates
과도한 객체 복잡성 - 드로우 호출 수만큼 중요하지는 않지만, 장면의 삼각형 수는 프레임 렌더링 시간에 영향을 미칩니다. 매우 복잡한 메시가 많거나 너무 많은 MeshPart.RenderFidelity 속성이 너무 많은 메시에서 Precise로 설정된 장면이 일반적인 문제입니다.
과도한 그림자 생성 - 그림자를 처리하는 것은 비용이 많이 드는 프로세스이며, 그림자를 생성하는 빛이 있는 객체가 많거나 작은 부분이 영향을 받는 경우 성능 문제가 발생할 수 있습니다.
높은 투명도 과다 사용 - 부분적으로 투명한 객체를 서로 가까이 배치하면 엔진이 겹치는 픽셀을 여러 번 렌더링해야 하며, 이는 성능 저하로 이어질 수 있습니다. 이 문제를 식별하고 수정하는 방법에 대한 자세한 내용은 레이어화된 투명도 삭제를 참고하십시오.
불필요한 스킨드 MeshPart 이동 - Humanoid가 없는 모델에 속하는 스킨드 MeshPart는 공간적으로 구성된 FastCluster를 사용하여 그룹화됩니다. 이 MeshPart가 이동할 때마다 공간 클러스터에 계속 추가하고 제거해야 하므로 클러스터를 재구성해야 하며 성능에 영향을 미칩니다.
- 효과적인 해결 방법은 모델 내에 Humanoid를 삽입하는 것입니다. Humanoid의 존재는 기본적인 공간 클러스터링 동작을 무시하고 전체 모델에 대해 단일의 통합된 FastCluster 사용을 의무화합니다. 결과적으로 위치 업데이트가 클러스터 재구성을 필요로 하지 않게 되어 성능 병목 현상이 완화됩니다. 이 기술은 이동이 예상되는 MeshPart에만 사용해야 하며, 메모리 오버헤드를 발생시킬 수 있고 공간 최적화의 이점을 무효화할 수 있습니다. 이러한 유형의 변경을 한 후 항상 게임을 프로파일링하는 것을 추천합니다. 추가 정보는 휴머노이드 성능 팁을 참조하십시오.
Model 내의 부품 과다 - 모델 내에 너무 많은 부품이 있을 경우, 속성 변경 가능성으로 인해 재구성이 더 자주 발생할 수 있습니다. FastCluster를 사용할 때 모델 내의 부품 균형을 맞추는 것이 중요합니다.
완화
동일한 메시 인스턴싱 및 고유 메시 수 줄이기 - 모든 동일한 메시가 동일한 기본 자산 ID를 갖도록 보장하면, 엔진이 이를 인식하고 단일 드로우 호출에서 렌더링할 수 있습니다. 매핑에 있는 메시를 한 번만 업로드한 후 스튜디오에서 복제하여 재사용하는 것이 좋습니다. 그렇지 않으면 대규모 맵을 가져오는 동안 동일한 메시가 별개의 콘텐츠 ID를 갖고 엔진에 의해 고유 자산으로 인식될 수 있습니다. 패키지는 객체 재사용을 위한 유용한 메커니즘입니다.
프루닝 - 프루닝은 최종 렌더링 프레임에 포함되지 않는 객체의 드로우 호출을 제거하는 프로세스를 설명합니다. 기본적으로 엔진은 카메라의 시야(frustum) 밖에 있는 객체에 대한 드로우 호출을 건너뛰고(프러스텀 프루닝) 다른 객체에 의해 가려진 부분, 메시 및 지형에 대해서도(가림 프루닝) 드로우 호출을 프루닝합니다. 실내 환경과 같은 특정 상황에서는 방 또는 포털 시스템을 구현하고 객체를 수동으로 프루닝할 수 있습니다.
모델의 세부 수준 줄이기 - 인스턴스 스트리밍을 활성화하고 세계 모델의 LevelOfDetail 속성을 SLIM로 설정하여 카메라에서 멀어질수록 최적화된 경량 SLIM 메시를 렌더링합니다.
아바타의 세부 수준 줄이기 - 인스턴스 스트리밍을 활성화하고 Workspace.EnableSLIMAvatars 속성을 설정하여 카메라에서 멀어질수록 어떤 수의 악세서리나 의상 층이 포함된 플랫폼 아바타 모델을 최적화된 경량 아바타 표현으로 렌더링합니다.
렌더링 충실도 줄이기 - MeshPart.RenderFidelity를 Automatic 또는 Performance로 설정합니다. 이를 통해 메시가 덜 복잡한 대안으로 하락하게 되어 드로우할 다각형 수를 줄일 수 있습니다.
적절한 부품 및 빛 객체에서 그림자 생성을 비활성화 - Roblox 엔진은 클라이언트 그래픽 품질 수준이 낮아질수록 그림자 품질을 자동으로 저하시키며, 품질 수준이 4 미만일 때는 그림자를 완전히 비활성화합니다. 그러나 그림자가 활성화되어 있을 때 성능을 개선하고 그림자가 유지될 가능성을 높이기 위해 조명 객체 및 부품에 대한 그림자 생성을 선택적으로 비활성화할 수 있습니다. 다음은 편집 시 또는 런타임 중 동적으로 적용할 수 있는 몇 가지 최적화 사례입니다:
BasePart.CastShadow 속성을 사용하여 그림자가 보이지 않을 가능성이 있는 작은 부품에서 그림자 생성을 비활성화합니다. 이 전략은 사용자 카메라에서 멀리 떨어진 부품에 적용할 때 특히 효과적입니다.
가능할 때 이동하는 객체에서 그림자를 비활성화합니다.
그림자를 생성할 필요가 없는 조명 인스턴스에서 Light.Shadows를 비활성화합니다.
조명 인스턴스의 범위와 각도를 제한합니다.
조명 인스턴스를 줄입니다.
실내 환경에서는 특정 범위 외부에 있는 조명을 비활성화하거나 방 단위로 비활성화하는 것을 고려합니다.
마이크로 프로파일러 범위
| 범위 | 관련 계산 |
| Prepare and Perform | 전체 렌더링 |
| Perform/Scene/computeLightingPerform | 조명 그리드 및 그림자 업데이트 |
| LightGridCPU | 복셀 조명 그리드 업데이트 |
| ShadowMapSystem | 그림자 맵핑 |
| Perform/Scene/UpdateView | 렌더링 준비 및 파티클 업데이트 |
| Perform/Scene/RenderView | 렌더링 및 후처리 |
네트워킹 및 복제
네트워킹 및 복제는 데이터가 서버와 연결된 클라이언트 간에 전송되는 프로세스를 설명합니다. 클라이언트와 서버 간의 정보는 매 프레임마다 전송되지만, 더 큰 양의 정보는 더 많은 계산 시간을 요구합니다.
공통 문제
과도한 원격 트래픽 - RemoteEvent 또는 RemoteFunction 객체를 통해 많은 양의 데이터를 전송하거나 매우 자주 호출하는 것은 매 프레임에서 수신 패킷을 처리하는 데 많은 CPU 시간이 소요되게 할 수 있습니다. 일반적인 실수는 다음과 같습니다:
- 복제할 필요가 없는 데이터를 매 프레임마다 복제합니다.
- 사용자 입력에 따라 데이터를 복제하되, 이를 조절할 메커니즘이 없이 합니다.
- 필요 이상의 데이터를 전송합니다. 예를 들어, 아이템을 구매할 때 플레이어의 전체 인벤토리를 전송하는 것 보다 구매한 아이템의 세부 정보만 전송하는 것이 좋습니다.
복잡한 인스턴스 트리의 생성 또는 제거 - 서버의 데이터 모델에 변경이 발생하면, 연결된 클라이언트로 복제됩니다. 이는 실행 시간에 맵과 같은 큰 인스턴스 계층 구조를 생성하고 파괴할 경우 매우 네트워크 집약적일 수 있습니다.
일반적인 원인은 애니메이션 편집기 플러그인에서 저장된 복잡한 애니메이션 데이터입니다. 게임이 발표되기 전에 이들을 제거하지 않으면 애니메이션 모델이 정기적으로 복제될 때 많은 양의 데이터가 불필요하게 복제됩니다.
서버 측 TweenService - 서버 측에서 객체를 트윈하기 위해 TweenService를 사용하는 경우, 트윈된 속성이 매 프레임마다 각 클라이언트에 복제됩니다. 이는 클라이언트의 지연 변동으로 인해 트윈이 흔들리게 되고, 불필요한 네트워크 트래픽을 유발합니다.
완화
불필요한 복제를 줄이기 위해 다음과 같은 전술을 사용할 수 있습니다:
- 원격 이벤트를 통한 대량 데이터 전송을 피하십시오. 대신 필요한 데이터를 더 낮은 빈도로 전송하십시오. 예를 들어, 캐릭터의 상태는 매 프레임마다 복제하는 것보다 변경될 때만 복제하는 것이 좋습니다.
- 복잡한 인스턴스 트리를 청크로 나누어 여러 프레임 전반에 걸쳐 작업을 분산시킵니다.
- 애니메이션 메타데이터를 정리하십시오, 특히 리그의 애니메이션 디렉토리를 가져온 후.
- 불필요한 인스턴스 복제를 제한하십시오. 특히 서버가 생성된 인스턴스를 알 필요가 없는 경우에는 더욱 그렇습니다. 여기에는:
- 폭발이나 마법 주문 폭발과 같은 시각적 효과입니다. 서버는 결과를 결정할 수 있는 위치만 알면 되며, 클라이언트는 로컬에서 비주얼을 생성할 수 있습니다.
- 1인칭 아이템 뷰 모델입니다.
- 서버가 아닌 클라이언트에서 객체를 트윈하십시오.
마이크로 프로파일러 범위
| 범위 | 관련 계산 |
| ProcessPackets | 이벤트 호출 및 속성 변경과 같은 수신 네트워크 패킷을 처리합니다. |
| Allocate Bandwidth and Run Senders | 서버와 관련된 나가는 이벤트 |
자산 메모리 사용
클라이언트의 메모리 사용을 개선할 수 있는 가장 큰 영향 기제로는 인스턴스 스트리밍을 활성화하는 것이 있습니다.
인스턴스 스트리밍
인스턴스 스트리밍은 필요하지 않은 데이터 모델의 부분을 선택적으로 로드 아웃하여 부하 시간을 상당히 줄이고 메모리 압박에 직면했을 때 클라이언트가 충돌을 방지할 수 있는 능력을 높일 수 있습니다.
메모리 문제를 겪고 있고 인스턴스 스트리밍이 비활성화되어 있는 경우, 게임을 업데이트하여 이를 지원하는 것을 고려하십시오. 특히 귀하의 3D 세계가 큰 경우에 더욱 그렇습니다. 인스턴스 스트리밍은 3D 공간 내의 거리 기반으로 작동하므로 넓은 세계가 자연스럽게 더 많은 이점을 누릴 수 있습니다.
인스턴스 스트리밍이 활성화된 경우, 공격성을 높일 수 있습니다. 예를 들어:
- 가능한 한 Enum.ModelStreamingMode.Persistent의 사용을 줄입니다. 호환성 조치로 사용하고 있다면 스크립트를 업데이트해야 할 수 있습니다.
- Workspace.StreamingMinRadius 및 Workspace.StreamingTargetRadius를 줄입니다.
스트리밍 옵션 및 이점에 대한 자세한 내용은 스트리밍 속성을 참조하십시오.
기타 공통 문제
자산 중복 - 같은 자산을 여러 번 업로드하여 서로 다른 자산 ID가 생성되는 경우가 많습니다. 이는 동일한 콘텐츠가 메모리에 여러 번 로드되는 원인이 될 수 있습니다.
과도한 자산량 - 자산이 동일하지 않은 경우에도, 같은 자산을 재사용하고 메모리를 절약할 기회를 놓치는 경우가 종종 있습니다.
오디오 파일 - 오디오 파일은 메모리 사용에 예상치 못한 기여를 할 수 있으며, 게임의 일부분에 필요한 것만 로드하지 않고 모든 파일을 한 번에 로드하면 특히 그렇습니다. 전략에 대한 상세 정보는 로드 시간을 참조하십시오.
고해상도 텍스처 - 텍스처의 그래픽 메모리 소비는 디스크에서의 텍스처 크기와 관련이 없으며, 텍스처의 픽셀 수가 메모리 사용량을 결정합니다. 예를 들어, 1024x1024 픽셀 텍스처는 512x512 텍스처의 네 배의 그래픽 메모리를 소모합니다.
Roblox에 업로드된 이미지는 고정 형식으로 트랜스코딩되므로 픽셀당 바이트 수와 연관된 색상 모델의 이미지를 업로드하는 것이 메모리 이점이 없습니다. 이미지를 업로드하기 전에 압축하거나 필요하지 않은 이미지의 알파 채널을 제거하면 디스크 공간은 줄어들 수 있지만 메모리 사용량 개선에는 기여하지 않습니다.
게임이 로드될 때, 엔진은 자동으로 낮은 품질 텍스처로 시작한 다음 사용 가능한 장치 메모리, 카메라 거리, 텍스처가 차지하는 스크린 공간 양 및 기타 요인에 따라 품질을 높입니다. 그럼에도 불구하고 텍스처의 크기를 전략적으로 조정하면 게임의 메모리 사용량이 개선될 수 있습니다.
완화
자산을 한 번만 업로드하십시오 - 동일한 자산 ID를 객체 간에 재사용하고, 동일한 자산, 특히 메시 및 이미지를 여러 번 별도로 업로드하지 않도록 합니다.
중복 자산을 찾고 수정하십시오 - 여러 번 서로 다른 ID로 업로드된 동일한 메시 부분 및 텍스처를 찾습니다.
- 자산의 유사성을 자동으로 감지하는 API는 없지만, 장소 내 모든 이미지 자산 ID를 수집하고 다운로드하여 외부 비교 도구를 사용하여 비교할 수 있습니다.
- 메시 부분의 경우, 고유 메시 ID를 가져와 크기에 따라 정리하여 수동으로 중복 메시를 확인하는 것이 최선의 전략입니다.
- 서로 다른 색상을 위해 별도의 텍스처를 사용하는 대신 단일 텍스처를 업로드하고 SurfaceAppearance.Color 속성을 사용하여 다양한 색조를 적용하십시오.
개별적으로 맵의 자산을 가져오고 구성합니다 - 전체 맵을 한 번에 가져오는 대신, 개별적으로 자산을 가져오고 재구성하십시오. 가져오기 도구는 메시 중복을 제거하지 않으므로, 많은 개별 바닥 타일로 구성된 대규모 맵을 가져오는 경우 각 타일이 별도의 자산으로 가져와지게 되어 메모리 및 성능 문제를 초래할 수 있습니다.
이미지의 픽셀 수를 필요한 수치로 제한 - 한 객체가 화면에서 차지하는 물리적인 공간이 크지 않은 이상, 일반적으로 512x512 픽셀을 초과할 필요가 없습니다. 대부분의 작은 이미지는 256x256 픽셀 이하로 설정하는 것이 좋습니다.
트림 시트를 사용하여 3D 맵에서 텍스처 재사용을 극대화합니다. 트림 시트를 만드는 방법과 예제는 트림 시트 만들기를 참조하십시오.
여러 개의 더 작은 UI 이미지를 단일 이미지로 로드하기 위해 스프라이트 시트를 사용하는 것도 고려할 수 있습니다. 이후 ImageLabel.ImageRectOffset 및 ImageLabel.ImageRectSize를 사용하여 시트의 일부를 표시할 수 있습니다.
로드 시간
많은 게임이 커스텀 로딩 화면을 구현하고 ContentProvider:PreloadAsync() 메서드를 사용하여 에셋을 요청하여 이미지, 사운드 및 메시가 백그라운드에서 다운로드되게 합니다.
이 접근 방식의 장점은 게임의 중요 부분이 팝인 없이 완전히 로드되도록 보장할 수 있다는 것입니다. 그러나 일반적인 실수는 실제로 필요한 것보다 더 많은 자산을 미리 로드하기 위해 이 방법을 과도하게 사용하는 것입니다.
나쁜 관행의 예는 전체 Workspace를 로드하는 것입니다. 텍스처 팝인을 방지할 수는 있지만 로드 시간이 크게 증가합니다.
또는 ContentProvider.RequestQueueSize를 활용하여 모든 요청 자산이 로딩을 완료했는지 확인하는 비슷한 관행입니다. 그러나 이 방법은 로드 시간을 상당히 증가시키는 같은 문제를 안고 있으며, 불안정한 방법이 될 수 있습니다.
대신, 필요한 상황에서만 ContentProvider:PreloadAsync()를 사용하십시오. 여기에는:
- 로딩 화면의 이미지.
- 버튼 배경 및 아이콘과 같은 게임 메뉴의 중요한 이미지.
- 시작 또는 스폰 영역의 중요한 자산.
많은 양의 자산을 로드해야 하는 경우, 로딩 건너뛰기 버튼을 제공하는 것을 추천합니다.