언리얼엔진5/[Part1] 이득우의 언리얼 프로그래밍
[이득우의 언리얼 프로그래밍 Part1 필기] 14. 오브젝트 관리 II 패키지
Rocketbabydolls
2024. 2. 27. 17:59
언리얼 오브젝트 패키지
단일 언리얼 오브젝트가 가진 정보는 저장할 수 있지만, 오브젝트들이 여러 개 조합되어 있다면?
-> 복잡한 계층 구조를 가진 언리얼 오브젝트를 효과적으로 저장하고 불러들이는 방법을 통일해야 함.
- 언리얼 엔진은 이를 위해 패키지(UPackage) 단위로 언리얼 오브젝트를 관리함.
- 패키지의 중의적 개념
언리얼 엔진은 다양한 곳에서 단어 패키지를 사용하고 있음.
언리얼 오브젝트를 감싼 포장 오브젝트를 의미함.
개발된 최종 콘텐츠를 정리해 프로그램으로 만드는 작업.
DLC와 같이 향후 확장 콘텐츠에 사용되는 별도의 데이터 묶음을 의미하기도 함.
패키지(Package) 와 에셋(Asset)
- 언리얼 오브젝트 패키지는 다수의 언리얼 오브젝트를 포장하는데 사용하는 언리얼 오브젝트.
모든 언리얼 오브젝트는 패키지에 소속되어 있음. - 언링러 오브젝트 패키지의 서브 오브젝트를 에셋(Asset)이라고 하며 에디터에는 이들이 노출됨.
- 구조상 패키지는 다수의 언리얼 오브젝트를 소유할 수 있으나, 일반적으로는 하나의 에셋만 가짐.
- 에셋은 다시 다수의 서브오브젝트를 가질 수 있으며, 모두 언리얼 오브젝트 패키지에 포함됨.
일반적으로 하나의 패키지에는 하나의 에셋이 들어 있다.
※ NewObject<>(); 시에 인자가 없으면 transient package 안에 저장된다. (게임 실행 중에만 유효)
에셋 정보의 저장과 로딩 전략
- 게임 제작 단계에서 에셋 간의 연결 작업을 위해 직접 패키지를 불러 할당하는 작업은 부하가 크다.
에셋 로딩 대신 패키지와 오브젝트를 지정한 문자열을 대체해 사용. 이를 오브젝트 경로라고 함.
프로젝트 내에 오브젝트 경로 값은 유일함을 보장함.
그렇기에 오브젝트 간의 연결은 오브젝트 경로 값으로 기록될 수 있음.
오브젝트 경로를 사용해 다양한 바업으로 에셋을 로딩할 수 있음. - 에셋의 로딩 전략
프로젝트에서 에셋이 반드시 필요한 경우 : 생성자 코드에서 미리 로딩
런타임에서 필요할 때 바로 로딩하는 경우 : 런타임 로직에서 정적 로딩
런타임에서 비동기적으로 로딩하는 경우 : 런타임 로직에서 관리자를 사용해 비동기 로딩
동기 로딩과 비동기 로딩에 대한 설명
동기 로딩(Synchronous Loading):
동기 로딩은 리소스가 로드될 때까지 코드 실행이 멈추고 기다립니다. 즉, 로드되는 동안 다른 작업을 수행할 수 없습니다.
주로 작은 크기의 리소스를 로드할 때 사용하며, 로드 시간이 짧고 예측 가능할 때 유용합니다.
주로 작은 게임이나 리소스 양이 적은 상황에서 사용됩니다.
비동기 로딩(Asynchronous Loading):
비동기 로딩은 리소스를 백그라운드에서 로드하고, 코드 실행이 계속됩니다. 따라서 다른 작업을 수행하면서 리소스가 로드될 수 있습니다.
주로 큰 크기의 리소스나 네트워크를 통해 리소스를 로드할 때 사용됩니다.
주로 대규모 게임이나 로드 시간이 긴 리소스를 다룰 때 유용합니다.
오브젝트 경로(Obejct Path)
- 패키지 이름과 에셋 이름을 한 데 묶은 문자열
- 에셋 클래스 정보는 생략할 수 있음.
- 패키지 내 데이터를 모두 로드하지 않고 오브젝트 경로를 사용해 필요한 에셋만 로드할 수 있음.
에셋 스트리밍 관리자(Streamable Manager)
- 에셋의 비동기 로딩을 지원하는 관리자 객체
- 콘텐츠 제작과 무관한 싱글턴 클래스에 FStreamableManager를 선언해두면 좋음.
GameInstance 는 좋은 선택지 - FStreamableManager를 활용해 에셋의 동기/비동기 로딩을 관리할 수 있음.
- 다수의 오브젝트 경로를 입력해 다수의 에셋을 로딩하는 것도 가능함.
소스코드
/Game -> 에셋을 모아놓은 대표 폴더
/Temp -> saved 폴더 매핑되어 있음.
void UMyGameInstance::SaveStudentPackage() const
{
UPackage* StudentPackage = ::LoadPackage(nullptr, *PackageName, LOAD_None);
if (StudentPackage)
{
StudentPackage->FullyLoad();
}
StudentPackage = CreatePackage(*PackageName);
EObjectFlags ObjectFlag = RF_Public | RF_Standalone;
UStudent* TopStudent = NewObject<UStudent>(StudentPackage, UStudent::StaticClass(), *AssetName, ObjectFlag);
TopStudent->SetName(TEXT("이득우"));
TopStudent->SetOrder(36);
const int32 NumofSubs = 10;
for (int32 ix = 1; ix <= NumofSubs; ++ix)
{
FString SubObjectName = FString::Printf(TEXT("Student%d"), ix);
UStudent* SubStudent = NewObject<UStudent>(TopStudent, UStudent::StaticClass(), *SubObjectName, ObjectFlag);
SubStudent->SetName(FString::Printf(TEXT("학생%d"), ix));
SubStudent->SetOrder(ix);
}
const FString PackageFileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension());
FSavePackageArgs SaveArgs;
SaveArgs.TopLevelFlags = ObjectFlag;
if (UPackage::SavePackage(StudentPackage, nullptr, *PackageFileName, SaveArgs))
{
UE_LOG(LogTemp, Log, TEXT("패키지가 성공적으로 저장되었습니다."));
}
}
void UMyGameInstance::LoadStudentPackage() const
{
UPackage* StudentPackage = ::LoadPackage(nullptr, *PackageName, LOAD_None);
if (nullptr == StudentPackage)
{
UE_LOG(LogTemp, Warning, TEXT("패키지를 찾을 수 없습니다."));
return;
}
StudentPackage->FullyLoad();
UStudent* TopStudent = FindObject<UStudent>(StudentPackage, *AssetName);
PrintStudentInfo(TopStudent, TEXT("FindObject Asset"));
}
UMyGameInstance::UMyGameInstance()
{
const FString TopSoftObjectPath = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
static ConstructorHelpers::FObjectFinder<UStudent> UASSET_TopStudent(*TopSoftObjectPath);
if (UASSET_TopStudent.Succeeded())
{
PrintStudentInfo(UASSET_TopStudent.Object, TEXT("Constructor"));
}
}
//
init() 함수
//비동기 로딩
const FString TopSoftObjectPath = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
Handle = StreamableManager.RequestAsyncLoad(TopSoftObjectPath,
[&]()
{
if (Handle.IsValid() && Handle->HasLoadCompleted())
{
UStudent* TopStudent = Cast<UStudent>(Handle->GetLoadedAsset());
if (TopStudent)
{
PrintStudentInfo(TopStudent, TEXT("AsyncLoad"));
Handle->ReleaseHandle();
Handle.Reset();
}
}
}
);
void UMyGameInstance::LoadStudentObject() const
{
//동적 로딩
const FString TopSoftObjectPath = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
UStudent* TopStudent = LoadObject<UStudent>(nullptr, *TopSoftObjectPath);
PrintStudentInfo(TopStudent, TEXT("LoadObject Asset"));
}