본문 바로가기
Unreal/[서적] 언리얼5 이득우님 인프런1

Ch 14. 패키지

by GameStudy 2023. 5. 13.

14.1 언리얼 오브젝트 패키지

14.1-1 언리얼 오브젝트 패키지

  - 단일 언리얼 오브젝트가 가진 정보는 저장할 수 있지만, 오브젝트들이 조합되어 있다면?

    저장된 언리얼 오브젝트 데이터를 효과적으로 찾고 관리하는 방법은?

    복잡한 계층 구조를 가진 언리얼 오브젝트를 효과적으로 저장 및 불러들이는 방법을 통일해야 함.

  - 언리얼 엔진은 이를 위해 패키지 단위로 언리얼 오브젝트를 관리함.

  - 패키지의 중의적 개념

    언리얼 엔진은 다양한 곳에서 "패키지"라는 단어를 사용하고 있음.

    언리얼 오브젝트를 감싼 포장 오브젝트를 의미하기도 하고(이번 단원의 주제)

    개발된 최종 컨텐츠를 정리해 프로그램으로 만드는 작업을 의미하기도 하고(게임 패키징)

    DLC와 같이 향후 확장 컨텐츠에 사용되는 별도의 데이터 묶음을 의미하기도 함(pkg 파일)

    그래서 구분을 위해 언리얼 오브젝트 패키지로 부르는 것도 좋음.

 

14.1-2 패키지와 애셋

  - 언리얼 오브젝트 패키지는 다수의 언리얼 오브젝트를 포장하는데 사용하는 언리얼 오브젝트.

    모든 언리얼 오브젝트는 패키지에 소속되어 있음. ex) Transient Package

  - 언리얼 오브젝트 패키지의 서브 오브젝트를 애셋이라고 하며 에디터에는 이들이 노출됨.

    구조상 패키지는 다수의 언리얼 오브젝트를 소유할 수 있으나, 일반적으로는 하나의 애셋만 가짐.

  - 애셋은 다시 다수의 서브 오브젝트를 가질 수 있으며, 모두 언리얼 오브젝트 패키지에 포함됨.

    하지만 에디터에는 노출되지 않음.

 

14.1-3 패키지를 활용한 에디터 상의 애셋 저장 실습

<hide/>

// MyGameInstance.h

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "Engine/StreamableManager.h"
#include "MyGameInstance.generated.h"

...

UCLASS()
class UNREALSERIALIZATION_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()
	
public:
	UMyGameInstance();

	virtual void Init() override;

	void SaveStudentPackage() const;
	void LoadStudentPackage() const;
	void LoadStudentObject() const;

private:

	static const FString PackageName;
	static const FString AssetName;

	UPROPERTY()
	TObjectPtr<class UStudent> StudentSrc;

	FStreamableManager StreamableManager;
	TSharedPtr<FStreamableHandle> Handle;
};
<hide/>

// MyGameInstance.cpp

#include "MyGameInstance.h"
#include "Student.h"
#include "JsonObjectConverter.h"
#include "UObject/SavePackage.h"

const FString UMyGameInstance::PackageName = TEXT("/Game/Student");
const FString UMyGameInstance::AssetName = TEXT("TopStudent");

...

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"));
	}
}

void UMyGameInstance::Init()
{
	...

	SaveStudentPackage();
	LoadStudentPackage();

}

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"));
}

 

14.2 애셋 참조와 로딩

14.2-1 애셋 정보의 저장과 로딩 전략

  - 게임 제작 단계에서 애셋 간의 연결 작업을 위해 직접 패키지를 불러 할당하는 작업은 부하가 큼.

    애셋 로딩 대신 패키지와 오브젝트를 지정한 문자열을 대체해 사용. 이를 오브젝트 경로라고 함.

    프로젝트 내에 오브젝트 경로 값은 유일함을 보장함.

    그렇기에 오브젝트 간의 연결은 오브젝트 경로 값으로 기록될 수 있음.

    오브젝트 경로를 사용해 다양한 방법으로 애셋을 로딩할 수 있음.

  - 애셋의 로딩 전략

    프로젝트에서 애셋이 반드시 필요한 경우: 생성자에서 미리 로딩

    런타임에서 필요할 때에 바로 로딩하는 경우: 런타임 로직에서 정적 로딩(다른 프로세스를 막음. 즉, 게임이 멈춤)

    런타임에서 비동기적으로 로딩하는 경우: 런타임 로직에서 관리자를 사용해 비동기 로딩

 

14.2-2 오브젝트 경로(Object Path)

  - 패키지 이름과 애셋 이름을 한 데 묶은 문자열

    애셋 클래스 정보는 생략할 수 있음.

    패키지 내 데이터를 모두 로드하지 않고 오브젝트 경로를 사용해 필요한 애셋만 로드할 수 있음.

 

14.2-3 애셋 스트리밍 관리자(Streamable Manager)

  - 애셋의 비동기 로딩을 지원하는 관리자 개체

    컨텐츠 제작과 무관한 싱글턴 클래스에 FStreamableManager를 선언해두면 좋음.

    GameInstance에 만들면 좋음.

  - FStreamableManager를 활용해 애셋의 동기/비동기 로딩을 관리할 수 있음.

    다수의 오브젝트 경로를 입력해 다수의 애셋을 로딩하는 것도 가능함.

 

14.2-4 애셋 참조와 로딩 예제

<hide/>

// MyGameInstance.h

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "Engine/StreamableManager.h"
#include "MyGameInstance.generated.h"

...
class UNREALSERIALIZATION_API UMyGameInstance : public UGameInstance
{
	...
	
public:
	...
	void LoadStudentPackage() const;
	void LoadStudentObject() const;

private:
    ...

	FStreamableManager StreamableManager;
	TSharedPtr<FStreamableHandle> Handle;
};
<hide/>

// MyGameInstance.cpp

#include "MyGameInstance.h"
#include "Student.h"
#include "JsonObjectConverter.h"
#include "UObject/SavePackage.h"

...

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"));
	}
}

void UMyGameInstance::Init()
{
	...

	SaveStudentPackage();
	//LoadStudentPackage();
	//LoadStudentObject();

	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"));
}

 

 

 

'Unreal > [서적] 언리얼5 이득우님 인프런1' 카테고리의 다른 글

Ch 15. 언리얼 빌드 시스템  (0) 2023.05.13
Ch 13. 직렬화  (0) 2023.05.13
Ch 12. 언리얼 엔진의 메모리 관리  (0) 2023.05.13
Ch 11. 구조체와 Map  (0) 2023.05.13
Ch 10. Array and Set  (0) 2023.05.12

댓글