언리얼 엔진5 Basic - 언리얼 오브젝트 관리 2 (패키지)

타입·2024년 3월 26일
0

언리얼 강의

목록 보기
6/47

언리얼 오브젝트 패키지

언리얼 엔진은 패키지(UPackage)단위로 언리얼 오브젝트를 관리

  • 패키지와 애셋
    언리얼 오브젝트 패키지는 다수의 언리얼 오브젝트를 포장하는데 사용하는 언리얼 오브젝트
    모든 언리얼 오브젝트는 패키지에 소속되어 있음
    언리얼 오브젝트 패키지는 서브 오브젝트를 애셋(Asset)이라고 하며 에디터에는 이들이 노출됨
    구조상 패키지는 다수의 언리얼 오브젝트를 소유할 수 있으나, 일반적으로는 하나의 애셋만 가짐
    애셋은 다시 다수의 서브 오브젝트를 가질 수 있으며, 모두 언리얼 오브젝트 패키지에 포함됨

패키지 저장

// MyGameInstance.h
UCLASS()
class UNREALSERIALIZATION_API UMyGameInstance : public UGameInstance
{
	/* 직전 포스트의 코드에서 계속 */
    ...
    void SaveStudentPackage() const;
    
    static const FString PackageName;
    static const FString AssetName;
};


// MyGameInstance.cpp
#include "UObject/SavePackage.h"
const FString UMyGameInstance::PackageName = TEXT("/Game/Student");
const FString UMyGameInstance::AssetName = TEXT("TopStudent");

void SaveStudentPackage() const
{
	UPackage* StudentPackage = CreatePackage(*PackageName);
    EObjectFlags ObjectFlag = RF_Public|RF_Standalone; // 패키지 저장 옵션
    
    // StudentPackage에 저장되도록 설정
    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)
    {
    	// TopStudent에 들어가도록 설정
    	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("패키지가 성공적으로 저장되었습니다."));
        // Content 폴더에 TopStudent 패키지가 저장됨
    }
}

패키지 불러오기

// MyGameInstance.h
UCLASS()
class UNREALSERIALIZATION_API UMyGameInstance : public UGameInstance
{
	...
    void LoadStudentPackage() const;
};


// MyGameInstance.cpp
void UMyGameInstance::LoadStudentPackage() const
{
	// 패키지 정보는 없음, 패키지 이름과 기본 옵션으로 로딩
	UPackage* StudentPackage = ::LoadPackage(nullptr, *PackageName, LOAD_NONE);
    if (StudentPackage == nullptr)
    {
    	UE_LOG(LogTemp, Warning, TEXT("패키지를 찾을 수 없습니다."));
        return;
    }
    
    StudentPackage->FullyLoad(); // 찾은 패키지의 모든 애셋을 로딩
    
    // StudentPackage에서 TopStudent 이름의 애셋 찾기
    UStudent* TopStudent = FindObject<UStudent>(StudentPackage, *AssetName);
    PrintStudentInfo(TopStudent, TEXT("FindObject Asset")); // 이득우 36
}
void UMyGameInstance::SaveStudentPackage() const
{
	// 패키지 저장 전 이미 패키지가 있다면 먼저 로드하는게 안전
	UPackage* StudentPackage = ::LoadPackage(nullptr, *PackageName, LOAD_NONE);
    if (StudentPackage)
    {
    	// 만약 있다면 모두 로딩
    	StudentPackage->FullyLoad();
    }
    
    /* 이후 패키지 저장에서 작성한 코드 */
    ...
}

애셋 참조와 로딩

  • 애셋 정보의 저장과 로딩 전략
    • 게임 제작 단계에서 애셋 간의 연결 작업을 위해 직접 패키지를 불러 할당하는 작업은 부하가 큼
      애셋 로딩 대신 패키지와 오브젝트를 지정한 문자열을 대체해 사용, 이를 오브젝트 경로라고 함
      프로젝트 내에 오브젝트 경로 값은 유일함을 보장
      오브젝트 간의 연결은 오브젝트 경로 값으로 기록될 수 있음
      오브젝트 경로를 사용해 다양한 방법으로 애셋을 로딩할 수 있음
    • 애셋의 로딩 전략
      • 프로젝트에서 애셋이 반드시 필요한 경우
        생성자 코드에서 미리 로딩
      • 런타임에서 필요한 때 바로 로딩하는 경우
        런타임 로직에서 정적 로딩 (로딩중 게임이 멈추는 현상 발생)
      • 런타임에서 비동기적으로 로딩하는 경우
        런타임 로직에서 관리자를 사용하여 비동기 로딩
  • 오브젝트 경로 (Object Path)
    패키지 이름과 애셋 이름을 한 데 묶은 문자열
    패키지 내 데이터를 모두 로드하지 않고 오브젝트 경로를 사용해 필요한 애셋만 로드할 수 있음

    {애셋클래스정보}'{패키지이름}.{애셋이름}'

    애셋 클래스 정보는 생략 가능
    {패키지이름}.{애셋이름}

  • 애셋 참조 문서
    • 간접 프로퍼티 참조
      TSoftObjectPtr를 사용하여 LoadObject<>()나 FStreamingManager로 필요한 때 오브젝트 로딩 가능
  • 애셋 스트리밍 관리자 (Streamable Manager)
    애셋의 비동기 로딩을 지원하는 관리자 객체 (FStreamableManager)
    • 콘텐츠 제작과 무관한 싱글턴 클래스에 선언해두면 좋음
      ex) GameInstance
    • FStreamableManager를 활용해 애셋의 동기/비동기 로딩을 관리할 수 있음
    • 다수의 오브젝트 경로를 입력해 다수의 애셋을 로딩하는 것도 가능
  • 패키지 대신 오브젝트 경로로 바로 애셋 로딩
// MyGameInstance.h
UCLASS()
class UNREALSERIALIZATION_API UMyGameInstance : public UGameInstance
{
	...
    void LoadStudentObject() const;
};


// MyGameInstance.cpp
void UMyGameInstance::LoadStudentObject() cons
{
	const FString TopSoftObjectPath = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
    
    // 패키지를 로딩하지 않기에 nullptr을 넣어줌
    UStudent* TopStudent = LoadObject<UStudent>(nullptr, *TopSoftObjectPath);
    PrintStudentInfo(TopStudent, TEXT("LoadObject Asset")); // 이득우 36
}
  • 생성자에서 애셋 로딩
    게임 시작 전 메모리에 로딩되어야 한다는 걸 의미
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")); // 이득우 36
	}
}
  • 비동기 로딩
// MyGameInstance.h
#include "Engine/StreamableManager.h"
UCLASS()
class UNREALSERIALIZATION_API UMyGameInstance : public UGameInstance
{
	...
    FStreamableManager StreamableManager;
    TSharedPtr<FStreamableHandle> Handle; // 스트리밍된 애셋을 관리할 핸들
};


// MyGameInstance.cpp
UMyGameInstance::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")); // 이득우 36
                    
                    // 다 쓴 핸들 닫아줌
                    Handle->ReleaseHandle();
                    Handle.Reset();
                }
            }
        }
    );
}
profile
주니어 언리얼 프로그래머

0개의 댓글