March 15, 2018

Battle Breakers의 청크 다운로드용 최적화

저자: Ben Zeigler

중간 규모에서 대규모의 용량을 가진 게임을 발매할 때는 초기 다운로드 용량을 줄이고, 패치를 더 빠르게 하고, 로드 시간을 최적화하기 위해 게임 콘텐츠를 여러 개의 청크로 분할하는 것이 일반적입니다. 콘솔과 PC 플랫폼에서는 보통 적은 개수의 큰 청크로 분할하는 형태가 최적이며, 모바일 게임의 경우에는 일반적으로 많은 개수의 작은 청크로 분할하는 것이 더 낫습니다. 이 프로세스는 콘텐츠를 독립된 청크로 나누는 것으로 시작하며, 언리얼 엔진 4.19는 이런 구성을 가능하게 해 주고, 분석과 최적화를 할 수 있게 해 주는 다양한 툴들을 제공합니다. 이 포스트에서는 저희가 배틀 브레이커스(Battle Breakers)를 제작하면서, 초기 다운로드 용량을 최소한으로 줄이는 동시에 필요에 따라 콘텐츠가 추가로 다운로드 되는 것을 가능하도록 만드는데 사용한 툴과 테크닉에 대해 알아볼 것입니다.

SHARE_BattleBreakersOptimizations.jpg

청크 구성

청크란 독립적으로 디플로이 및 다운로드 될 수 있는 애셋(게임 콘텐츠)의 모음입니다. 기본적으로 하나의 프로젝트는 청크 0라는 하나의 청크만을 가지며, 모든 애셋은 이 청크에 포함됩니다. 애셋들을 각각 다른 청크에 할당한 후, 언리얼 엔진 4의 스테이징 시스템은 각 청크에서 게임의 타깃 플랫폼을 기반으로 적절한 플랫폼 특화 디플로이먼트 시스템에 통합될 수 있는 개별적, 독립적인 .pak 파일을 만들어냅니다. 배틀 브레이커스의 경우, 청크들은 HttpChunkInstaller 플러그인을 사용해 다운로드되는 다른 형태의 파일들로 변경됩니다. 쿠커 콜백을 사용해 청크를 만드는 예전 방식이 있기는 하지만, 4.17부터는 애셋매니저(AssetManager)가 이 작업을 할 수 있는 가장 쉽고 유연한 방법입니다. 이 기능들을 이용하려면, 게임은 패키징 문서에서 설명된 대로 청크를 만들도록 구성되어야만 합니다.

배틀 브레이커스(코드명 “월드 익스플로러”)는 얼마간 개발 과정에 있었으며, 따라서 첫번째 단계는 이 게임이 애셋 매니저를 사용하도록 전환하는 것이었습니다. 이 작업을 하기 위해, 저희는 게임을 분석하여 어떤 게임 콘텐츠가 프라이머리 애셋(Primary Assets)이 되어야 하는지 찾았고, 이것을 AssetManagerSettings에 구성했습니다. 배틀 브레이커스의 경우 28가지 종류의 서로 다른 프라이머리 애셋 유형이 있었지만, 가장 흥미로운 콘텐츠는 맵과 캐릭터였습니다. 배틀 브레이커스에는 독특한 아트를 가진 수백 가지의 캐릭터들이 있지만, 대부분의 플레이어들은 이 캐릭터들 중에서 일부에만 액세스 할 수 있을 것이었습니다. 또한 디자이너들은 플레이어들이 게임 플레이에 입문하는 과정에서 난관을 겪는 것을 줄이기 위해 모든 입문/튜토리얼 콘텐츠가 초기 다운로드에 모두 포함되기를 원했습니다. 따라서 배틀 브레이커스에 필요한 청크 모델은 총 세 가지의 서로 다른 유형의 청크를 가지는 것이었습니다: 입문 콘텐츠는 청크 0에 넣고, 플레이어가 입문을 마칠 경우 다운로드 될, 수동으로 0보다 높은 숫자를 매겨 다른 맵 및 공유된 게임플레이 콘텐츠를 포함한 청크가 있고, 그리고 필요에 따라 다운로드해야 하는, 다른 캐릭터들이 포함된 자동 생성 청크가 있습니다. DefaultGame.ini의 구문 몇 가지를 사례로 들어보자면 다음과 같습니다:
 
[/Script/Engine.AssetManagerSettings]
+PrimaryAssetTypesToScan=(PrimaryAssetType="Character",AssetBaseClass=/Script/WorldExplorers.WExpCharacterDefinition,bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Characters/Classes"), (Path="/Game/World/Map")),Rules=(Priority=1,CookRule=AlwaysCook))
+PrimaryAssetTypesToScan=(PrimaryAssetType="WExpChunkDefinition",AssetBaseClass=/Script/WorldExplorers.WExpChunkDefinition,bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Chunks")))
매뉴얼 청크를 구현하기 위해, 저희는 온보딩 청크, 자동 다운로드 청크, 그리고 필요할 때 다운로드해야 하는 청크를 구별할 줄 아는, 배틀 브레이커스를 위한 UPrimaryAssetLabel의 특정한 서브클래스를 만들었습니다. 각 라벨은 수동으로 정의한 ChunkId와 Rules 섹션의 Priority 값을 가지며, 에디터에서 아티스트와 디자이너에 의해 수정될 수 있는(4.18에서의 새로운 기능입니다) 모음을 지정합니다. 모음 속의 모든 애셋은 더 높은 중요도의 프라이머리 애셋/라벨에 의해 관리를 받지 않는 한, 그 청크에 들어갈 수 있을 것입니다. 자동 생성 캐릭터 청크를 만들기 위해, 저희는 청크에 할당하는 각 캐릭터마다 SetPrimaryAssetRules를 호출하는 UAssetManager의 배틀 브레이커스에 특화된 서브클래스에 청크 1000부터 시작하는 코드를 추가했습니다. 이 청크 할당은 시간이 지나더라도 변하지 않도록 간단한 .csv 파일에서 저장/불러오기 됩니다. 또한 우리는 특정 프라이머리 애셋 ID에 의해 관리되는 애셋을 결정하기 위해 호출되는 가상 함수 ShouldSetManager에서 몇 가지 게임 특화 변경을 만들어야 했습니다. 각 게임은 해당 특정 게임의 필요에 따라 콘텐츠를 청크에 할당하는 자신만의 규칙이 필요할 것이었습니다. AssetManage는 필요에 따라 서브클래스 및 커스터마이즈 되어 이것을 지원하도록 설계되었습니다.

BattleBreakers_Screen1.jpg

배틀 브레이커스에서 사용한 또 다른 툴은 청크 간의 부모/자식 관계를 지정하는 ChunkDependencyInfo 였습니다. 이것을 구성하기 위해 DefaultEngine.ini에 다음과 같은 구문을 추가했습니다.
 
[/Script/UnrealEd.ChunkDependencyInfo]
+DependencyArray=(ChunkID=100,ParentChunkID=11)
+DependencyArray=(ChunkID=101,ParentChunkID=11)

이 구문은 청크 100과 청크 101이 청크 11의 자식이라고 지정합니다. 즉 청크 11에 포함된 모든 애셋은 청크 100이나 청크 101에 포함되지 않을 것이라는 뜻입니다. 기본적으로 모든 애셋은 청크 0의 자식으로, 이는 즉 초기 스타트업 청크에 있는 것들은 다른 청크에 쓸데없이 복제되지 않을 것이라는 뜻입니다. 청크의 계층구조를 올바르게 구성함으로써, 우리는 플레이어의 디바이스에 필요한 디스크 공간을 최소화할 수 있었고, 콘텐츠 복제를 피할 수 있었습니다.

청크 할당 분석

초기 청크 구성을 끝낸 후, 원하던 결과가 나오고 있는지 확인을 했습니다. 하지만 그렇지 않았습니다. 이 문제의 추적을 돕기 위해, 4.19에 어떤 애셋이 어느 청크로 들어가고 있는지, 그리고 그 이유는 무엇인지 보여주는 새로운 트래킹 툴을 추가했습니다. 첫번째 툴은 에디터에서 Windows --> Developer Tools --> Asset Audit 순으로 액세스할 수 있는 Asset Audit 창이었습니다. Asset Audit 창을 연 후, Add Chunks 버튼을 클릭하면 게임 속에 존재하는 모든 청크들의 요약이 창에 출력됩니다:  

ChunkAssetAudit_01.jpg

각 청크를 분석하려면, 청크를 우클릭 한 다음 Size Map 이나 Reference Viewer 중 하나를 사용해, 청크가 무엇을 포함하고 있는지 보면 됩니다. Size Map 은 최고 레벨의 애셋(혹은 프라이머리 애셋 ID) 연관성을 기반으로 그룹화되고 디스크 용량이 조정된 콘텐츠들의 전체적인 분석을 보여주고, Reference Viewer 는 약간의 필터 및 옵션과 함께 각 애셋 간의 부모자식 관계를 보여줍니다. 한 예로, 우리는 Chunk_7에 Reference Viewer 를 사용해 이것이 85MB의 콘텐츠를 가지고 있는 이유를 볼 수 있습니다:
  
Chunk7.png

여기서 Chunk_7의 레퍼런스 뷰를 보고 Chunk_Onboarding 이 레퍼런스 중 하나라는 것을 알았으므로, 이것을 더블클릭하여 레퍼런스 뷰어의 초점을 여기에 맞췄습니다. 녹색 상자는 프라이머리 애셋이고, 회색 상자는 실제, 디스크상 애셋입니다. 프라이머리 애셋부터 디스크 애셋까지의 레퍼런스는 매니지먼트 레퍼런스라 부르며, 이것을 보려면 좌상단 창에 있는 “Show Management References” 옵션을 켜야 합니다. 이 경우 이것들이 명시적 매니지먼트 레퍼런스라는 점을 하얀 선이 표시해주며, 이 애셋이 프라이머리 애셋의 일부로 명백하게 포함된다는 것을 보여줍니다. 이 경우에는 위에 설명된 모음이겠죠. 만약 Search Breadth Limit 옵션을 끄고 스크롤을 내리면, 암시적 매니지먼트 레퍼런스라는 것을 나타내는 분홍색 선을 보게 될 것입니다. 이것은 명시적으로 관리되는 애셋의 재귀 레퍼런스 때문에 매니지먼트 세트에 자동으로 포함되는 애셋들입니다.

청크 할당을 조사하는 또 다른 방법은 콘텐츠 브라우저에서 자신이 알고 싶은 애셋을 찾아서, 우클릭 한 다음, Reference Viewer 를 선택하고, 그런 다음 Show Management References 를 수동으로 켜는 것입니다. 그러면 특정 프라이머리 애셋이 해당 애셋을 관리하고 있을 경우, 어떤 프라이머리 애셋이 관리하고 있는지 보여줍니다:

AssetReference.png

이 사례에서는 애셋이 WExpCharacterDisplay 프라이머리 애셋에 의해 암시적으로 관리된다는 것을 볼 수 있습니다. 만약 이 프라이머리 애셋을 더블클릭하면, 이것이 청크 1167에 할당되는 것을 볼 수 있을 것입니다. 이것은 좌상단 Display 애셋의 하드 레퍼런스로 인한 것입니다. 만약 이 애셋을 더블클릭하면, 이것이 프라이머리 애셋에 의해 명시적으로 관리되고 있다는 것을 볼 수 있을 것입니다.

청크 할당을 조사하는 또 다른 방법은 복수의 실제 애셋을 콘텐츠 브라우저에서 선택해서든, 아니면 Asset Audit 창의 bulk add 옵션을 사용해서든, 둘 중 어떤 방법으로든 Asset Audit 창에 추가하는 것입니다. 이 사례에서, 우리는 모든 Texture2D 애셋을 Asset Audit 창에 추가한 다음, 용량에 따라 정렬했습니다. 또한 일부 Columns 도 해제해서 가독성을 높였습니다:
  
TextureAuditBB_01.png

이 사례에서, 가장 큰 텍스처 대부분은 청크 2에 들어갑니다. 그런 다음 각 텍스처를 우클릭하고 Reference Viewer 로 가서 그것이 왜 그 청크에 있는지 볼 수 있습니다. 또한 Asset Audit 창은 다른 유용한 정보들도 가지고 있습니다. 메모리 용량은 ResourceSize 를 통해 추정된 것이며, Total Usage 는 프라이머리 애셋 Priority로 스케일된, 해당 텍스처를 레퍼런스하는 프라이머리 애셋의 숫자입니다.

디스크 용량 최적화

청크 툴이 원하는 대로 구성되었는지 확인하기 위해 분석 툴을 사용했지만, 몇몇 청크는 아직도 지나치게 컸습니다. 이것을 고치기 위해, 우리는 같은 툴을 사용해서 모든 디스크 공간이 어디서 사용되었는지 트래킹했습니다. 이 툴과 테크닉은 여러분의 게임이 아직 청크 작업을 위해 구성되지 않았더라도 실제로 사용될 수 있으며, 4.19에서는 획기적으로 개선되었습니다. 작업을 시작하면서 디스크에서 실제로 쿠킹된 용량을 확인하고 싶었고, 그래서 다시 Asset Audit 창의 청크 뷰로 돌아갔습니다.

기본적으로, 위 스크린샷에도 나타나 있듯이, 이 창은 에디터 디스크 용량을 보여주며, 이것은 실제 쿠킹된 빌드에서 애셋들의 용량과는 꽤 다를 수 있습니다. 하지만 빌드를 로컬에서 쿠킹했다면, 이 드롭다운 메뉴는 콘텐츠를 쿠킹한 대상 플랫폼들로 채워져 있을 것이기 때문에, 발매한 게임에는 실제로 어느 정도의 용량으로 디플로이 될지 볼 수 있습니다. Custom 을 선택해도 하드 드라이브에서 특정 DevelopmentAssetRegistry.bin 파일을 불러오는 것이 가능합니다. 만약 빌드 프로세스가 해당 파일을 아카이브 하도록 구성되어 있다면, 이것은 프로덕션 빌드의 실제 크기를 구하는데 사용될 수도 있습니다.

일단 원하는 플랫폼을 선택했다면, 이것을 우클릭한 다음 Size Map 을 선택해 전체 디스크 용량이 어떻게 될지 개요를 확인합니다. 다음은 아직 최적화를 하지 않은 배틀브레이커스의 Chunk_0를 사례로 든 것입니다:

ChunkSizeMap.png 
여기서는 디스크 용량에 따라 스케일된 청크 내의 모든 애셋들을 보여줍니다. 색칠된 상자는 각각 다른 최고 레벨 애셋 혹은 프라이머리 애셋 ID를 나타냅니다. 마우스 휠로 줌 인을 하거나 더블클릭을 하면 지정된 애셋 모음을 더 자세히 볼 수 있으며, 휠로 줌 아웃을 하거나 좌상단 화살표를 사용해 이전 보기로 돌아갈 수 있습니다. 애셋을 우클릭 하면 그것을 레퍼런스 뷰어나 Asset Audit 로 가져와 좀 더 자세하게 조사할 수 있습니다. Size Map 뷰는 청크 및 프라이머리 애셋 매니지먼트 레퍼런스에 대한 정보를 사용해 콘텐츠 필터링을 하므로, 해당 청크에 실제로 있는 애셋들만이 표시됩니다.

Size Map 애셋은 용량에 따라 스케일되기 때문에, 즉시 몇 가지 명확한 최적화 경로를 가리켜 줍니다. 대형 블룸 텍스처와 몇몇 VR 관련 애셋은 모바일 타이틀에 필요하지 않으므로, 빌드에서 완전히 없애버렸습니다. 또 다른 경우에는 특정 애셋을 청크 간에 이동시키도록 에디터 모음을 수정하였습니다. 디스크 용량은 일반적으로 메모리 크기를 판단하기에 좋은 자료이기 때문에, 메모리 최적화에도 이것과 똑같은 기술을 사용할 수 있습니다. 실제로 쿡타임에는 특정 플랫폼의 런타임에서 메모리 사용이 얼마나 되는지 계산할 수 없으므로, Size Map 을 메모리 용량으로 바꾸면 Resource Size 에 기반한 조금 부정확한 근사치를 사용하게 됩니다.

이 툴과 게임에 특화된 최적화를 사용해, 저희는 유저들이 더 빠르게 다운로드할 수 있으며, 변경사항이 생길 경우 디자이너들에 의해 업데이트 될 수 있는 배틀 브레이커스의 청크 구성을 만들 수 있었습니다. 이런 툴들은 모든 게임의 구조화와 최적화를 돕는데 사용될 수 있습니다.