애셋번들 사용패턴 (AssetBundle usage patterns) 5 – 번역
애셋번들 사용패턴 (AssetBundle usage patterns) – 번역
– 원문 링크 –
4.5 애셋번들을 사용할때 발생할 수 있는 일반적인 문제 (Common pitfalls)
이 섹션에서는 프로젝트에서 애셋번들을 사용할때 일반적으로 발생할 수 있는 몇가지 문제에 대해서 설명합니다.
4.5.1 애셋 중복 (Asset duplication)
유니티 5의 애셋번들 시스템은, 오브젝트가 애셋번들로 빌드될 때, 해당 오브젝트들의 모든 의존성(dependencies)을 확인합니다.
이 작업은 Asset Database를 사용해서 처리됩니다. 이 의존성 정보는 애셋번들에 포함될 오브젝트를 결정하는데 사용됩니다.
애셋번들에 명시적으로 할당된 오브젝트는, 해당 애셋번들에만 빌드됩니다.
AssetImporter의 assetBundleName 속성에 문자열이 설정된 경우에 오브젝트는 “명시적으로 할당”됩니다.
이 작업은 유니티 에디터 내의 해당 오브젝트의 인스펙터뷰에서 AssetBundle을 선택하거나, 에디터 스크립트(Editor Script)를 통해서 처리할 수 있습니다.
애셋번들에 명시적으로 할당되지않은 모든 오브젝트는, 태그가 설정되지 않은 오브젝트를 하나 이상 참조하는 오브젝트가 포함된 모든 애셋번들에 추가됩니다.
두 개의 서로 다른 오브젝트가 두 개의 다른 애셋번들에 할당되었는데, 이 두 오브젝트에서 동일한 오브젝트를 참조하면, 이렇게 참조된 오브젝트는 두 애셋번들에 모두 복사됩니다.
이렇게 중복된 의존성(dependency) 역시 인스턴스화 됩니다. 즉, 의존성 오브젝트(참조된 오브젝트)의 두 복사본은 서로 다른 식별자를 가진 별개의 오브젝트로 간주됩니다.
이로 인해서, 어플리케이션의 전체 애셋번들 크기가 증가합니다. 또한 어플리케이션에서 두 부모 계층을 로드하면, 두 개의 오브젝트 복사본이 모두 메모리에 로드됩니다.
이 문제를 해결할 수 있는 몇가지 방법이 있습니다:
- 서로 다른 애셋번들에 빌드되는 오브젝트가 의존성(dependency)을 공유하지 않도록 확인합니다. 의존성을 공유하는 모든 오브젝트를 동일한 애셋번들에 배치하면, 의존성을 복제하지 않게됩니다.
– 이 방법은 일반적으로 의존성 공유가 많은 프로젝트에서는 사용할 수 없습니다. 이 경우, 편의성과 효율성을 위해서, 너무 자주 재-다운로드(re-download)되고 리빌드(rebuild)되는 단일 애셋번들이 만들어집니다. - 애셋번들을 분할해서, 의존성을 공유하는 애셋번들이 동시에 로드되지 않도록 합니다.
– 이 방법은 레벨-기반 게임과 같은, 특정 타입의 프로젝트에 적용될 수 있습니다. 하지만, 이 방법은 여전히 프로젝트의 애셋번들 크기, 빌드 시간, 로딩 시간을 불필요하게 증가시킵니다. - 의존성 애셋(참조되는 애셋) 모두를 별도의 애셋번들로 빌드했는지 확인합니다. 이 방법은 애셋이 중복되는 위험을 없애주지만, 개발의 복잡도를 야기할 수 있습니다. 이 방법을 적용하는 어플리케이션은 애셋번들 간의 의존성(dependency)을 추적해서, AssetBundle.LoadAsset API를 호출하기 전에, 정확한 애셋번들리 로드되는지 확인해야 합니다.
유니티 5에서는, UnityEditor 네임스페이스에 있는 AssetDatabase API를 통해서, 오브젝트의 의존성(dependency)을 추적합니다.
네임스페이스가 암시하듯이, 이 API는 UnityEditor에서만 사용가능하며, 런타임에서는 사용할 수 없습니다.
AssetDatabase.GetDependencies를 통해서 특정 오브젝트의 의존성 또는 특정 애셋의 의존성 정보를 모두 즉시 찾을 수 있습니다.
이러한 의존성에는 그 자체의 의존성이 존재할 수 있다는 점을 주의해야 합니다. 추가적으로, AssetImporter API는 특정 오브젝트가 할당된 애셋번들을 요청하는데에도 사용할 수 있습니다.
AssetDatabase 와 AssetImporter API를 결합해서, 특정 애셋번들에 직-간접적인 의존성을 갖는 오브젝트가 해당 애셋번들에 정확히 할당되도록 하거나, 동일한 애셋번들에 할당된 오브젝트를, 두 개의 애셋번들에서 참조하는 것을 방지하는 에디터 스크립트를 작성할 수 있습니다. 중복되는 애셋으로 인한 메모리 비용 문제 때문에, 모든 프로젝트에서 이런 문제를 방지하는 스크립트의 작성을 권장합니다.
4.5.2 스프라이트 아틀라스 중복 (Sprite atlas duplication)
이어지는 섹션에서는, 자동 생성된 스프라이트 아틀라스와 유니티 5의 애셋(Asset) 의존성 계산 코드가 함께 사용되었을때 발생하는 단점에 대해서 설명합니다.
유니티 5.2.2p4 와 유니티 5.3은 이 동작을 해결하기 위해서 패치를 진행했습니다.
유니티 5.2.2p4, 유니티 5.3 버전 이상
자동으로 생성된 스프라이트 아틀라스는 모두, 해당 스프라이트 아틀라스로 부터 생성된, 스프라이트 오브젝트가 포함되어있는 애셋번들에 할당됩니다.
스프라이트 오브젝트가 여러 애셋번들에 할당되는 경우, 스프라이트 아틀라스는 하나의 애셋번들에만 할당되지 않고, 여러 애셋번들에 중복 할당됩니다.
스프라이트 오브젝트가 애셋번들에 할당되지 않으면, 해당 스프라이트 아틀라스도 애셋번들에 할당되지 않습니다.
스프라이트 아틀라스가 중복되는 것을 방지하려면, 동일한 스프라이트 아틀라스 태그(tag)로 지정된, 모든 스프라이트들이 같은 애셋번들에 할당되었는지 확인해야 합니다.
유니티 5.2.2p3 및 이전 버전
자동으로 생성된 여러 개의 스프라이트 아틀라스는 절대 동일한 애셋번들에 할당되지 않습니다.
이 때문에 스프라이트 아틀라스는, 해당 아틀라스의 스프라이트를 포함하거나 그 스프라이트를 참조하는 애셋번들에 추가될 수 있습니다.
이런 문제 때문에, 유니티의 스프라이트 패커(Sprite Packer)를 사용하는 유니티 5 프로젝트의 경우, 유니티 5.2.2p4 또는 유니티 5.3 이상의 최신 버전으로 업그레이드하는 것을 권장합니다.
업그레이드를 할 수 없는 프로젝트의 경우, 이 문제를 해결할 수 있는 2가지 방법이 있습니다:
- Easy: 유니티의 내장된 스프라이트 패커(Sprite packer)를 사용하지 마십시오. 외부 툴에서 생성된 스프라이트 아틀라스는 정상적인 애셋이 되어, 애셋번들에 적절하게 할당될 수 있습니다.
- Hard: 자동으로 아틀라스에 포함된 스프라이트를 사용하는 모든 오브젝트를, 해당 스프라이트와 동일한 애셋번들에 할당합니다.
- 이렇게 하면, 자동으로 생성된 스프라이트 아틀라스가 다른 애셋번들로부터 간접적인 의존성을 갖지 않으므로, 중복되지 않습니다.
- 이 해결책은 유니티의 스프라이트 패커(Sprite Packer)를 사용하는 간단한 작업 흐름을 유지시켜 줍니다. 하지만, 이 방법은 여러 애셋을 별도의 애셋번들로 구분할 수 있도록 하는 개발자의 능력을 저하시킵니다. 또한, 아틀라스 자체는 변경된 사항이 없더라도, 해당 아틀라스를 참조하는(사용하는) 컴포넌트의 데이터가 변경된 경우, 스프라이트 아틀라스 전체를 다시 다운로드 하도록 강제합니다.
4.5.3 안드로이드 텍스쳐 (Android Textures)
안드로이드 환경에서의 장치의 파편화가 매우 심하기 때문에, 텍스쳐를 여러 가지 다른 포맷으로 압축해야하는 경우가 있습니다.
모든 안드로이드 장치에서 ETC1 포맷을 지원하지만, ETC1 포맷은 알파 채널을 가진 텍스쳐를 지원하지 않습니다.
어플리케이션에서 OpenGL ES 2를 지원하지 않아도 되는 경우, 이 문제를 해결하는 가장 깔끔한 방법은 ETC2 포맷을 사용하는 것입니다.
ETC2 포맷은 OpenGL ES 3를 지원하는 모든 안드로이드 장치에서 지원됩니다.
하지만, 아직 많은 어플리케이션이 ETC2 포맷의 사용이 불가능한 장치에 배포해야 합니다.
이 문제를 해결할 수 있는 한가지 방법은 유니티 5의 AssetBundle Variant를 사용하는 것입니다.
(다른 방법에 대한 자세한 내용은 유니티의 안드로이드 최적화 가이드 문서를 참고하시기 바랍니다.)
AssetBundle Variant를 사용하려면, ETC1을 이용해서 깔끔하게 압축할 수 없는 텍스쳐를 모두, 텍스쳐 전용 애셋번들로 분리시켜야 합니다.
그 다음, 안드로이드 환경에서 ETC2 포맷을 사용할 수 없는 장치를 지원하기 위해서, DXT5, PVRTC, ATITC와 같은 제조사 전용 텍스쳐 압축 포맷을 사용해서, 이런 애셋번들의 충분한 수의 변형(variant)을 생성합니다.
각 애셋번들 변형(AssetBundle Variant)에 포함된 텍스쳐의 TextureImporter 설정을, 해당 변형(Variant)에 적절한 압축 포맷으로 변경합니다.
런타임에서, 특정 장치에서 지원되는 서로 다른 압축 포맷을 SystemInfo.SupportsTextureFormat API를 사용해서 검색할 수 있습니다.
이 정보는 지원되는 포맷으로 압축된 텍스쳐를 포함하는, 애셋번들 변형(AssetBundle Variant)을 선택하고 로드하는데 사용해야 합니다.
안드로이드 텍스쳐 압축 포맷에 대한 자세한 내용을 여기를 참고하시기 바랍니다.
4.5.4 iOS 파일 핸들의 과다 사용 (iOS file handle overuse)
다음 섹션에서 설명하는 문제는 유니티 5.3.2p2에서 해결되었습니다. 유니티 최신 버전에서는 이 문제에 영향을 받지 않습니다.
유니티 5.3.2p2 이전 버전에서는, 유니티가 애셋번들이 로드되는 전체 시간 동안에 애셋번들을 로드하는데 사용되는 파일 핸들을 유지했습니다.
이는 대부분의 플랫폼에서는 문제가 되지 않습니다. 그러나, iOS는 하나에 프로세스에서 동시에 열수 있는 파일 핸들의 수를 255개로 제한합니다.
특정 애셋번들을 로드하는데 한계를 초과하면, “파일 핸들이 너무 많이 열림(Too Many Open File Handles)” 오류가 발생해서, 로딩을 진행하는 호출이 실패합니다.
이는 콘텐츠를 수백, 수천 개의 애셋번들로 나눠서 사용하려는 프로젝트에서 발생할 수 있는 일반적인 문제였습니다.
패치된 유니티 버전으로 업그레이드할 수 없는 프로젝트에는 다음과 같은 임시 해결책이 있습니다:
– 관련된 애셋번들을 병합해서 사용중인 애셋번들의 수를 줄입니다.
– AssetBundle.Unload(false)를 사용해서 애셋번들의 파일 핸들을 닫고, 로드된 오브젝트의 라이프사이클(lifecyle)을 수동으로 관리합니다.
내용 끝까지 읽어주셔서 감사합니다.
배너 클릭은 저에게 많은 힘이 됩니다.
감사합니다 🙂