유니티에서 C# 코드는 msc.exe라는 프로그램에 의해  IL 코드로 변환됩니다.

(mono 프레임워크의 경우 msc.ec를 사용하며 일반 C#은 다를 수 있음)


C# 코드를 유니티에서 빌드하면 Assembly-Csharp.dll 이라는 파일이 생성됩니다. 이 파일은 IL(Intermediate Language) 형태로 이루어져 있습니다.


그리고 이 IL코드가  실제 기기에서 실행됩니다. mono가 IL 코드를 읽어 어셈블리(바이너리)로 변환시켜 실행되는데 이 과정을 JIT 컴파일링 이라고 합니다.



IL2CPP는 msc.exe가 만든 IL 코드를  il2cpp.exe를 사용하여 C++ 형태로 코드를 변환한 뒤 바로 기계어로 변환시킵니다.


이 경우 JIT 과정이 없기 때문에 성능상 이점이 있으며 보안성도 더 좋습니다.


이런식의 실행 중 컴파일이 없는 것을 AOT 컴파일링이라고 합니다.

'유니티' 카테고리의 다른 글

AssetBundle의 사용 패턴  (0) 2018.03.09
AssetBundle의 기본 요소  (0) 2018.03.07
Resources 폴더  (0) 2018.03.05
Assets, Objects 그리고 직렬화  (0) 2018.03.04
AssetBundles 및 Resources 가이드  (0) 2018.03.03

AssetBundle의 사용 패턴      [원본 링크]


확인 완료한 버전 2017.3 - 난이도: 고급



이 문서는 유니티 AssetBundles 및 Resources 가이드의 5번째 챕터 입니다.



이전 챕터에서는 다양한 로딩 API의 저레벨 동작을 포함하는 AssetBundle의 기본 요소에 대해서 설명했습니다. 이번 챕터에서는 AssetBundle을 사용하는 다양한 측면에 대한 문제와 잠재적 해결 방법에 대해 이야기 합니다.




로드 된 Asset 관리


메모리에 민감한 환경에서는 로드 된 Object의 크기와 수를 신중하게 제어하는것이 중요합니다. 유니티는 Object가 활성화된 씬에서 제거될 때 자동으로 Object를 언로드 하지 않습니다. Asset은 특정 시간에 정리되며 수동으로 정리 실행이 가능합니다.


AssetBundle 자체는 신중하게 관리되어야 합니다. AssetBundle은 로컬 저장소의 파일에서 불러오기 때문에(유니티 캐시 또는 AssetBundle.LoadFromFile을 통해 로드 된 파일) 수십 킬로바이트 이상 소모하는 일이 잘 없을 정도로 적은 메모리 오버 헤드를 가집니다. 하지만 많은 수의 AssetBundle이 있다면 오버 헤드 문제가 발생할 수 있습니다.



대부분 프로젝트는 사용자가 레벨 재생과 같이 콘텐츠를 다시 경험할 수 있기 때문에 AssetBundle을 언제 로드하고 언로드할지 잘 알아야 합니다. AssetBundle이 잘못 언로드 된다면 메모리에서 Object 복제가 일어날 수 있습니다. AssetBundle의 잘못된 언로딩은 texture 누락 등 의도하지 않은 문제 동작이 일어날 수 있습니다. 이것이 가능한 이유를 알아보려면 Asset, Object 그리고 직렬화 챕터의 Object간 참조 부분을 참고하세요.



asset과 AssetBundle을 관리할 때 이해해야 할 가장 중요한 것은 AssetBundle.Unload를 호출할 때 unloadAllLoadedObjects 파라미터의 참과 거짓 설정에 따른 차이 입니다.



이 API는 호출 된 AssetBundle의 헤더 정보를 언로드 합니다. unloadAllLoadedObjects 파라미터는 AssetBundle에서 모든 Object를 언로드 할지를 결정합니다. 참으로 설정하면 AssetBundle에서 비롯된 모든 객체가 현재 씬에서 사용중이라도 즉시 언로드 됩니다.




예를 들어 material M이 AssetBundle AB에서 로드되었고 지금 M 이 활성 씬에 있다고 가정합니다.



만약 AssetBundle.Unload(true) 가 호출 된다면 M은 씬에서 지워지고 파괴된 뒤 언롣 됩니다.

하지만 만약 AssetBundle.Unload(false) 가 호출된다면 AB의 헤더 정보는 언로드 되지만 M은 씬에 남고 계속 작동합니다. AssetBundle.Unload(false)를 호출하는 것은 M과 AB 사이의 링크를 끊는 것 입니다. 만약 AB가 다시 로드 된다면, AB를 포함한 새로운 Object가 메모리에 로드됩니다.




만약 AB가다시 로드 된다면, AssetBundle의 새로운 헤더 정보가 다시 로드됩니다. 하지만 M은 새로운 AB에서 로드 된 것이 아닙니다. 유니티는 새로운 AB와 기존 M 사이의 링크를 정립하지 못합니다.



만약 AssetBundle.LoadAsset() 이 M을 리로드 하기 위해 불렸다면, 유니티는 오래된 M을 AB의 데이터 인스턴스로 인식하지 않습니다. 따라서, 유니티는새로운 M을 로드하고 씬에는 2개의 M이 있게 됩니다.


대부분 프로젝트에서 이 행동은 의도되지 않은 것 입니다. AssetBundle.Unload(true)를 사용하고 Object가 복제되지 않는 방법을 선택해야 합니다. 두가지 일방적인 방법은 다음과 같습니다.


1. 애플케이션의 라이프타임 중 로딩 스크린이나 레벨 사이같이 일시적인 AssetBundle이 언로드 되는 잘 정의된 포인트를 가지는 것 입니다. 이것이 간단하고 가장 일반적인 옵션입니다.


2. 각 Object들의 참조 카운트를 유지하고 모든 구성 Object가 사용중이지 않을 때 AssetBundle을 언로드 하는것 입니다. 이렇게 하면 애플리케이션이 메모리 복제 없이 각 Object를 언로드, 리로드 할 수 있습니다.


애플리케이션에서 AssetBundle.Unload(false)를 사용한다면, 각 Object는 2가지 방법으로만 언로드 가능합니다.


1. 씬과 코드에서 원하지 않는 Object에 대한 참조를 모두 제거합니다. 이것이 완료된 후 Resources.UnloadUnusedAssets를 호출합니다.


2. 씬을 지금 씬에 추가되지 않게 로드합니다. 이는 지금 씬의 모든 Object를 파괴하고 Resources.UnloadUnusedAssets를 자동 호출 합니다.


만약 프로젝트에 Object들이 로드, 언로드 될 때 게임 모드나 레벨 사이와 같이 유저가 기다릴 수 있는 잘 정의된 지점이 있다면, 최대한 많은 Object들이 언로드 되고 새로운 Object를 로드하게 해야합니다.


가장 간단한 방법은 프로젝트의 별개 청크들을 씬에 패키징 하고 모든 의존 관계가 있는것을 씬과 함께 AssetBundle로 빌드하는 것 입니다. 이렇게 하면 애플리케이션이 로딩 씬에 들어갈 때 이전 씬의 모든 AssetBundle을 언로드 하고 새로운 씬을 포함안 AssetBundle을 로드하게 됩니다.


이것이 가장 간단한 흐름이지만, 일부 프로젝트는 보다 복잡한 AssetBundle 관리가 필요합니다. 모든 프로젝트가 다르기 때문에 보편적인 AssetBundle 디자인 패턴은 없습니다.


AssetBundle에 Object를 어떻게 그룹화 하여 넣을 지 결정할 때 Object들이 동시에 로드되고 업데이트 되는 것들을 AssetBundle에 같이 넣는것이 일반적으로 좋습니다. 롤 플레잉 게임을 예로 들어보자면, 각 맵과 컷씬은 씬 별로 AssetBundle 그룹화 시킬 수 있지만 일부 Object들은 대부분 씬에서 필요합니다. AssetBundle은 초상화, 인게임 UI, 다양한 캐릭터 모델과 텍스처를 제공하도록 제작 될 수 있습니다. 후자의 Object와 Asset은 애플리케이션 시작 시 로드하여 계속 남아있도록 하는 AssetBundle로 그룹화 될 수 있습니다.


유니티에서 언로드 된 AssetBundle에서 Object를 리로드 하려고 할 때 또 다른 문제가 생길 수 있습니다. 이 경우, 리로드는 실패하고 Object는 유니티 에디터 하이어라키에 (missing) Object로 나타납니다.


이는 주로 모바일 앱이 중지되거나 PC를 잠그는 등의 경우 유니티가 그래픽 건텍스트에 대한 컨트롤을 잃고 되찾을 때 일어납니다. 이 경우 유니티는 텍스처와 쉐이더를 GPU에 다시 업로드 해야합니다. 만약 이 asset들에 대한 소스 AssetBundle을 사용할 수 없는 경우, 애플리케이션은 씬에서 Object를 자홍색으로 렌더링합니다.




4.2 배포

프로젝트의 AssetBundle을 클라이언트에 배포하는것에는 2가지 기본적인 방밥이 있습니다. 프로젝트와 동시에 설치하거나 설치 후 다운로드하는 것 입니다.


AssetBundle을 설치와 함께 또는 설치 후 제공하는것은 프로젝트를 실행 할 플랫폼의 기능과 제한사항에 의해 결정됩니다. 모바일 프로젝트는 최초 설치 크기를 줄이고 무선 다운로드 수를 제한 이하로 줄이기 위해서 설치 후 다운로드를 주로 선택합니다. 콘솔이나 PC 프로젝트는 보통 최초 설치시 AssetBundle을 제공합니다.


적절한 아키텍처를 사용하면 AssetBundle의 최초 전달 방법과 상관 없이 새로운 패치나 수정사항 적용이 가능합니다. 이에 관련된 더 많은 정보는 유니티 메뉴얼의 AssetBundle로 패치하기 문서를 참고하세요.



4.2.1 프로젝트화 함께 제공하기

AssetBundle을 프로젝트와 함께 제공하는것은 추가적인 다운로드 관리 코드가 필요없기 때문에 가장 간단한 방법입니다. 프로젝트의 AssetBundle을 설치와 동시에 포함하는 것 에는 2가지 주요 이유가 있습니다.


● 프로젝트의 빌드 횟수를 줄이고 더 단순한 반복 개발을 가능하게 합니다. 만약 AssetBundle들이 애플리케이션과 별도로 업데이트 될 필요가 없다면 AssetBundle은 Streaming Assets에 저장하여 애플리케이션에 포함 할 수 있습니다. 아래의 Streaming Assets 부분을 참고하세요.


● 업데이트 가능한 콘텐츠의 초기 버전을 제공합니다. 이것은 보통 초기 설치 후 최종 사용자의 시간을 절약하거나 나중에 패치할 때 기본으로 사용합니다. Streaming Assets는 이 경우 이상적이지 않습니다. 하지만 커스텀 다운로딩, 캐싱 시스템을 작성하는 것이 옵션이 아니라면, 업데이트 가능한 콘텐츠의 초기 리비전을 Streaming Assets의 유니티 캐시에 로드 할 수 있습니다.


4.2.1.1 Streaming Assets

설치 시 유니티 애플리케이션에 AssetBundle을 포함한 다양한 유형의 콘텐츠를 포함시키는 가장 쉬운 방법은, 프로젝트 빌드 시 콘텐츠를 /Assets/StreamingAssets/ 폴더에 빌드하는 것 입니다. 빌드 시 StreamingAssets 폴더에 포함된 모든 콘텐츠는 최종 애플리케이션에 포함됩니다.


런타임 중 로컬 저장소의 StreamingAssets 폴더에 대한 모든 경로는 Application.streamingAssetsPath 를 통해서 접근 가능합니다. AssetBundle은 대부분 플랫폼에서 AssetBundle.LoadFromFile을 통해 로드 가능합니다.


안드로이드 개발자: 안드로이드에서는 StreamingAssets 폴더가 APK에 저장되며 APK가 압축된 방식에 따라 로드될 때 시간이 더 걸릴 수 있습니다. 압축 시 사용된 알고리즘은 유니티 버전에 따라 다를 수 있습니다. 7-zip 과 같은 아카이버를 사용하여 파일이 압축되어있는지 알아내기위해 APK를 열 수 있습니다. 압축되어 있다면, AssetBundle.LoadFromFile() 은 느려질 수 있습니다. 이럴 경우 UnityWebRequest.GetAssetBundle을 사용하여 캐싱 된 버전을 검색할 수 있습니다. UnityWebRequest를 사용하면 AssetBundle이 첫 실행 때 압축 해제되어 캐싱되므로 실행 속도가 빨라집니다. 이 경우 AssetBundle이 캐시에 복사되어 더 많은 저장 장소를 필요하다는 것을 기억하세요. 다른 방법으로, 빌드 시 Gradle 프로젝트를 익스포트하여 AssetBundle 확장을 추가 할 수 있습니다. 그 다음 build.gradle 파일을 편집하여 해당 확장을 noCompress 섹션에 추가 할 수 있습니다. 이것을 완료하면, AssetBundle.LoadFromFile()을 압축 해제 비용 없이 사용할 수 있습니다.


참고: 일부 플랫폼에서 Streaming Assets는 쓰기 가능한 위치가 아닙니다. 만약 프로젝트의 AssetBundle이 설치 후 업데이트 되어야 한다면 WWW.LoadFromCacheOrDownload를 쓰거나 커스텀 다운로더를 작성해야합니다.


4.2.2 설치 후 다운로드

모바일 디바이스에 AssetBundle을 전달하는 가장 좋은 방법은 앱 설치 후에 다운로드 받는 것 입니다. 이는 또한 애플리케이션 설치 후 콘텐츠 업데이트를 위해 전체 재설치를 하지 않도록 합니다. 많은 플랫폼에서, 애플이케이션 바이너리는 고비용의 긴 재 인증 과정을 거쳐야 합니다. 따라서, 설치 후 다운로드릉 위한 좋은 시스템을 개발하는 것은 중요합니다.


AssetBundle을 전달하는 가장 간단한 방법은 웹서버에 두고 UnityWebRequest를 사용하는 것 입니다. 유니티는 다운로드한 AssetBundle을 로컬 저장소에 자동으로 캐시합니다. 만약 다운로드한 AssetBundle이 LZMA 압축되어 있다면, 다음에 빠르게 로딩하기 위해서 압축 해제되거나 LZ4로 재압축 하여 (이는 Caching.compressionEnabled 설정에 따름 저장됩니다. 캐시가 가득 찬다면, 유니티는 최근 가장 사용하지 않은 AssetBundle을 캐시에서 제거합니다. 자세한 사항은 빌트인 캐싱 부분을 참고하세요.


보통 가능하다면 UnityWebRequest 사용을 권장합니다. 만약 유니티 5.2 이하의 버전을 사용한다면 WWW.LoadFromCacheOrDownload 를 사용하세요. 빌트 인 API의 성능이나 메모리 사용량, 캐싱 동작 등이 프로젝트에 적합하지 않거나 플랫폼에 적합하지 않다면 커스텀 다운로드 시스템을 개발하세요.


UnityWebRequest 또는 WWW.LoadFromCacheOrDownload 의 사용이 제한되는 상황

● AssetBundle 캐시에 대한 세밀한 제어가 필요할 때

● 프로젝트에 커스텀 압축 방식이 필요할 때

● 비활성화 상태에서의 데이터 스트리밍과 같이 특정 플랫폼 별 API를 사용해야 할 경우

예: IOS 백그라운드 테스크 API 를 사용

● 유니티가 적절한 SSL 지원을 제공하지 않는 플랫폼(PC와 같은)에 SSL을 통해 AssetBundle을 전달해야 하는 경우



4.2.3 빌트 인 캐싱

유니티는 AssetBundle 버전 번호를 인수로 한 오버로드를 가진 UnityWebRequest API를 통해 다운로드 된 AssetBundle을 캐시하는 데 사용 가능한 빌트 인 AssetBundle 캐싱 시스템이 있습니다. 이 번호는 AssetBundle 내부에 저장되지 않고, AssetBundle 시스템에 의해 생성되지 않습니다.


캐싱 시스템은 UnityWebRequest에 전달한 마지막 버전 번호를 계속 추적합니다. API가 버전 번호와 같이 호출되면 캐싱 시스템은 버전 번호와 비교하여 캐시 된 AssetBundle이 있는지 확인합니다. 이 번호가 일치하면 시스템은 캐시 된 AssetBundle을 로드합니다. 만약 번호가 일치하지 않거나 캐시 된 AssetBundle이 없으면, 유니티는 새로 다운로드 합니다. 다운로드 된 AssetBundle은 새로운 버전 번호와 연결됩니다.


캐싱 시스템의 AssetBundle은 파일 이름으로만 식별됩니다. 다운로드 된 전체 URL로 구분되지 않습니다. 이는 CDN 처럼 같은 이름의 AssetBundle 파일을 여러 장소에 저장할 수 있다는 것을 의미합니다. 파일 이름이 동일하면 캐싱 시스템은 같은 AssetBundle로 인식합니다.


AssetBundle에 버전 번호를 할당하는 적절한 방법을 결정하고 이러한 번호를 UnityWebRequest에 전달하는 것은 각 애플리케이션에 달려있습니다. 번호는 CRC값과 같이 일종의 고유한 식별자 입니다. AssetBundleManifest.GetAssetBundleHash() 또한 이러한 용도로 사용 가능하지만, 이 함수를 버전 관리에 사용하지 않기를 권장합니다. 이는 추정값을 제공할 뿐 실제 hash 연산을 하지 않습니다.


자세한 사항은 AssetBundle로 패치하기 부분을 참고하세요.


유니티 2017.1 이후 버전에서는 개발자가 여러 캐시에서 활성 캐시를 선택 할 수 있도록 세분화된 제어 기능을 제공하기 위해서 Caching API가 확장되었습니다. 이전 버전의 유니티에서는 캐시된 항목을 제거하기 위해서 Caching.expirationDelay Caching.maximumAvailableDiskSpace 를 수정했습니다. (이러한 속성은 유니티 2017.1에서 Cache 클래스에 남아있습니다)


expirationDelay는 AssetBundle이 자동으로 삭제되기 전에 경과해야 하는 최소 시간(초) 입니다. 이 시간동안 AssetBundle에 대한 접근이 없으면 자동으로 삭제됩니다.


maximumAvailableDiskSpace는 expirationDelay 이전에 사용 된 AssetBundle 삭제를 시작하기 전에 캐시가 사용할 수 있는 로컬 저장소의 공간을 지정합니다.  한계에 도달하면, 유니티는 캐시에서 최근 가장 적게 사용 된 (또는 Caching.MarkAsUsed로 표시 한) AssetBundle을 삭제합니다. 유니티는 새로운 다운로드에 충분한 공간을 확보할 때 까지 캐시된 AssetBundle을 삭제할 것입니다.


4.2.3.1 캐시 준비하기

AssetBundle은 파일 이름으로 식별되기 때문에, 애플리케이션과 함께 제공되는 AssetBundle로 캐시를 준비 할 수 있습니다. 이것을 하기위해서 초기 또는 기본 보전의 AssetBundle을 /Assets/StreamingAssets/ 에 저장하세요. 이 과정은 프로젝트와 함께 제공하기 부분에 설명한 것과 동일합니다.


애플리케이션이 처음 실행 될 때 캐시는 Application.StreamingAssetsPath로 로드 되는 AssetBundle로 채워질 수 있습니다. 그 후, 애플리케이션은 UnityWebRequest를 정상적으로 호출 할 수 있습니다. (UnityWebRequest는 AssetBundle을 StreamingAssets 경로에서 불러올 수 있습니다.)

'유니티' 카테고리의 다른 글

유니티의 컴파일  (0) 2019.03.14
AssetBundle의 기본 요소  (0) 2018.03.07
Resources 폴더  (0) 2018.03.05
Assets, Objects 그리고 직렬화  (0) 2018.03.04
AssetBundles 및 Resources 가이드  (0) 2018.03.03

AssetBundle의 기본 요소     [원본 링크]


확인 완료한 버전 2017.3 - 난이도: 고급


이 문서는 유니티 AssetBundles 및 Resources 가이드의 4번째 챕터 입니다.


이 챕터에서는 AssetBundle에 대해 설명합니다. AssetBundle이 구축되는 기본 시스템과 AssetBundle과 상호작용하는 핵심 API를 소개합니다. 특히 AssetBundle의 로드와 언로드 그리고 AssetBundle에서 Asset과 Object를 로드, 언로드에 대해 설명합니다.


AssetBundle의 사용에 대한 패턴과 좋은 이용 예제는 다음 챕터에서 이야기 합니다.




3.1 개요

AssetBundle 시스템은 유니티가 인덱싱 하고 직렬화 할 수 있는 보관 형식으로 하나 이상의 파일을 보관하는 방법을 제공합니다. AssetBundle은 유니티 설치 후 비 코드 콘텐츠의 배포, 업데이트를 위한 기본 도구 입니다. 이를 총해 개발자는 더 작은 앱 패키지를 제출하고 런타임 메모리 부담을 최소화 하며 촤종 사용자의 장치에 최적화 된 콘텐츠를 선택적으로 로드 할 수 있습니다.


AssetBundle의 동작 방식을 이해하는 것은 모바일 장치를 대상으로 한 유니티 프로젝트의 성동적인 빌드에 필수입니다. AssetBundle 콘텐츠에 대한 전체 설명을 보려면 AssetBundle 문서를 보세요.




3.2 AssetBundle 레이아웃

요약하면 AssetBundle은 헤더와 데이터 세그먼트의 두 부분으로 구성됩니다.

헤더는 식별자, 압축 유형 및 매니페스트와 같은 AssetBundle의 정보가 들어있습니다. 매니페스트는 Object 이름으로 키가 지정된 조회 테이블 입니다. 각 엔트리는 AssetBundle의 데이터 세그먼트 내에서 주어진 Object가 어디서 찾을 수 있는지 나타내는 바이트 인덱스를 제공합니다. 대부분의 플랫폼에서, 이 조회 테이블은 균형 탐색 트리로 구현됩니다. 특히 윈도우나 OSX 파생 플랫폼(iOS 포함)에서는 red-black 트리를 사용합니다. 따라서 매니페스트를 구성하는데 걸리는 시간은 AssetBundle에 포함된 Asset의 수가 증가에 따라 선형적으로 증가합니다.


데이터 세그먼트는 AssetBundle내부의 Asset을 직렬화 하여 생성 된 원시 데이터가 포함되어 있습니다. 만약 LZMA가 압축 스키마로 지정되어 있으면 직렬화 된 Asset들의 전체 바이트 배열은 압축됩니다. LZ4가 대신 지정 된다면, 각 Asset들을 위한 바이트는 개별적으로 압축됩니다. 만약 압축을 사용하지 않으면, 데이터 세그먼트는 원시 바이트 스트림으로 유지됩니다.


유니티 5.3 이전에는 Object를 AssetBundle 내에 개별적으로 압축 할 수 없었습니다. 결과적으로 유니티 5.3 이전에는 하나 이상의 Object를 AssetBundle에서 불러오면 전체 AssetBundle을 압축 해제해야 했습니다. 일반적으로 유니티는 동일한 AssetBundle에서 후속 로드 요청에 대한 로딩 성능을 향상시키기 위해 압축 해제 된 AssetBundle의 복사본을 캐싱합니다.




3.3 AssetBundle 로딩

AssetBundle은 fivedistinct API를 통해 로드 됩니다. 이 다섯가지 API는 2가지 기준에 따라 다릅니다.

1. AssetBundle이 LZMA 또는 LZ4 아니면 비압축 상태인지 여부

2. AssetBundle이 로드되는 플랫폼에 따라서


API는 다음과 같습니다.

● AssetBundle.LoadFromMemory (Async optional)

● AssetBundle.LoadFromFile (Async optional)

● AssetBundle.LoadFromStream (Async optional)

● UnityWebRequest's DownloadHandlerAssetBundle

● WWW.LoadFromCacheOrDownload (on Unity 5.6 or older)


이 API들로 AssetBundle에 참조하는 방법은 자유롭게 혼합 사용이 가능합니다. 즉, UnityWebRequest를 통해 로드 된 AssetBundle은 AssetBundle.LoadFromFile 이나 AssetBundle.LoadFromMemory 를 통해 로드 된 것과 호환됩니다.



3.3.1 AssetBundle.LoadFromMemory(Async)

유니티는 이 API를 사용하지 않기를 권장합니다.

AssetBundle.LoadFromMemoryAsync 는 관리 코드 바이트 배열(C#의 byte[]) 에서 AssetBundle을 로드합니다. 이는 언제나 소스 데이터를 관리 코드 바이트 배열에서 새로 할당된 인접 기본 메모리 블록으로 복사합니다. AssetBundle이 LZMA 압축이라면 복사할 때 압축을 해제합니다. 비압축 또는 LZ4 압축 AssetBundle이라면 그대로 복사됩니다.


이 API가 소비하는 메모리의 최대량은 AssetBundle 크기의 2배가 됩니다: 하나는 API의해 생성된 기본 메모리에 있는 것 이며, 다른 하나는 관리 바이트 배열에서 API로 전달 한 것 입니다. 이 API를 통해 만들어진 AssetBundle에서 로드 한 Asset은 메모리에서 3번 중복 될 수 있습니다: 하나는 관리 코드 바이트 배열, 다른 하나는 기본 메모리의 AssetBundle, 마지막으로 GPU나 시스템 메모리에 있는 Asset 그 자체 입니다.


유니티 5.3.3 이전에는 이 API를 AssetBundle.CreateFromMemory 라고 불렀습니다. 기능상 차이점은 없습니다.



3.3.2 AssetBundle.LoadFromFile(Async)

AssetBundle.LoadFromFile 은 비압축 또는 LZ4 압축 AssetBundle을 하드디스크나 SD 카드와 같은 로컬 저장소에서 로딩하는데 효율적인 API 입니다.


데스크톱 독립형, 콘솔, 모바일 플랫폼에서 API는 AssetBundle의 헤더만 로드하고 나머지 데이터는 디스크에 남겨둡니다. AssetBundle의 Object는 로딩 메소드(AssetBundle.Load)의 호출이 있거나 인스턴스 ID가 참조 될 경우 로드 됩니다. 이 경우 초과 메모리 소비는 없습니다. 유니티 에디터에서, API는 디스크에서 데이터를 읽어내고 AssetBundle.LoadFromMemoryAsync가 사용 된 것 처럼 모든 AssetBundle을 메모리에 로드합니다. 이 API는 유니티 에디터에서 프로젝트를 프로파일 중일 때 메모리 스파이크가 나타난것으로 보여질 수 있습니다. 이는 기기에서의 성능에는 영향을 미치지 않을 수 있으며 추가 조치 전에 기기에서 스파이크가 일어나는지 테스트를 해 보아야 합니다.


참고: 유니티 5.3 이하의 안드로이드 디바이스 빌드에서, 이 API는 스트리밍 Asset 경로에서 AssetBundle을 로드하는것을 실패합니다. 이 문제는 유니티 5.4에서 수정되었으며 자세한 내용은 유니티 사용 패턴 챕터의 프로젝트와 함께 배포 부분을 참고하세요.



3.3.3 AssetBundleDownloadHandler

UnityWebRequest API는 개발자들이 유니티가 다운로드된 데이터를 처리하는 방법을 명확하게 지정하고 개발자가 불필요한 메모리 사용을 제거할 수 있게 합니다. UnityWebRequest를 사용하여 AssetBundle을 다운로드하는 가장 간단한 방법은 UnityWebRequest.GetAssetBundle의 호출 입니다.


이 가이드의 목적에 따라서, 관심 가질 클래스는 DownloadHandlerAssetBundle 입니다. 워커 스레드를 사용하여 다운로드 된 데이터를 고정 크기 버퍼로 스트리밍하고 버퍼된 데이터를 다운로드 핸들러의 설정에 따라 AssetBundle 캐시나 임시 저장소로 스풀링 합니다. 모든 작업은 네이티브 코드에서 이루어지며, 이는 힙 영역의 증가 부담이 없습니다. 추가로 이 다운로드 핸들러는 모든 다운로드 된 바이트에 대한 네이티브 코드를 유지하지 않기 때문에 AssetBundle 다운로드로 일어나는 메모리 오버해드를 줄입니다.


LZMA 압축 AssetBundle은 다운로드 중에 압축 해제되며 LZ4 압축을 사용하서 캐시됩니다.  이 동작은 Caching.CompressionEnabled 설정을 통해 변경할 수 있습니다.


다운로드가 완료되면 다운로드 핸들러의 AssetBundle 속성은 마치 AssetBundle.LoadFromFile이 호출 된 것 처럼 다운로드 된 AssetBundle에 대한 접근을 제공합니다.


만약 캐싱된 정보가 UnityWebRequest object에 제공되고 유니티의 캐시에 요청 된 AssetBundle이 이미 존재한다면 AssetBundle은 즉시 사용이 가능해지며 이 API는 AssetBundle.LoadFromFile과 동일하게 작동합니다.


유니티 5.6 이전에는 UnityWebRequest 시스템은 고정 워커 스레드 풀을 사용하고 내부 작업 시스템을 사용했습니다. 이는 동시에 UnityWebRequest 시스템의 과도한 동시 다운로드를 방지하기 위해서입니다. 스레드 풀의 크기는 설정이 불가능했습니다. 유니티 5.6에서 더 많은 최신 하드웨어를 수용하고 HTTP 응답 코드와 헤더에 빠른 접근을 허용하기 위해서 위의 방지는 제거되었습니다.



3.3.4 WWW.LoadFromCacheOrDownload

* 참고: 유니티 2017.1 부터 WWW.LoadFromCacheOrDownload 는 단순히 UnityWebRequest를 랩 합니다. 따라서 유니티 2017.1 이상을 사용하는 개발자는 UnityWebRequest로 마이그레이션 해야합니다.

WWW.LoadFromCacheOrDownload 는 앞으로의 릴리즈에서 더이상 사용되지 않습니다.


이하 정보는 유니티 5.6 이하 버전에 해당됩니다.


WWW.LoadFromCacheOrDownload 는 원격 서버와 로컬 저장소에서 Object를 로딩하는 API 입니다. 로컬 저장소의 파일은 file:// URL로 로드 가능합니다. 이미 AssetBundle이 유니티 캐시에 있으면 API는 AssetBundle.LoadFromFile과 같이 동작합니다.


만약 AssetBundle이 아직 캐시되지 않았다면 WWW.LoadFromCacheOrDownload 는 소스에서 AssetBundle을 읽어옵니다. 만약 AssetBundle이 압축된 경우, 워커 스레드를 사용하여 압축을 해제한 뒤 캐시에 올립니다. 압축되어 있지 않다면 워커 스레드를 통해 바로 캐시에 올라갑니다. AssetBundle이 캐시 되면 WWW.LoadFromCacheOrDownload는 압축 해제되어 캐시된 AssetBundle에서 해더 정보를 불러옵니다. 이후 API는 AssetBundle이 로드 된 AssetBundle.LoadFromFile과 동일하게 작동합니다. 이 캐시는 WWW.LoadFromCacheOrDownload와 UnityWebRequest가 공유합니다. 한 API에서 로드 한 AssetBundle은 다른 API를 통해서도 사용 가능합니다.


데이터가 압축 해제되고 고정 크기 버퍼를 통해 캐시로 올라갈 때, WWW Object는 AssetBundle의 전체 바이트 복사본을 네이티브 메모리에 유지합니다. 이 여분의 AssetBundle 복사본은 WWW.bytes 속성을 지원하도록 유지됩니다.


WWW Object에 있는 AssetBundle 바이트 캐시가 일으키는 메모리에 오버해드 때문에 AssetBundle은 최대 몇 메가바이트의 작은 크기로 남아있습니다. AssetBundle 크기 조절에 대한 더 자세한 내용은 AssetBundle 사용 패턴 챕터의 Asset 할당 전략 부분을 참고하세요.


UnityWebRequest와 달리, 이 API는 호출 할 때 마다 새로운 워커 스레드가 생성됩니다. 따라서, 모바일 장치와 같이 제한된 메모리를 가진 플랫폼에서는 메모리 스파이크를 방지하기 위해 이 API를 사용한다면 한번에 하나의 AssetBundle을 다운로드 해야합니다. 이 API를 여러번 호출하여 과도한 수의 스레드를 생성하는것을 조심하세요. 만약 5개 이상의 AssetBundle 다운로드가 필요하다면, 스크립트 코드에서 다운로드 큐를 만들고 관리하여 적은 수의 동시 다운로드를 유지하도록 해야합니다.



3.3.5 권장 사항

가능한 AssetBundle.LoadFromFile을 사용하세요. 이 API는 속도, 디스크 및 메모리 사용 측면에서 가장 효율적입니다.

AssetBundle의 다운로드나 패치가 필요하다면 유니티 5.3 이상의 프로젝트에서는 UnityWebRequest 사용을 권장합니다. 유니티 5.2 이하의 프로젝트에서는 WWW.LoadFromCacheOrDownload 사용을 권장합니다. 다음 챕터의 배포 부분에서 자세하게 설명하겠지만, 프로젝트 설치 프로그램에 포함 된 Bundle로 AssetBundle 캐시를 준비 할 수 있습니다.


UnityWebRequest 나 WWW.LoadFromCacheOrDownload를 사용 할 때 AssetBundle 로딩 후 다운로더 코드가 Dispose를 제대로 호출하도록 해야합니다. C#의 using statement를 사용하는것이 WWW나 UnityWebRequest를 안전하게 사용하는 가장 편리한 방법입니다.


실력있는 엔지니어링 팀이 존재하는 프로젝트는 특별한 방식의 캐싱이나 다운로드가 필요하다면 커스텀 다운로더를 고려할 수 있습니다. 커스텀 다운로더를 만드는것은 쉬운 엔지니어링 작업이 아닙니다. 커스텀 다운로더는 AssetBundle.LoadFromFile과 호환되어야 합니다. 자세한 사항은 다음 챕터의 배포 부분을 확인하세요.




3.4 AssetBundle에서 Asset 로드하기

UnityEngine.Object는 AssetBundle Object에 첨부된 3개의 API를 통해서 로드 될 수 있습니다. 이 API들은 동기적인 혹은 비동기적인 방식을 모두 가지고 있습니다.


LoadAsset (LoadAssetAsync)

LoadAllAssets (LoadAllAssetsAsync)

LoadAssetWithSubAssets (LoadAssetWithSubAssetsAsync)


이 API들의 동기적인 버전은 각각의 비동기적인 버전보다 최소 1프레임이라도 빠릅니다.


비동기 로드는 매 프레임마다 시분할 한도까지 다수의 Object를 로드합니다. 이 동작에 대한 기술적인 이유는 저레벨 로딩 상세 정보 부분에서 확인 가능합니다.


LoadAllAssets는 다수의 독립적인 UnityEngine.Object를 로드할 때 사용합니다. 이는 AssetBundle 내의 개부분 또는 모든 Object를 로드해야 할 때 사용해야합니다. 다른 2개의 API와 비효해서 LoadAllAssets는 LoadAsset을 여러번 호출하는것에 비해여 약간 빠릅니다. 따라서 한번에 로드 될 asset의 수가 많지만 AssetBundle의 66% 미안일 경우 AssetBundle을 작은 크기의 Bundle로 나누어 LoadAllAssets를 호출하는것을 고려해보세요.


LoadAssetWithSubAssets는 다수의 내장 Object를 가지고 있는 합성 Asset(애니메이션이 포함 된 FBX 모델이나 다수의 스프라이트가 포함 된 스프라이트 아틀라스 등)을 로드 할 때 사용해야합니다. 로드해야 할 Object들이 같은 Asset에서 비롯되지만 AssetBundle 내부에 다른 관련 없는 Object가 많다면 이 API를 사용하세요.


다른 경우 LoadAsset 또는 LoadAssetAsync를 사용하세요.



3.4.1 저레벨 로딩 상세 정보

UnityEngine.Object 로딩은 메인 스레드에서 수행됩니다: Object의 데이터는 워커 스레드가 저장소에서 읽어옵니다. 유니티 시스템에서 스레드에 민감한 부분을 건드리지 않는 항목은 워커 스레드에서 일어납니다. 예를 들어 메쉬에서 만들어지는 VBO나 텍스쳐 압축 해제와 같이 스크립팅이나 그래픽관련 부분들이 있습니다.


유니티 5.3 이후부터 Object 로딩은 병렬화 되었습니다. 워커 스레드에서 다수의 Object들이 역직렬화되고 처리되고 통합됩니다. Obejct의 로딩이 완료되면 Awake 콜백이 호출되며 해당 Object는 다음 프레임 부터 유니티 엔진에서 사용이 가능해집니다.


동기 AssetBundle.Load 메소드는 Object 로딩이 완료될 때 까지 메인 스레드를 정지시킵니다. 이는 시분할 Object 로딩이기 때문에 Object Integration은 프레임 시간의 밀리 초 이상을 차지하지 않습니다. 밀리 초의 수는 Application.backgroundLoadingPriority 설정에 의해서 결정됩니다.


● ThreadPriority.High: 프레임 당 최대 50 밀리 초

● ThreadPriority.Normal: 프레임 당 최대 10 밀리 초

● ThreadPriority.BelowNormal: 프레임 당 최대 4 밀리 초

● ThreadPriority.Low: 프레임 당 최대 2 밀리 초


유니티 5.2 이후부터 Object 로딩을 위한 프레임 시간의 한계까지 다수의 Object가 로드 됩니다. 다른 모든 요건이 동일하다고 가정하면, 비동기 방식의 asset 로딩 API는 비동기 요청과 Object가 엔진에서 사용 가능해지는 것 사이의 최소 1프레임 지연 때문에 동등한 동기 방식보다 무조건 더 오래 걸립니다.



3.4.2 AssetBundle 의존 관계

AssetBundle 사이의 의존 관계는 런타임 환경에 따라 2개의 다른 API를 사용하여 자동으로 추적됩니다. 유니티 에디터에서 AssetBundle 의존 관계는 AssetDatabase API를 통해 쿼리 가능합니다. AssetBundle 의 할당과 의존 관계는 AssetImpoter API를 통해서 접근하고 변경 가능합니다. 런타임에서 유니티는 AssetBundle이 빌드 되는 중에 생성되는 의존성 정보를 로드하기 위한 ScriptableObject 기반 AssetBundleManifest API와 같은 API를 제공합니다.


AssetBundle은 하나 이상의 부모 AssetBundle의 UnityEngine.Object가 다른 AssetBundle의 UnityEngine.Object를 참조한다면 다른 AssetBundle에 종속됩니다. Object간 참조에 대한 더 많은 정보는 Assets, Objets그리고 직렬화 챕터의 Object간 참조 부분을 확인하세요.


Assets, Objets그리고 직렬화 챕터 직렬화와 인스턴스 부분에서 설명한대로 AssetBundle은 AssetBundle에 포함된 Object의 파일 GUID와 로컬 ID로 식별되는 소스 데이터의 소스로 사용됩니다.


인스턴스 ID가 처음으로 참조될 때 Object가 로드 되고, AssetBundle이 로드 될 때 Object에 유효한 인스턴스 ID가 할당되기 때문에 AssetBundle의 로드 순서는 중요하지 않습니다. 대신, Object가 로드 되기 전에 Object의 의존 관계가 포함된 모든 AssetBundle을 로드하는 것이 중요합니다. 유니티는 부모 AssetBundle이 로드 될 때 하위 AssetBundle을 자동으로 로드하지 않습니다.


Example:

Material A가 Texture B를 참조한다고 가정. Material A는 AssetBundle 1에 패키징 되어있고 Texture B는 AssetBundle 2에 패키징 되어있음.




이 경우 AssetBundle 2는 AssetBundle 1에서 Material A를 로드하기 전에 로드 되어야 합니다.


이는 AssetBundle 2가 반드시 AssetBundle 1보다 먼저 로드되어야 함을 의미하지 않으며 Texture B가 명시적으로 AssetBundle 2에서 로드되어야 함을 의미하지 않습니다. 그저 AssetBundle 1에서 Material A를 로드 하기 전에 AssetBundle 2를 로드하면 됩니다.


하지만 유니티는 AssetBundle 1이 로드 될 때 AssetBundle 2를 자동으로 로드하지 않습니다. 이는 반드시 코드에서 수동으로 이루어집니다.


AssetBundle의 의존 관계에 대한 더 많은 정보는 메뉴얼을 참고하세요.



3.4.3 AssetBundle 매니페스트

BuildPipeline.BuildAssetBundle API를 사용해서 AssetBundle 빌드 파이프라인을 실행할 때, 유니티는 각 AssetBundle의 의존 관계 정보를 포함하여 Object를 직렬화 합니다. 이 데이터는 AssetBundleManifest 타입의 단일 Object를 포함하는 별도의 AssetBundle에 저장됩니다.


이 Asset은 AssetBundle이 빌드 될 때 상위 디렉토리와 동일한 이름을 가진 AssetBundle에 저장됩니다. 만약 프로젝트가 (projectroot)/build/Client/ 에 있는 폴더에 AssetBundle을 빌드한다면 매니페스트가 포함된 AssetBundle은 (projectroot)/build/Client/Client.manifest 로 저장됩니다.


매니페스트를 포함한 AssetBundle은 다른 AssetBundle과 똑같이 로드, 캐싱 또는 런로드가 가능합니다.


AssetBundleManifest Object는 GetAllAssetBundles API를 제공합니다. 이는 모든 매니페스트와 동시에 빌드된 모든 AssetBundle의 리스트와 특정 AssetBundle의 의존 관계를 쿼리 하는 2개의 메소드를 제공합니다.


AssetBundleManifest.GetAllDependencies AssetBundle의 하위 또는 하위의 하위 등 AssetBundle의 모든 계층적 의존 관계를 반환


AssetBundleManifest.GetDirectDependencies AssetBundle의 직접적인 하위 만 반환


이 두 API는 모두 문자열 배열을 할당합니다. 따라서 아껴서 사용해야하며 애플리케이션의 성능에 민감한 부분에서 사용하지 않아야 합니다.



3.4.4 권장 사항

대부분의 경우 플레이어가 애플리케이션에서 성능이 중요한 부분(메인 게임 영역)에 들어가기 전에 최대한 많은 Object들을 로드하는것이 좋습니다. 이는 특히 로컬 저장소에 대한 접근이 느리고 Object 오딩, 언로딩 등 메모리 변동으로 가비지 컬렉터가 실행되는 모바일 플랫폼에서 중요합니다.


애플리케이션이 동적이라 Object 로드 및 언로드가 필수인 프로젝트라면 Object와 AssetBundle의 언로드에 대해서 AssetBundle 사용 패턴로드 된 Asset의 관리 부분을 참고하세요.

'유니티' 카테고리의 다른 글

유니티의 컴파일  (0) 2019.03.14
AssetBundle의 사용 패턴  (0) 2018.03.09
Resources 폴더  (0) 2018.03.05
Assets, Objects 그리고 직렬화  (0) 2018.03.04
AssetBundles 및 Resources 가이드  (0) 2018.03.03

Resources 폴더    [원본 링크]


확인 완료한 버전 2017.3 - 난이도: 고급


이 문서는 유니티 AssetBundles 및 Resources 가이드의 3번째 챕터 입니다.


이 챕터에서는 Resources 시스템에 대해 설명합니다. 이것은 개발자가 Resources 라고 이름 지은 하나 이상의 폴더에 Asset들을 저장하고 Resources API를 이용하여 Asset들에서 Object들을 런타임 상에서 로드하고 언로드 하는 시스템 입니다.




2.1 Resources 시스템에 대한 모범 사례


사용하지 마세요.


사용하지 않도록 권장하는 이유는 다음과 같습니다.


● Resources 폴더를 사용하면 세분화 된 메모리 관리가 더욱 어려워집니다.

● 부적절한 Resources 폴더 사용 시 어플리케이션 시작 시간과 빌드 시간이 길어집니다.

- Resources 폴더의 수가 증가하면 폴더 내의 Asset 관리가 어려워집니다.

● Resources 시스템은 특정 플랫폼에 맞춰진 콘텐츠 제공 을 위한 프로젝트의 역량을 감소시키며 차후 콘텐츠 업데이트 가능성을 손실시킵니다.

- AssetBundle Variant는 장치 단위로 콘텐츠를 조절 할 수 있는 유니티의 기본 도구 입니다.




2.2 Resources 시스템의 적절한 사용


Resources 시스템이 도움이 되는 두가지 구체적인 예가 있습니다.


1. Resources 폴더의 쉬운 사용법은 빠르게 프로토 타입을 만들 때 용이합니다. 하지만 프로젝트가 본격적으로 시작된다면 사용하지 않아야 합니다.


2. Resources 폴더는 다음과 같이 사소한 경우에는 유용 할 수 있습니다:

● 프로젝트 전체에서 일반적으로 필요한 경우

● 메모리를 많이 사용하지 않음

● 패치가 발생하지 않거나 플랫폼이나 장치에 따라 다르지 않을 경우

● 최소한의 부트스트랩에 사용




2.3 Resources의 직렬화


Resources 폴더 내부의 Asset 및 Object들은 프로젝트가 빌드 될 때 하나의 직렬화 된 파일로 결합됩니다. 이 파일은 AssetBundle과 같이 metadata와 인덱싱 정보를 포함하고 있습니다. AssetBundle 문서에서 설명한 것 처럼 이 인덱스는 주어진 Object의 이름을 파일 GUID와 로컬 ID로 변환 하는데 사용되는 직렬화 된 검색 트리를 포함합니다. 이는 직렬화 된 파일 내부의 특정 바이트 오프셋에서 Object의 위치를 찾는데 사용됩니다.


대부분의 플랫폼에서 데이터 검색 구조는 균형 탐색 트리이며. O(n log(n))의 속도로 증가하는 구축 시간을 가집니다. 이 증가로 인해 인덱스 로딩 시간은 Resources 폴더에 있는 Object가 늘어날수록 선형적으로 증가합니다.


이 작업은 건너 뛸 수 없으며 초기 조작 불가능한 시작화면이 표시되는 어플리케이션 시작 시 발생합니다. Resources 폴더에 포함된 Object들이 어플리케이션의 첫 씬에 거의 필요가 없더라도 10000개의 Asset을 포함하는 Resources 시스템을 초기화 하는데 낮은 사양의 모바일 장치에서는 수 초의 시간이 걸립니다.

'유니티' 카테고리의 다른 글

유니티의 컴파일  (0) 2019.03.14
AssetBundle의 사용 패턴  (0) 2018.03.09
AssetBundle의 기본 요소  (0) 2018.03.07
Assets, Objects 그리고 직렬화  (0) 2018.03.04
AssetBundles 및 Resources 가이드  (0) 2018.03.03

Assets, Objects 그리고 직렬화      [원본 링크]


확인 완료한 버전 2017.3 - 난이도: 고급


이 문서는 유니티 AssetBundles 및 Resources 가이드의 2번째 챕터 입니다.


이 챕터에서는 유니티의 직렬화 시스템의 깊은 내부 구조와 유니티 에디터와 런타임에서 Objects 사이의 견고한 참조를 유지하는 방법을 설명합니다. Objects와 Assets의 기술적 차이점에 대해서도 다룹니다. 여기서 다루는 내용은 유니티에서 Assets의 효율적인 로드, 언로드 방식을 이해하는 기본이 됩니다. 적절한 Asset 관리는 로딩 시간을 짧게 유지하고 메모리 사용을 줄이는데 중요합니다.


1.1 Assets와 Objects

유니티에서 데이터를 올바르게 관리하는 방법을 이해하려면, 유니티가 어떤 방법으로 데이터를 식별하고 직렬화 하는지를 알아야 합니다. 첫 번째 요점은 Assets와 UnityEngine.Objects의 차이 입니다.


Asset은 디스크에 있는 파일로 유니티 프로젝트의 Assets 폴더에 저장되어 있습니다. 텍스처, 3D 모델, 또는 오디오 클립 등 이 일반적인 타입의 Assets 입니다. materials와 같은 일부 Assets는 유니티 고유 형태의 데이터를 포함하고 있습니다. FBX파일과 같은 Assets는 기본 형태로 처리되어야 합니다.


UnityEngine.Object나 Object 와 같이 대문자 'O' 집합인 것들은 리소스의 특정한 인스턴스를 설명하는 직렬화 된 데이터의 집합입니다. 이것은 유니티 엔진이 사용하는 mesh, sprite, AudioClip, AnimationClip 등 어떤 타입이든 될 수 있습니다. 모든 Objects는 UnityEngine.Object 기본 클래스의 하위 클래스입니다.


대부분 Object 유형들은 내장되어있지만 2개의 특수 유형이 있습니다.


1. ScriptableObject 는 개발자가 고유 데이터 유형을 정의 할 수 있도록 편리한 시스템을 제공합니다. 이 유형들은 유니티에서 기본적으로 직렬화, 직렬화 해제가 가능하며 유니티 에디터의 Inspector창에서 조작이 가능합니다.


2. MonoBehaviourMonoScript를 연결하는 래퍼를 제공합니다. MonoScript는 유니티가 특정 어셈블리와 네임스페이스 내에서 특정 스크립팅 클래스에 대한 참조를 유지하는데 사용하는 내부 데이터 타입입니다.MonoScript는 실제 실행 코드를 포함하지 않습니다.


Assets와 Objects에는 일대다 관계가 있습니다.이는 Asset파일이 하나 이상의 Objects를 포함한다는 것 입니다.




1.2 Object간 참조

모든 UnityEngine.Objects는 다른 UnityEngine.Objects에 대한 참조를 가질 수 있습니다. 이런 각각의 Objects들은 같은 Asset 파일에 있거나 다른 Asset 파일에서 가져올 수 있습니다. 예를 들어 material Object는 하나 이상의 texture Object를 참조 할 수 있습니다. 이 texture Objects들은 보통 하나 이상의 texture Asset 파일(png 또는 jpg)에서 가지고 옵니다.


직렬화 될 때, 이 참조들은 '파일 GUID'와 '로컬 ID' 로 2개의 데이터로 구성됩니다. 파일 GUID는 대상 리소스가 저장된 Asset 파일을 식별합니다. 로컬 고유인[각주:1] 로컬 ID는 Asset 파일이 다수의 Object를 포함 할 수 있기 때문에 Asset 파일에 있는 각 Object를 식별합니다.


파일 GUID들은 .meta 파일 안에 저장됩니다. .meta 파일들은 유니티에 Asset이 최초로 추가될 때 생성되며 Asset이 저장된 같은 경로에 저장됩니다.


위에서 설명한 식별, 참조 시스템은 텍스트 편집기에서 볼 수 있습니다: 새로운 유니티 프로젝트를 만들고 에디터 설정을 변경하여 meta파일을 노출시키고 Asset을 text로 직렬화 하도록 합니다. material을 만들고 texture를 프로젝트에 추가합니다. 씬 안에 큐브를 만들어 material을 적용한 뒤 씬을 저장합니다.


텍스트 편집기를 사용하여  material과 연관된 .meta 파일을 엽니다. "guid"라고 표시된 줄이 파일 최상단에 나타납니다. 이 줄은 material Asset의 파일 GUID 입니다. 로컬 ID를 찾으려면 material 파일을 텍스트 편집기로 엽니다. material Object의 정의는 다음과 같습니다.


--- !u!21 &2100000

Material:

 serializedVersion: 3

 ... more data …


위의 예제에서, &가 앞에오는 숫자가 material의 로컬 ID 입니다. 이 material Object가 파일 GUID "abcdefg"로 식별되는 Asset안에 위치하면, material Object는 파일 GUID "abcdefg"와 로컬 ID 2100000로 고유하게 식별됩니다.




1.3 왜 파일 GUID와 로컬 ID 인가?

유니티의 파일 GUID와 로컬 ID가 필요한 이유는 무엇인가? 답은 견고함 그리고 플랫폼 독립적인 유연한 워크 플로우 제공을 위해서 입니다. 


파일 GUID는 파일의 특정 위치의 추상화를 제공합니다. 특정 파일과 특정 파일 GUID가 연관되어 있다면, 파일의 디스크 상의 위치는 상관 없어집니다. 파일을 참조하는 모든 Object들이 업데이트 될 필요 없이 자유롭게 파일을 옮길 수 있습니다. 


Asset 파일은 다수의 UnityEngine.Object 리소스가 포함 될 수 있기 때문에 각 Object들을 명확히 구분하기 위해서 로컬 ID가 필요합니다.


만약 Asset 파일과 연관된 파일 GUID가 손실된 경우 해당 Asset 파일을 참조하는 모든 Object들의 참조 또한 손실됩니다. 따라서 .meta파일은 연관된 Asset 파일과 같은 이름으로 같은 경로에 있어야 합니다. 유니티는 삭제되거나 잘못 배치된 .meta 파일을 다시 생성하는것을 기억하세요.


유니티 에디터는 알려진 파일 GUID들에 대한 특정 파일 경로 맵을 가지고 있습니다. Asset이 로드되거나 추가될 때 마다 맵의 항목이 기록됩니다. 맵 항목은 Asset의 특정 경로와 Asset의 파일 GUID를 연결합니다. 유니티 에디터가 열려있다면 .meta 파일이 손실되어도 Asset의 경로가 바뀌지 않았을 경우 에디터는 동일한 파일 GUID 유지를 보장합니다.


만약 유니티 에디터가 닫혀있는 동안 .meta파일이 손실되거나 경로가 변경된 Asset의 .meta파일이 함께 이동하지 않았다면 Asset을 참조하는 모든 Object들의 참조가 손실됩니다.




1.4 합성 Assets와 importer

Assets와 Objects 부분에서 언급했듯이 비 고유 Asset 타입들은 유니티로 임포트 되어야 합니다. 이 작업은 asset importer가 수행합니다. 일반적으로 자동호출 되지만 AssetImporter API를 통해 스크립트에서 노출됩니다. 예를 들어, TextureImporter API는 PNG파일과 같은 개별적인 texture Asset들을 가져올 때 사용하는 접근 설정을 제공합니다.


임포트 작업의 결과는 하나 이상의 UnityEngine.Objects 입니다. 이것들은 유니티 에디터에서 부모 Asset 하위의 여러 Asset들로 보여집니다. 예를들어 스프라이트 아틀라스로 가져온 texture Asset 아래에 내포된 다수의 sprite 들이 있습니다. 소스 데이터가 같은 Asset 파일에 저장된 Object들은 파일 GUID를 공유합니다. 각 Object는 texture Asset 내에서 로컬 ID로 구분됩니다.


이 임포트 과정은 소스 Asset들을 유니티 에디터에서 대상으로 한 플랫폼에 적합한 형식으로 변환시킵니다. 이 과정에는 texture 압축 과 같은 무거운 작업이 포함될 수 있습니다. 이 작업은 시간이 많이 소요될 수 있기 때문에, Library 폴더에 캐시되어있습니다. 따라서 다시 에디터를 실행할 때 Asset들을 임포트 하지 않습니다.


임포트 과정의 결과는 Asset들의 파일 GUID 처음 2자리로 명명된 폴더에 저장됩니다. 이 폴더는 Library/metadata/ 폴더에 저장됩니다. Asset의 개별 Object들은 Asset의 파일 GUID와 동일한 이름을 가진 단일 바이너리 파일로 직렬화 됩니다.


이 프로세스는 모든 비고유 Asset에 적용됩니다. 고유 Asset은 재 직렬화가 필요하지 않거나 변환 과정이 길지 않습니다.




1.5 직렬화와 인스턴스

파일 GUID와 로컬 ID는 견고하지만, GUID 비교는 느리고 런타임 시 더욱 성능을 요구합니다. 유니티는 내부적으로 파일 GUID와 로컬 ID들을 단순한 세션-고유의 정수 형태 캐시로[각주:2] 변환하여 유지합니다. 이는 인스턴스 ID로 부르며, 새로운 Object가 캐시에 등록될 때 마다 단순하게 증가시키며 할당됩니다.


캐시는 주어진 인스턴스 ID, 파일 GUID 및 Object들의 소스 데이터가 정의된 로컬 ID와 메모리에 있는 Object의 인스턴스 (있는 경우) 간의 매핑을 유지합니다. 이를 통해 UnityEngine.Objects는 서로간의 참조를 견고하게 유지할 수 있습니다. 인스턴스 ID의 참조를 해석하면 인스턴스 ID가 나타내는 로드된 Object를 빠르게 반환할 수 있습니다. 만약 대상 Object가 아직 로드되지 않았을 경우 파일 GUID와 로컬 ID를 통해 Object의 소스 데이터의 확인이 가능하고 유니티가 적시에 Object를 로드시킬 수 있습니다.


시작 시, 인스턴스 ID 캐시는 리소스 폴더에 포함 된 모든 Object뿐 아니라 프로젝트에서 즉시 필요한 (내장된 씬들에서 참조하는) 모든 Object에 대한 데이터로 초기화 됩니다. 캐시에 추가되는 것들은 런타임 시 새로운 Asset이 임포트 되거나[각주:3] AssetBundle에서 Object가 로드 될 경우가 있습니다. 인스턴스 ID가 캐시에서 제거되는 경우는 특정 파일 GUID나 로컬 ID에 대한 접근을 제공하는 AssetBundle이 언로드 될 때 입니다. 이 경우 인스턴스 ID와 특정 파일 GUID와 로컬 ID 사이의 매핑은 메모리 절약을 위해 삭제됩니다. 만약 AssetBundle이 다시 로드 된다면 다시 로드 된 AssetBundle에서 Object를 로드 할 때 마다 새로운 인스턴스 ID가 만들어집니다.


AssetBundle의 언로드에 대한 더 자세한 설명은 AssetBundle 사용 패턴 문서의 로드 된 Assets의 관리 부분을 참조하세요.


특정 플랫폼에서, 특정 이벤트로 인해 Object가 메모리에서 강제로 지워질 수 있습니다. 예를 들어, IOS에서 앱이 일시적으로 멈춤다면 그래픽 Asset들은 그래픽 메모리에서 언로드 될 수 있습니다. 이때 Object가 언로드 된 AssetBundle로 부터 비롯된 경우 유니티는 Object의 소스 데이터를 다리 소르 할 수 없습니다. 이 Object에 대한 어떠한 참조도 유효하지 않습니다. 이 경우 씬은 보이지 않는 메시나 자홍색 texture로 보이게 됩니다.


구현 참고 사항: 런타임 시 위의 제어 흐름은 확실하지 않습니다. 파일 GUID와 로컬 ID들의 비교는 런타임에서 무거운 로딩 작업 시 성능이 충분하지 않을 수 있습니다. 유니티 프로젝트를 만들 때, 파일 GUID와 로컬 ID는 결정적으로 단순한 형식으로 매핑됩니다. 하지만 개념은 동일하게 유지되며 런타임 시 파일 GUID와 로컬 ID에 대한 생각은 유용할 수 있습니다. 이것은 Asset 파일 GUID가 런타임 중 쿼리 될 수 없는 이유이기도 합니다.




1.6 MonoScripts

MonoBehaviour는 MonoScript에 대한 참조가 있으며 MonoScript는 특정 스크립트 클래스를 찾는데 필요한 정보를 담고 있다는 것을 이해하는 것은 중요합니다. Object 유형은 실행 가능한 어떠한 스크립트 클래스 코드도 가지고있지 않습니다.


MonoScript는 3개의 문자열을 가지고 있습니다.: 어셈블리 이름, 클래스 이름, 네임스페이스


프로젝트를 빌드하는 동안 유니티는 Assets 폴더안에 있는 모든 느슨한 스크립트 파일들을 모노 어셈블리로 컴파일합니다. Plugins 하위 폴더 외부에 있는 C# 스크립트들은 Assembly-CSharp.dll에 저장됩니다. Plugins 폴더 내부의 스크립트들은 Assembly-CSharp-firstpass.dll에 저장됩니다. 유니티 2017.3에서는 커스텀 관리 어셈블리 정의 기능을 도입하였습니다.


이 어셈블리들은 사전 빌드 된 어셈블리 DLL과 같이 유니티 어플리케이션 최종 빌드에 포함됩니다. 또한 MosoScript가 참조하는 어셈블리들이기도 합니다. 다른 리소스들과 달리 유니티 어플리케이션에 포함된 모든 어셈블리들은 어플리케이션 시작 시 로드 됩니다.


이 MonoScript Object는 AssetBundle(또는 씬이나 프리팹)이 어떠한 MonoBehaviour 컴포넌트에도 실제 실행 가능한 코드를 포함하지 않고있는 이유이기도 합니다. 이것은 MonoBehaviours가 다른 AssetBundles에 있어도 다른 MonoBehaviours가 특정 공유 클래스를 참조 가능하게 합니다.




1.7 리소스 수명주기

로딩 시간을 줄이고 어플리케이션의 메모리 공간을 관리하려면, UnityEngine.Objects의 리소스 수명주기를 이해하는것이 중요합니다. Object들은 특정 시간이나 정의된 시간에 메모리에 로드/언로드 됩니다.


Object가 자동으로 로드 되는 경우:

1. 참조되는 Object에 인스턴스 ID가 매핑 되는 경우

2. Object가 현재 메모리에 로드 되어있지 않았을 경우

3. Object의 소스 데이터가 찾아지는 경우


Object는 Object를 생성하거나 리소스 로딩 API(AssetBundle.LoadAsset)를 사용하여 스크립트에 명시적으로 로드 할 수도 있습니다. Object가 로드되면 유니티는 각 참조의 파일 GUID와 로컬 ID를 인스턴스 ID로의 변환을 시도합니다. Object는 로드됩니다. 다음 2가지 조건에 해당되면 인스턴스 ID가 처음 참조 될 때 필요할 경우 Object가 로드 됩니다.


1. 인스턴스 ID가 아직 로드 되지 않은 Object를 참조 할 경우

2. 인스턴스 ID 캐시에 유효한 파일 GUID와 로컬 ID가 등록 된 경우


이것은 보통 참조 자체가 로드된 직후에 일어납니다.


만약 파일 GUID와 로컬 ID가 인스턴스 ID를 가지고 있지 않거나 인스턴스 ID가 언로드 된 Object를 참조하는 유효하지 않은 파일 GUID나 로컬 ID를 참조한다면 참조는 유지되지만 Object는 로드되지 않습니다. 이것은 유니티 에디터에서 Missing 참조로 나타납니다. 어플리케이션 실행중이나 씬 뷰에서는 Missing Object는 그것의 타입에 따라 다른 방식으로 표현됩니다. 예를들어 메시는 보이지 않고 texture는 자홍색으로 보입니다.


Object는 3가지 특정 시나리오에서 언로드 됩니다.


● Object들은 사용되지 않는 Asset 정리가 발생하면 자동으로 언로드 됩니다. 이 과정은 씬이 파괴적으로 변경되거나 (SceneManager.LoadScene이 비가산적으로 호출 될 때), 또는 스크립트에서 Resources.UnloadUnesedAssets API를 호출하면 트리거 됩니다. 이 과정은 참조되지 않는 Object들만 언로드 합니다. Mono 변수가 Object에 대한 참조를 유지하지 않고, Object에 대한 참조를 가진 다른 활성화된 Object가 없는 경우에만 언로드 됩니다. 또한 HideFlags.DontUnloadUnusedAssetHideFlags.HideAndDontSave 표시 된 항목들은 언로드 되지 않음을 기억하세요.


● Resources 폴더에서 가져온 Object들은 Resources.UnloadAsset API를 통해 명시적으로 언로드 가능합니다. 이러한  Object의 인스턴스 ID는 계속 유효하며 유효한 파일 GUID와 로컬 ID를 유지합니다. 만약 Mono 변수나 다른 Object가 Resources.UnloadAsset 으로 언로드 된 Object를 참조하고 있다면 참조가 불릴 경우 해당 Object는 즉시 다시 로드 됩니다.


● AssetBundle에서 가져온 Object는 AssetBundle.Unload(true) API를 호출하여 즉시 언로드 됩니다. 이것은 Object 인스턴스 ID의 파일 GUID와 로컬 ID를 즉시 무효화 하고 실시간으로 참조중인 참조는 손실된 참조로 변합니다. C# 스크립트에서는 언로드 된 Object의 메서드나 속성에 접근을 시도하면 NullReferenceException이 발생합니다.


만약 AssetBundle.Unload(false)가 불리면, AssetBundle에서 불러와 작동중인 Object는 파괴되지 않지만, 유니티는 인스턴스 ID의 파일 GUID와 로컬 ID에 대한 참조를 무효화 합니다. 만약 그것들이 후에 메모리에서 언로드 되면 언로드 된 Object에 대한 참조가 남아있더라도 유니티에서 해당 Object를 다시 로드하는것은 불가능해집니다.[각주:4]




1.8 큰 계층 로드

프리팹 직렬화와 같은 유니티 GameObjects 계층을 직렬화 할 때, 전체 계층이 완전히 직렬화 되는것을 기억해야 합니다. 즉, 계층의 모든 GameObject와 컴포넌트는 직렬화된 데이터에 개별적으로 표현됩니다. 이는 계층의 GameObjects들을 인스턴스화 하고 불러오는데 필요한 시간에 흥미로운 영향을 줍니다.


GameObjects 계층을 만들 때, CPU 시간은 여러가지 방향으로 사용됩니다.

● 소스 데이터 읽기 (저장소, AssetBundle, 다른 GameObjects 등)

● 새로운 Transform 사이에서 부모-자식 관계 설정

● 새로운 GameObjects나 컴포넌트의 인스턴스화

● 새로운 GameObjects나 컴포넌트를 메인 스레드에서 깨울 때


위 4가지 중 아래 3가지의 시간 비용은 기존 계층에서 복제되거나 저장소에서 로드되는지에 관계 없이 일반적으로 변하지 않습니다. 하지만, 소스 데이터를 읽어오는 시간은 컴포넌트나 GameObjects가 계층에서 직렬화 되는 수에 따라 선형적으로 증가합니다. 그리고 데이터 소스의 속도에 따라 배가 됩니다.


모든 플랫폼에서 장치의 저장소에서 데이터를 로드 하는것 보다 메모리에서 불러오는것이 훨씬 빠릅니다. 또한 사용 가능한 저장소의 특성은 플랫폼마다 매우 다양합니다. 따라서 플랫폼의 느린 저장소에서 프리팹을 불러오면 직렬화된 프리팹 데이터를 저장소에서 읽어오는데 걸리는 시간은 프리팹을 인스턴스화 하는데 걸리는 시간을 빠르게 초과 할 수 있습니다. 즉, 로딩 작업의 비용은 저장소 I/O 시간에 종속됩니다.


앞서 언급했듯이, monolithic 프리팹을 직렬화 하면 모든 GameObjects와 컴포넌트들의 데이터가 별도로 직렬화 되고 이 데이터는 중복 될 수 있습니다. 예를 들어 UI 화면의 30개의 동일한 요소들은 30번 직렬화 되어 큰 이진 데이터가 생기게 됩니다. 로드시에는 30번 복제 된 요소의 각 GameObjects 및 컴포넌트들에 대한 데이터는 새로 인스턴스화 된 Object로 전송되기 전에 읽어져야 합니다. 이 파일 읽기 시간은 큰 프리팹의 인스턴스화에 걸리는 시간에 큰 영향을 줍니다. 큰 계층은 모듈 단위로 인스턴스화 하여 런타임 중에 합쳐야 합니다.


유니티 5.4 노트 : 유니티 5.4는 메모리의 transform 표현 방법을 변경했습니다. 각 루트 transform의 하위 계층은 작고 연속된 메모리 영역에 저장됩니다. 새로운 GameObject를 인스턴스화 할 때 다른 계층 안으로 재배치 될 예정이라면, GameObject.Instantiate 의 부모 인수를 허용하는 새 오버로드 형태의 사용을 고려해보세요. 이것을 사용하면 새 GameObject에 대한 루트 transform 계층 할당이 이루어지지 않습니다. 테스트 결과 이 경우 인스턴스화 작업에 필요한 시간이 5~10% 단축되었습니다.




  1. AA 로컬 ID는 Asset 파일 내의 다른 모든 로컬 ID에서 유일합니다. [본문으로]
  2. 내부적으로 이 캐시를 PersistentManager라고 부릅니다. [본문으로]
  3. 런타임 중 생성 된 Asest의 예는 다음과 같은 스크립트에서 만들어진 Texture2D Object가 있습니다. var myTexture = new Texture2D(1024, 768); [본문으로]
  4. 런타임 중 언로드가 일어나지 않고 Object가 메모리에서 제거되는 가장 일반적인 경우는 유니티가 그래픽 컨텍스트의 제어 권한을 상실하는 경우 입니다. 이는 보통 모바일 앱이 일시 중지되어 백그라운드로 강제 실행되는 경우 발생합니다. 이 경우 모바일 OS는 일반적으로 GPU 메모리에서 모든 그래픽 리소스를 제거합니다. 앱이 다시 활성화 되면, 유니티는 GPU가 씬을 다시 랜더링 하기 전에 필요한 모든 텍스처, 쉐이더, 메쉬를 다시 로드해야 합니다. [본문으로]

'유니티' 카테고리의 다른 글

유니티의 컴파일  (0) 2019.03.14
AssetBundle의 사용 패턴  (0) 2018.03.09
AssetBundle의 기본 요소  (0) 2018.03.07
Resources 폴더  (0) 2018.03.05
AssetBundles 및 Resources 가이드  (0) 2018.03.03

AssetBundles 및 Resources 가이드      [원본 링크]


확인 완료한 버전 2017.3 - 난이도: 고급


이것은 유니티 엔진에서 Assets와 resource 관리에 대하여 심층적인 내용을 다루는 문서 모음입니다.

상급 개발자에게 유니티의 Asset과 직렬화 시스템에 관한 깊이있는 source-level의 지식을 제공하고자합니다.

Unity AssetBundle 시스템의 기술적 도태와 현재 그것들을 사용하는 최상의 사례를 살펴봅니다.


이 가이드는 4개의 챕터로 분류되어 있습니다.


1. Assets, Objects 그리고 직렬화

유니티가 low-level에서 Assets를 어떻게 직렬화 하고 Assets간 참조를 어떻게 관리하는지에 대하여 자세한 사항을 다룹니다. 이 챕터에서 가이드 전체에서 사용하는 용어를 정의하기 때문에 이 챕터부터 읽기를 강하게 추천합니다.


2. Resources 폴더

내장 Resources API 에 대하여 설명합니다.


3. AssetBundle의 기본 요소

챕터1에서 익힌 정보를 바탕으로 AssetBundles의 작동방식을 설명하고 AssetBundles의 로드 그리고 AssetBundles에서 Assts을 로드하는 방법을 다룹니다.


4. AssetBundle 사용 패턴

AssetBuldles의 실제 사용을 둘러싼 많은 주제를 다루는 긴 문서 입니다. 이는 AssetBundles에 Assets을 할당하거나 로드한 Assets를 관리하는 내용을 포함하고 있으며, AssetBundles를 사용하는 개발자들이 자주 접하는 문제를 설명합니다.


참고: 이 가이드에서 사용하는 용어 Objects와 Assets는 유니티의 공개 API에서 사용하는 명명 규칙과 다릅니다.

이 가이드에서 Objects라고 부르는 데이터는 AssetBundle.LoadAsset이나 Resources.UnloadUnusedAssets와 같은 유니티 공개 API에서 Assets라고 부릅니다. 이 가이드에서 Assets라고 부르는 파일들은 공개 API에서 잘 노출되지 않습니다. 일반적으로 노출 될 경우 AssetDatabaseBuildPipeline같은 빌드 관련 코드에서만 노출됩니다. 이 경우 공개 API에서는 files라고 부릅니다.


'유니티' 카테고리의 다른 글

유니티의 컴파일  (0) 2019.03.14
AssetBundle의 사용 패턴  (0) 2018.03.09
AssetBundle의 기본 요소  (0) 2018.03.07
Resources 폴더  (0) 2018.03.05
Assets, Objects 그리고 직렬화  (0) 2018.03.04

+ Recent posts