언리얼 오브젝트 패키지
- 단일 언리얼 정보는 직렬화를 통해서 저장가능
- 복잡한 계층 구조를 가진 언리얼 오브젝트를 효과적으로 저장, 불러오기 방법을 통일
- UPackage 단위로 언리얼 오브젝트를 관리
- 패키지의 중의적 개념
- 다양한 곳에서 패키지 단어를 사용
- 언리얼 오브젝트를 감싼 포장 오브젝트
- 개발된 최종 콘텐츠를 정리해 프로그램으로 만드는 작업
- DLC와 같이 확장 콘텐츠에 사용되는 별도의 데이터 묶음 의미 (pkg 파일)
패키지(Package)와 에셋(Asset)
- UObject 패키지는 다수의 UObject를 포장하는데 사용하는 UObject
- 모든 UObject는 패키지에 소속되어 있음
- UObject 패키지의 서브 오브젝트를 에셋(Asset)이라 함
- 패키지는 다수의 에셋을 소유할 수 있지만, 일반적으로 하나의 에셋만 가짐
- 에셋은 다수의 서브 오브젝트를 가지며 패키지에 포함됨
- 에셋은 에디터에 노출되나 에셋의 서브 오브젝트는 노출되지 않음
실습
- 패키지를 사용하기 위해서는 패키지와 패지키가 담고 있는 대표 에셋을 설정
- /Game 경로: 게임에서 사용되는 에셋들을 모아 놓은 대표 폴더
- 패키지 경로(이름)과 에셋 이름 설정
- CreatePackage 를 통해 패키지 생성
- 패키지 저장 Flag 설정
- RF_Public 와 RF_Standalone 을 사용하는 게 일반적
- 오브젝트를 생성할 때 패키지를 지정할 수 있음
- 아무것도 지정하지 않으면 Transient Package라는 임시 패키지 안에 UObject가 저장됨
- 패키지, 클래스 이름, 에셋 이름, Flag를 지정해서 생성
UPackage* 패키지 = CreatePackage(*패키지이름); EObjectFlags 플래그 = RF_Public | RF_Standalone; U오브젝트* 오브젝트 = NewObject<U오브젝트>( 패키지, U오브젝트::StaticClass(), *에셋이름, 플래그);
- 서브 오브젝트 지정
- 첫 인자가 Outer
U오브젝트* 서브오브젝트 = NewObject<U오브젝트>(오브젝트, U오브젝트::StaticClass(), *서브오브젝트이름, 플래그);
- 패키지 저장
- #include "UObject\SavePackage.h" 추가
- 패키지 파일 이름 설정
- FPackageName::LongPackageNameToFilename 이용
- FPackageName::GetAssetPackageExtension 이용(UASSET 확장자)
- 플래그 설정
- UPackage::SavePackage
const FString 패지키파일이름 = FPackageName::LongPackageNameToFilename(패키지이름, FPackageName::GetAssetPackageExtension()); FSavePackageArgs 패키지저장플래그; 패키지저장플래그.TopLevelFlags = 위 플래그 써도 됨; if (UPackage::SavePackage(패키지, nullptr, *패키지파일이름, 패키지저장플래그)) { //... }
- 패키지를 저장할 때 이미 패키지가 있다면 다 로딩하고 저장하는 것이 안전
- 처음에 로딩해두고 진행
UPackage* 패키지 = ::LoadPackage(nullptr, *패키지이름, LOAD_None); if (패키지) { 패키지->FullyLoad(); }
- 패키지 불러오기
에셋 정보의 저장과 로딩 전략
- 에셋과의 연결 작업이 빈번함
- 에셋과 연결할 때 마다 직접 패키지를 불러서 동적으로 메모리 할당하는 작업은 부하가 큼
- 에디터는 에셋을 직접 로딩하지 않고 패키지와 오브젝트를 지정한 문자열을 대체해서 사용
- 오브젝트 경로라고 함
- 프로젝트 내에서 오브젝트 경로 값은 유일함을 보장
- 오브젝트 간의 연결은 오브젝트 경로 값으로 기록될 수 있음
- 오브젝트 경로를 사용해 다양한 방법으로 에셋을 로딩할 수 있음
- 로딩 전략
- 프로젝트에서 에셋이 반드시 필요한 경우
- 생성자 코드에서 미리 로딩
- 런타임에서 필요한 때 바로 로딩하는 경우
- 런타임 로직에서 정적 로딩
- 정적 로딩은 다른 프로세스의 실행을 막음 (게임이 멈춤)
- 런타임에서 비동기적으로 로딩하는 경우
- 런타임 로직에서 관리자를 사용해 비동기 로딩
오브젝트 경로
- 패키지 이름과 에셋 이름을 묶은 문자열
- 에셋 클래스 정보는 생략 가능
- 필요한 에셋만 로드할 수 있음
- {에셋클래스정보}'{패키지이름}.{에셋이름}' 또는
- {패키지이름}.{에셋이름}
에셋 참조
https://docs.unrealengine.com/5.3/ko/referencing-assets-in-unreal-engine/
직접 프로퍼티 참조
- 타입을 명시적으로 지정
생성 시간 참조
- 강 참조를 진행할 때 해당 오브젝트가 가리키고 있는 에셋을 생성자 코드에서 생성
- 생성자 코드는 엔진이 초기화될 때 실행
- 즉, 게임이 실행되기 전에 해당 에셋이 로딩
간접 프로퍼티 참조
- TSoftObjectPtr 을 사용
- LoadObject<>() 메서드나 StaticLoadObject() 나 FStreamingManager 를 사용하여 오브젝트를 로드할 수 있음
- FSoftObjectPath 가 오브젝트 경로를 의미
- 이 값을 이용해 스트리밍 매니저를 사용해 비동기적이나 동기적으로 에셋을 로딩
오브젝트 검색/로드
- 생성 또는 로드된 오브젝트: FindObject
- 로드되지 않은 오브젝트: LoadObject
에셋 스트리밍 관리자(Streamable Manager)
- 에셋의 비동기 로딩을 지원하는 관리자 객체
- 콘텐츠 제작과 무관한 싱글톤 클래스에 FStreamableManager를 선언해두면 좋음
- 예) GameInstance
- FStreamableManager를 활용해 에셋의 동기/비동기 로딩 관리 가능
- 다수의 오브젝트 경로를 입력해 다수의 에셋을 로딩하는 것도 가능
실습
- LoadObject<> 와 오브젝트 경로를 통해서 로딩
- 생성자에서 로딩
- ConstructorHelpers::FObjectFinder<> 를 이용
- Succeeded 를 통해 로딩이 됐는지 확인 가능
- .Object 로 접근해서 사용 가능
- 생성자에서 로딩하는 경우 에셋이 반드시 있다는 가정하에 진행 됨
- 없으면 시작할 때 강력한 경고와 에러 메시지가 나옴
- 비동기 로딩
- #include "Engine/StreamableManager.h" 헤더에 인클루드
- FStreamableManager StreamableManager 멤버변수
- 포인터가 아니라 인클루드해줘야함
- TSharedPtr< FStreamableHandle > Handle 멤버변수
- StreamableManager.RequestAsyncLoad 를 이용
- 오브젝트 경로와 람다 함수 이용
Handle = StreamableManager.RequestAsyncLoad(경로, [&]() { if (Handle.IsValid() && Handle->HasLoadCompleted()) { U오브젝트* 오브젝트 = Cast<U오브젝트>(Handle->GetLoadedAsset()); if (오브젝트) { //... Handle->ReleaseHandle(); Handle.Reset(); } } } );