[Unity] Asset Management(에셋 관리) - Resources, AssetBundle, Addressable
Asset이란?
유니티 프로젝트에서 사용하는 텍스처, 오디오, 스크립트, 애니메이션 등의 모든 데이터를 의미한다.
이러한 데이터들은 보통 외부에서 가져온 파일인데,
해당 파일들을 Unity에서 처리할 수 있는 형식으로 Import하여 사용할 수 있는 것이 'Asset'이다.
이번 게시글에서는 유니티에서 제공하는 기능을 통해 어떻게 에셋을 관리하고 어떤 부분에서 차이가 나는지에 대해 알아보고자 한다.
기본적으로 유니티에서 프로젝트를 빌드할 경우
각 씬(Scene)별로 패키징되어 씬을 로드할 때 해당 씬에 필요한 모든 에셋이 메모리에 로드된다.
빌드 과정에서 에셋이 씬에 포함되지 않았거나, 위의 사진과 같이 Inspector 창에서 참조되지 않은 경우는 빌드 파일에 포함되지 않는다.
Resources
에셋을 동적으로 사용하기 위해서는 메모리에 올라와 있는 에셋을 참조하고 있는 매개체가 필요하다.
유니티에선 이러한 참조 매개체 역할을 해주는 Resources 시스템이 있다.
해당 시스템의 경우
'Asset' 폴더내에 'Resources' 폴더를 포함할 시 빌드 할 때 해당 폴더 내에 있는 모든 에셋을 패키징하여
리소스가 필요한 순간에 모든 에셋이 메모리에 로드되는 방식이다.
모든 에셋을 메모리에 로드하는 방식이기 때문에 일반적으로 다음과 같은 단점을 가진다.
- 세분화된 메모리 관리가 어려워 짐
- 애플리케이션 시작 시간과 빌드 시간이 길어짐
- 특정 플랫폼에 사용자 지정 콘텐츠를 제공하는 프로젝트의 기능을 저하시키고 점진적인 콘텐츠 업그레이드의 가능성을 제거함
모든 에셋을 한 번에 패키징하기 때문에 메모리를 세분화로 관리하기 어려워지며
에셋이 많아질 수록 그만큼 시작과 빌드 시간이 길어지게 된다.또한 플랫폼 별 불필요한 에셋마저 빌드하기 때문에 프로젝트의 기능이 저하되며추가 콘텐츠 제작 시 새로운 에셋들만 빌드하기가 어렵기 때문에 업그레이드 가능성을 제거하는 것과 마찬가지이다.
유니티에서는 이러한 문제점들로 인해 Resources 시스템을 사용하는 것을 권장하지 않는다.
다만 리소스 폴더는 사용하기 쉬워 빠르게 프로토타입을 제작할 경우 사용하고,프로젝트가 정식 프로덕션 단계로 넘어갈 땐 제거하는 방식으로 사용하기도 한다.또한 일반적으로 프로젝트의 lifetime 동안 계속 필요한 에셋의 경우 사용한다.
AssetBundle
AssetBundle은 Resources 시스템의 단점을 보완하여 나온 것이다.
AssetBundle은 게임을 논리적 블록으로 분할할 수 있어
게임 빌드의 크기를 줄이면서 온디맨드(On-demand) 방식으로 콘텐츠를 제공하고 업데이트할 수 있다.
에셋 번들에는 프리팹, 머테리얼, 텍스처, 오디오 클립, 씬 등을 비롯한 모든 종류의 에셋을 포함할 수 있지만 스크립트는 포함할 수 없다.
AssetBundle은 일반적으로 두 부분으로 구성되어있다.
헤더(Header)
헤더에는 에셋 번들이 빌드될 때 유니티로부터 받은 많은 양의 정보가 들어있는 곳이다.
에셋번들의 식별자(Version에 대한 정보 같은), 압축 방식, 매니페스터 (manifest) 등과 같은 정보가 있다.
매니페스트에는 에셋의 이름을 키로 삼아 해당 에셋이 데이터 세그먼트에 위치한 바이트 인덱스를 저장하는 검색 테이블이다.
데이터 세그먼트(Data Segment)
직렬화 된 에셋의 실제 데이터가 저장되는 부분이며 위치를 찾을 수 있는 인덱스를 제공한다.
보통 검색의 경우 대부분 균형 검색 트리(balanced search tree)를 사용하며,Windows와 OSX계열(IOS포함) 플랫폼은 레드 블랙트리를 사용한다.
또한 그룹화하여 저장을 할 때 압축 방식을 선택할 수 있는데, 그 방식 3가지로 나뉜다.
- LZMA : 직렬화된 모든 에셋에 대한 전체 바이트 배열이 압축되는 방식
- LZ4 : 개별 에셋의 바이트가 개별적으로 압축되는 방식
- 비압축 : 원시 바이트 스트림으로 유지
비압축 방식으로 빌드할 경우 애플리케이션을 다운로드 할 때 에셋 번들의 크기를 추가 다운로드 하므로 시간 소요가 제일 길다.
하지만 다운로드 이후 에셋을 빠르게 로드할 수 있다.
일반적으로 압축을 할 때 압축 방식을 NONE으로 빌드하게 되면
LZMA(Lempel-Ziv-Markov chain Algorithm) 방식으로 압축이 이루어진다.
에셋 번들 내에 있는 전체 에셋을 한 번에 압축하기 때문에 파일의 크기가 가장 작아진다.
하지만 에셋을 로드할 경우 전체 압축을 해제하기 때문에 그만큼 압축 해제가 느리다.
Unity 5.3 이후 버전부터는 LZ4 압축 방식이 등장하였다.
청크 기반 알고리즘을 사용하여 에셋을 부분적 또는 "청크" 단위로 로드될 수 있도록 한다.
그렇기 때문에 번들 내의 모든 에셋을 한꺼번에 압축 해제하지 않아 시간이 많이 소요되지 않는다.
하지만 LZMA 압축 방식에 비해 압축 크기는 큰 편이다.
특정 상황에 맞게 압축 방식을 선택하여 빌드를 한다면
사용자에게 있어 더 나은 경험을 전달할 수 있기 때문에 압축 방식에 대한 간단한 이해는 필수라고 생각한다.
▶ 유니티는 성능 최적화를 위해 LZMA로 압축된 에셋 번들은 다시 LZ4 압축으로 재압축되어 로컬 파일 시스템에서 캐시된다.
AsserBundle WorkFlow
빌드한 에셋 번들을 CDN(파일 저장 서버 및 클라우드)에 올린다.
CDN에 UnityWebRequest를 요청하여 다운받아 캐싱한다. (캐싱이 되면 다시 실행할 때는 다운할 필요가 없다.)
클라이언트에서 에셋 번들을 통해 에셋을사용한다.
AssetBundle도 좋은 장점을 지닌 반면 단점 또한 존재한다.
의존성
하나의 AssetBundle에 포함된 에셋이 다른 AssetBundle에 의존하고 있는 경우를 말한다.
의존성의 문제로 인해 하나의 리소스를 각 번들에 적재함으로서 동일한 에셋이 중복되어 메모리 낭비의 원인이 된다.
또한 위와 같이 세분화하여 번들을 나누더라도 A와 B Object는 Image를 의존하기 때문에
C번들에 있는 Image를 사용하기 위해 이외의 모든 에셋들을 메모리에 올려야 하므로 메모리 낭비의 원인이 된다.
이러한 의존성 문제를 해결하기 위해 개발자가 따로 관리해줄 필요가 있다.
관리가 간단하고 할 양이 많지 않다면 괜찮겠지만
프로젝트 규모에 따라 관리가 쉽지 않기 때문에 문제가 된다.
경로
에셋을 로드하기 위해서는 경로와 파일명을 통해 해당 에셋이 들어있는 번들과 에셋을 찾을 수 있는데
해당 경로와 파일명을 string으로 매칭시켜 수정에 매우 취약하다.
한 에셋의 이름이 수정될 경우 해당 에셋을 로드하는 코드(파일명)도
일일이 다 수정해야할 뿐더러
문자열 오타로 인해 매칭이 끊어져버릴 수 있기 때문에 번거로움이 많다.
Addressable
Addressable은 AssetBundle을 토대로 설계된 시스템으로완전히 새롭게 만들어진 것이 아니라 AssetBundle의 편의성을 개선하기 위해 등장한 시스템이다.
카탈로그(Catalog)
AssetBundle에 manifest가 있다면 Addressable에는 Catalog가 있다.
Catalog는 Addressable의 번들과 함께 생성되는 것으로
Asset의 Address(Group 및 Lable 정보)와 Asset의 매핑 정보가 기재된 파일이다.
또한 버전이 붙어서 생성되기 때문에 번들 자체의 버전 관리도 된다.
Addressable이 AssetBundle의 단점을 보완하여 나온 만큼 다음과 같은 보완을 통해
이전의 단점을 보완했다.
의존성
기존 AssetBundle의 경우 의존성으로 인해 메모리 낭비의 원인이 되는 문제가 있었다.
Addressable의 경우 번들 단위로 의존성이 묶이지 않는다.
서로 다른 번들이라도 의존성이 있다면 API에서 일괄 관리가 되기 때문에
기존 의존성에 의한 중복이나 수동관리가 해결됐다.
경로
기존 AssetBundle의 경우 Asset을 로드하기 위해
해당 Asset이 저장된 Path와 코드에서 사용하는 Path 값을 똑같이 맞춰야한다.
그렇기 때문에 한쪽이 변경된다면 다른 쪽도 변경해줘야하는 번거로움이 생겼다.
Addressable은 이름에서 볼 수 있듯이 Address(참조) 방식을 사용한다.Asset의 Path를 참조하기 때문에 Path를 참조하는 값만 처음에 맞춰서 작업해 놓는다면경로가 바뀌거나 에셋의 이름을 변경하더라도 별도로 수정할 번거로움이 없어졌다.
번들 로드 & 언로드
이전 Bundle의 경우 Asset을 로드하기 위해 Bundle을 수동으로 로드하여야 했고
또한 메모리 관리를 위해 쓰지 않는 Asset의 경우 수동으로 언로드 해야했다.
하지만 Addressable API의 경우 번들을 로드하고 에셋을 로드하는 과정이 한번에 묶여있기 때문에
기존에 보다 간단하게 구현된다.
즉, 에셋을 요청하면 자동으로 번들을 로드해준다는 뜻이다.
또한 언로드하는 과정도 자동으로 이루어지는데
이는 참조 카운팅 방식을 통해 메모리 관리를 효율적으로 도와준다.
마무리
Asset을 잘 관리하여야 메모리 효율을 높일 수 있으며
애플리케이션의 크기 또한 간소화 시킬 수 있다.
특히 모바일 애플리케이션의 경우 PC와는 달리 배포 가능한 애플리케이션의 크기가 상당히 제한되어 있기 때문에
애플리케이션의 크기를 간소화할 필요가 있다.
이렇게 Asset들을 관리하는 방식과 원리에 대해 자세히 알고 적용한다면
효율적인 빌드가 가능해진다.
참고 블로그
https://gus6615.tistory.com/m/85
https://medium.com/pinkfong/unity-addressable-asset-%EB%A5%BC-%EC%99%9C-3017f3fa2edc
참고 문서
https://docs.unity3d.com/kr/2021.2/Manual/AssetBundles-Building.html