Unity Addressable Asset system어드레서블 에셋 시스템으로 메모리 최적화하기
고품질 게임은 메모리 내외부로 에셋을 효율적으로 스트리밍하는 기능을 필수로 갖추고 있어야 합니다. 저는 유니티에서 컨설턴트로 일하며 수많은 고객 프로젝트에서 성과를 개선하기 위해 최선을 다해 왔습니다. 지난 경험을 바탕으로 Unity 어드레서블 에셋 시스템을 활용하여 콘텐츠 로딩 전략을 강화하는 방법에 관한 몇 가지 팁을 공유하려 합니다.
메모리는 항상 과도하게 사용되지 않도록 관리해야 하며 특히 프로젝트를 새로운 플랫폼에 이식할 때라면 더욱 신중하게 관찰해야 합니다. 어드레서블을 사용하면 불필요한 에셋의 로드를 방지하는 약한 참조를 도입하여 런타임 메모리를 개선할 수 있습니다. 약한 참조는 참조된 에셋이 메모리 내부 및 외부에 로드되는 시점을 제어할 수 있음을 의미하며, 어드레서블 시스템 역시 필요한 모든 종속성을 찾아서 로드하게 됩니다. 이번 블로그 포스팅에서는 Unity 어드레서블 에셋 시스템을 사용할 때 당면할 수 있는 다양한 시나리오와 문제를 제시하며, 이를 인지하고 신속하게 해결하는 방법을 다룹니다.
l 인벤토리 예시
권장사항을 소개하기 위해 다음과 같은 방식으로 설정된 간단한 예시를 활용하려 합니다. 씬에는 InventoryManager 스크립트가 있으며 세 가지 인벤토리 에셋인 검, 보스 검, 방패 프리팹에 대한 참조가 있습니다.세 에셋이 게임플레이 중에 항상 필요한 것은 아닙니다. 이 예시가 사용된 프로젝트 파일은 제 GitHub에서 다운로드할 수 있습니다. 이번 예시에서는 프리뷰 패키지인 Memory Profiler를 사용하여 런타임의 메모리를 실시간으로 확인합니다. Unity 2020 LTS에서는 패키지 관리자에서 이 패키지를 설치하기 전에 먼저 프로젝트 설정에서 프리뷰 패키지를 활성화해야 합니다. Unity 2021.1을 사용하고 있다면 패키지 관리자 창의 추가 메뉴(+)에서 Add package by name 옵션을 선택합니다. “com.unity.memoryprofiler”라는 이름을 사용합니다.l 1단계 : 강한 참조, 어드레서블 없음
가장 기본적인 구현으로 시작해서 어드레서블 콘텐츠를 설정하는 최적의 방법으로 진행해 보겠습니다. 씬에 있는 MonoBehaviour 내의 프리팹에 강한 참조(인스펙터에서 직접 할당, GUID로 추적됨)를 적용합니다.
씬을 로드하면 씬에 있는 모든 오브젝트도 종속성과 함께 메모리에 로드됩니다. 이는 InventorySystem에 포함된 모든 프리팹이 모든 종속성(텍스처, 메시, 오디오 등)과 함께 메모리에 상주하게 됨을 의미합니다. 빌드를 생성하고 메모리 프로파일러로 스냅샷을 촬영해 보면 에셋의 텍스처가 아직 하나도 인스턴트화되지 않았음에도 불구하고 이미 메모리에 저장되어 있는 것을 확인할 수 있습니다.아이템의 텍스처가 아직 인스턴트화되지 않았음에도 메모리에 저장되어 있습니다.
문제점: 현재 필요하지 않은 에셋이 메모리에 로드되어 있습니다. 인벤토리 아이템이 많은 프로젝트에서는 런타임 메모리에 상당한 부담이 됩니다.l 2단계 : 어드레서블 구현원치 않는 에셋이 로드되지 않도록 하기 위해 어드레서블을 사용하도록 인벤토리 시스템을 변경합니다. 직접 참조 대신 에셋 참조를 사용하면 오브젝트가 씬과 함께 로드되지 않습니다. 인벤토리 프리팹을 어드레서블 그룹(Addressables Group)으로 옮기고 API를 사용하여 오브젝트를 인스턴스화하고 릴리스하도록 InventorySystem을 변경해 보겠습니다.
플레이어를 빌드하고 스냅샷을 찍습니다. 아직 에셋이 메모리에 있지 않음을 확인할 수 있으며, 에셋이 아직 인스턴스화되지 않았으므로 이는 바람직한 상황입니다.메모리에서 TextMeshPro 텍스처를 제외한 인벤토리 아이템 텍스처는 보이지 않습니다.
모든 아이템을 인스턴스화하여 에셋과 함께 메모리에 제대로 표시되는지 확인합니다.
문제점: 모든 아이템을 인스턴스화하고 보스 검을 사라지게 하는 경우, 사용되지 않는 보스 검의 텍스처 “BossSword_E ”가 여전히 메모리에 있는 것을 볼 수 있습니다. 이는 에셋 번들을 부분적으로 로드할 수 있는 반면, 자동으로 부분 언로드할 수는 없기 때문입니다. 이 동작은 모든 인벤토리 프리팹을 포함한 예시의 단일 에셋 번들처럼 에셋이 많은 번들에서 특히 문제가 될 수 있습니다. 전체 에셋 번들이 더 이상 필요하지 않게 되거나 비용이 많이 드는 CPU 작업인 Resources.UnloadUnusedAssets()를 호출할 때까지 번들에 있는 에셋은 하나도 언로드되지 않습니다.보스 검이 해제된 후에도 BossSword_E 텍스처가 메모리에 계속 남아 있게 됩니다.
l 3단계 : 소규모 번들이 문제를 해결하려면 에셋 번들의 구성 방식을 바꿔야 합니다. 지금은 모든 에셋을 에셋 번들 하나에 모두 포함시킨 단일 어드레서블 그룹을 사용하고 있지만, 각 프리팹마다 별도로 에셋 번들을 생성할 수도 있습니다. 이처럼 더 세분화된 에셋 번들을 사용하면 더 이상 필요하지 않은 에셋을 메모리에 유지하는 대규모 번들에 대한 문제를 완화할 수 있습니다.
변경하는 방법은 간단합니다.
어드레서블 그룹을 선택한 다음 Content Packaging & Loading > Advanced Options > Bundle Mode를 선택하고 Inspector로 이동하여 번들 모드를 Pack Together에서 Pack Separately로 바꿉니다.
Pack Separately를 사용하여 어드레서블 그룹을 빌드하면 어드레서블 그룹의 에셋마다 에셋 번들을 만들 수 있습니다.
이제 에셋과 번들이 다음과 같은 형태가 됩니다.
원래 테스트로 돌아가서 세 개의 아이템을 생성한 다음 보스 검을 제거해도 더 이상 불필요한 에셋이 메모리에 남지 않습니다. 전체 번들이 더 이상 필요하지 않게 되었으므로 이제 보스 검 텍스처가 언로드됩니다. 문제점: 세 아이템을 모두 생성하고 메모리를 캡처해 보면 중복된 에셋들이 메모리에 나타납니다. 예를 들어 ‘Sword_N’ 및 ‘Sword_D’의 텍스처 사본이 여러 개 표시됩니다. 번들 수만 변경했는데 왜 이러한 문제가 발생하는 것일까요?
l 4단계 : 에셋 중복 문제 해결위에서 발생한 문제를 해결하기 위해 앞서 생성한 세 번들에 들어가는 모든 요소를 고려해 보겠습니다. 번들에 배치한 것은 세 가지 프리팹이지만, 프리팹의 종속성으로 해당 번들에 암묵적으로 추가된 추가 에셋도 있습니다.
예를 들어 검 프리팹 에셋에는 메시, 머티리얼, 텍스처 에셋도 포함되어야 합니다. 이러한 종속성은 어드레서블의 다른 곳에서 명시적으로 포함되지 않은 경우 자신을 필요로 하는 각 번들에 자동으로 추가됩니다.Sword 및 BossSword의 에셋 번들에 동일한 종속성이 포함되어 있습니다.
어드레서블에는 번들 레이아웃을 진단할 수 있는 분석 창이 있습니다. Window > Asset Management > Addressables > Analyze를 열고 Bundle Layout Preview 규칙을 실행합니다. 여기에서 검 번들에 sword.prefab이 명시적으로 포함되어 있으나, 여러 암묵적인 종속성도 이 번들에 추가되어있음을 확인할 수 있습니다.동일한 창에서 Check Duplicate Bundle Dependencies를 실행합니다. 이 규칙은 현재 어드레서블 레이아웃을 기준으로 여러 에셋 번들에 중복 포함된 에셋을 주로 살펴봅니다.분석에 따르면 검 번들 사이에 중복된 텍스처와 메시가 있으며 세 번들 모두에 동일한 셰이더가 중복됩니다.
이러한 중복은 다음 두 가지 방법으로 방지할 수 있습니다.
종속성을 공유하도록 Sword, BossSword, Shield 프리팹을 동일한 번들에 배치합니다.
어드레서블의 다른 위치에 중복된 에셋을 명시적으로 포함합니다.
원치 않는 에셋이 메모리에 머무르지 않도록 하기 위해 여러 인벤토리 프리팹은 동일한 번들에 두지 않으며, 중복 에셋을 자체 번들(Bundle 4 및 Bundle 5)에 추가합니다. 중복 텍스처가 자체 번들에 명시적으로 포함됩니다.
분석 규칙(Analyze Rules)은 번들 분석 외에 Fix Selected Rules를 통해 문제가 되는 에셋을 자동으로 수정할 수도 있습니다. 이 버튼을 눌러 ‘Duplicate Asset Isolation’이라는 이름으로 새 어드레서블 그룹을 만듭니다. 여기에는 중복 에셋 네 가지가 포함되어 있습니다. 더 이상 필요하지 않은 기타 에셋이 메모리에 유지되지 않도록 이 그룹의 번들 모드를 Pack Separately로 설정합니다.l 5단계 : 대규모 프로젝트에서 에셋 번들 메타데이터 크기 줄이기이 에셋 번들 전략을 사용하면 대규모 작업 시 문제가 발생할 수 있습니다. 주어진 시간에 로드되는 에셋 번들마다 에셋 번들 메타데이터에 대한 메모리 오버헤드가 있습니다. 이러한 메타데이터는 지금의 전략을 수백 또는 수천 개의 인벤토리 아이템으로 확장할 경우 허용할 수 없는 수준의 메모리를 소모할 가능성이 있습니다. 어드레서블 문서에서 에셋 번들 메타데이터에 관해 자세히 알아보세요.
Unity 프로파일러에서 에셋 번들 메타데이터 메모리 비용을 확인할 수 있습니다. 메모리 모듈에서 메모리 스냅샷을 찍어보세요. Other > SerializedFile 카테고리를 살펴보면 됩니다.현재 SerializedFile 메모리로 1,819개 번들이 로드되어 있으며 크기는 총 263MB입니다.
메모리에는 로드된 에셋 번들마다 SerializedFile 엔트리가 있으며, 이 메모리는 번들의 실제 에셋이라기 보다는 에셋 번들 메타데이터에 해당됩니다. 이러한 메타데이터에는 다음이 포함되어 있습니다.
- 파일 읽기 버퍼 두 개
- 번들에 포함된 모든 고유한 타입을 나열하는 타입 트리
- 에셋을 나타내는 목차
이 세 가지 항목 중 파일 읽기 버퍼가 가장 큰 공간을 차지합니다. 이 버퍼의 크기는 PS4, Switch, Windows RT에서 64KB, 기타 플랫폼에서 7KB입니다. 위 예시에서는 번들 1,819개 x 64KB x 버퍼 2개이므로 버퍼만으로 227MB를 차지합니다.
버퍼의 수가 에셋 번들의 수에 따라 선형적으로 증가한다고 보면 런타임에 로드되는 번들의 수를 줄이는 것이 메모리를 줄이는 간단한 해법이 됩니다. 그러나 앞에서는 대규모 번들이 로드되지 않게 함으로써 원치 않는 에셋이 메모리에 유지되지 않도록 했습니다. 그렇다면 어떻게 해야 세분화를 유지하면서 번들의 수를 줄일 수 있을까요?
바람직한 첫 단계 중 하나는 애플리케이션 기준으로 에셋을 그룹화하는 것입니다. 각 애플리케이션을 기준으로 올바른 가정을 할 수 있다면 진행 중인 게임플레이 레벨에 따라 항상 로드되고 언로드되는 에셋을 함께 그룹화할 수 있습니다.
반면에 에셋이 필요하거나 필요하지 않은 경우를 정확하게 가정할 수 없는 상황도 있을 수 있습니다. 예를 들어 오픈 월드 게임을 만든다면 플레이어가 숲에서 얻은 아이템을 가지고 생물군 사이를 오갈 수 있으므로 숲 생물군의 모든 요소를 하나의 에셋 번들로 그룹화할 수는 없습니다. 플레이어에게 숲의 에셋 한 개가 계속 필요하므로 전체 숲 번들이 메모리에 유지됩니다.
다행히 세분화를 원하는 수준으로 유지하면서 번들의 수를 줄일 수 있는 방법이 있습니다. 이제 보다 스마트한 방식으로 번들의 중복을 없애 보겠습니다.
이전에 실행했던 빌트인 중복 해제 분석 규칙은 여러 번들에 있는 모든 에셋을 감지하고 하나의 어드레서블 그룹에 효율적으로 옮겨줍니다. 이 그룹을 Pack Separately로 설정하면 각 번들이 하나의 에셋을 갖게 됩니다. 물론 메모리 문제를 유발하지 않고 안전하게 합칠 수 있는 중복 에셋도 있습니다. 아래 다이어그램을 참고하세요. ‘Sword_N’ 및 ‘Sword_D’ 텍스처가 동일한 번들(Bundle 1 및 Bundle 2)의 종속성인 것은 알고 있는 사실입니다. 이 두 텍스처는 부모가 동일하기 때문에 메모리 문제가 유발되는 일 없이 안전하게 합칠 수 있습니다. 따라서 두 검 텍스처가 항상 함께 로드 또는 언로드됩니다. 특별히 하나의 텍스처만 사용하고 다른 하나는 사용하지 않는 경우는 없으므로, 텍스처 중 하나가 메모리에 남아 있을 염려를 할 필요가 없습니다. 이렇게 개선된 중복 제거 로직을 어드레서블 분석 규칙에서 구현할 수 있습니다. 기존의 CheckForDupeDependencies.cs 규칙을 수정했으며 전체 구현 코드는 인벤토리 시스템 예에서 확인할 수 있습니다. 이 단순한 프로젝트에서는 번들의 총 개수를 7개에서 5개로 줄이기만 했습니다. 하지만 어드레서블에 수백, 수천 또는 그 이상으로 많은 중복 에셋이 있는 애플리케이션을 생각해 보세요. 프로페셔널 서비스를 통해 Unknown Worlds Entertainment와 Subnautica에 관한 협업을 진행할 당시 빌트인 중복 제거 분석 규칙을 사용해 보니 이 프로젝트에는 처음에 총 8,718개 번들이 있었습니다. 중복 해제된 에셋을 번들 부모를 기준으로 그룹화하도록 커스텀 규칙을 적용하자 번들 수가 5,199개로 감소했습니다. 이 사용 사례에서 해당 팀과의 협업에 관해 자세히 알아볼 수 있습니다.
번들 수가 40% 감소했지만 포함된 콘텐츠는 여전히 동일했으며 세분화 또한 같은 수준으로 유지되었습니다. 번들 수가 40% 감소하면서 런타임의 SerializedFile 크기도 311MB에서 184MB로 비슷하게 40% 정도 감소했습니다.마무리
어드레서블을 사용하면 메모리 사용량을 크게 줄일 수 있습니다. 사용 사례에 맞춰 에셋 번들을 구성하면 메모리 절감 효과를 더 높일 수 있습니다. 빌트인 분석 규칙은 모든 애플리케이션에 맞도록 보수적으로 구성되어 있습니다. 자체 분석 규칙을 작성하면 번들 레이아웃을 자동화하고 애플리케이션에 맞춰 최적화할 수 있습니다. 메모리 문제를 추적하려면 프로파일링을 자주 진행하고 분석 창에서 번들에 명시적 또는 암묵적으로 포함된 에셋을 확인하세요. 시작하는 데 도움을 주는 가이드인 어드레서블 에셋 시스템 기술 자료에서 다양한 베스트 프랙티스와 확장 API 기술 자료를 확인할 수 있습니다.
어드레서블 에셋 시스템으로 콘텐츠 관리를 개선하는 방법에 대해 직접적으로 도움을 받으려면 세일즈 팀에 연락하여 프로페셔널 교육 과정인 어드레서블 에셋 시스템을 통한 콘텐츠 관리에 관해 자세히 알아보세요. 이 교육은 현재 정해진 일정에 따라 공개 라이브 세션을 통해 제공되고 있으나, 온디맨드로도 이용하실 수 있습니다.