[UE] 언리얼 오브젝트 관리 II - 패키지 (Package)

강의 목표
- 언리얼 엔진의 애셋과 이를 포장한 패키지의 개념 이해
- 언리얼 에디터에서 볼 수 있도록 애셋을 저장하고 불러들이는 방법의 이해
- 오브젝트 패스를 사용해 다양한 방식으로 애셋을 로딩 하는 방법의 이해
인프런 이득우님의 '언리얼 프로그래밍 Part1 - 언리얼 C++의 이해' 강의를 참고하였습니다.
😎 [이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해] 강의 들으러 가기!
목차
패키지 (Package)
언리얼 엔진의 패키지와 애셋 개념을 이해하고 언리얼 에디터에 애셋 데이터를 저장하고 불러들이기
언리얼 오브젝트 패키지
단일 언리얼 오브젝트가 가진 정보는 저장할 수 있지만, 오브젝트들이 조합되어 있다면?
- 저장된 언리얼 오브젝트 데이터를 효과적으로 찾고 관리하는 방법은?
- 복잡한 계층 구조를 가진 언리얼 오브젝트를 효과적으로 저장과 불러들이는 방법을 통일해야 함
언리얼 엔진은 이를 위해 패키지(UPackage) 단위로 언리얼 오브젝트를 관리함
패키지(Package)의 중의적 개념
- 언리얼 엔진은 다양한 곳에서 단어 패키지를 사용하고 있음
- 언리얼 오브젝트를 감싼 포장 오브젝트를 의미함. (이번 강의의 주제)
- 또한 개발된 최종 컨텐츠를 정리해 프로그램으로 만드는 작업을 의미함. ex. 게임 패키징
- DLC와 같이 향후 확장 콘텐츠에 사용되는 별도의 데이터 묶음을 의미하기도 함 ex. pkg 파일
구분을 위해 언리얼 오브젝트 패키지로 부르는 것도 고려
패키지(Package)와 애셋(Asset)
- 언리얼 오브젝트 패키지는 다수의 언리얼 오브젝트를 포장하는데 사용하는 언리얼 오브젝트
- 모든 언리얼 오브젝트는 패키지에 소속되어 있음. ex. Transient Package
- 언리얼 오브젝트 패키지의 서브 오브젝트를 애셋(Asset)이라고 하며 에디터에는 이들이 노출됨.
- 구조 상 패키지는 다수의 언리얼 오브젝트를 소유할 수 있으나, 일반적으로는 하나의 애셋만 가짐.
- 애셋은 다시 다수의 서브 오브젝트를 가질 수 있으며, 모두 언리얼 오브젝트 패키지에 포함됨.
- 하지만 에디터에는 노출되지 않음(= 서브 오브젝트는 에디터에 노출되지 않음).

패키지 생성 코드
const FString UMyGameInstance::PackageName = TEXT("/Game/Student"); ... UPackage* StudentPackage = CreatePackage(*PackageName); EObjectFlags ObjectFlag = RF_Public | RF_Standalone; UStudent* TopStudent = NewObject<UStudent>(StudentPackage, UStudent::StaticClass(), *AssetName, ObjectFlag); TopStudent->SetName(TEXT("홍길동")); TopStudent->SetOrder(36);
- CreatePackage() : 패키지를 찾고 없는 경우 생성
- EObjectFlags : 저장 플래그
- NewObject<T>() : 파라미터가 없는 경우 Transient Package라고 하는 임시 패키지에 저장
전체코드
GameInstance.h
#pragma once #include "CoreMinimal.h" #include "Engine/GameInstance.h" #include "Engine/StreamableManager.h" #include "MyGameInstance.generated.h" struct FStudentData { FStudentData() {} FStudentData(int32 InOrder, const FString& InName) : Order(InOrder), Name(InName) {} friend FArchive& operator<<(FArchive& Ar, FStudentData& InStudentData) { Ar << InStudentData.Order; Ar << InStudentData.Name; return Ar; } int32 Order = -1; FString Name = TEXT("홍길동"); }; UCLASS() class UNREALSERIALIZATION_API UMyGameInstance : public UGameInstance { GENERATED_BODY() public: UMyGameInstance(); virtual void Init() override; void SaveStudentPackage() const; // 패키지 저장 void LoadStudentPackage() const; // 패키지 로드 private: static const FString PackageName; static const FString AssetName; UPROPERTY() TObjectPtr<class UStudent> StudentSrc; };
패키지 저장
- void SaveStudentPackage() const
패키지 로드
- void LoadStudentPackage() const
GameInstance.cpp
#include "MyGameInstance.h" #include "Student.h" #include "JsonObjectConverter.h" #include "UObject/SavePackage.h" // "/Game"은 Game에서 사용되는 에셋들을 모아놓은 대표 폴더를 의미한다. const FString UMyGameInstance::PackageName = TEXT("/Game/Student"); const FString UMyGameInstance::AssetName = TEXT("TopStudent"); void PrintStudentInfo(const UStudent* InStudent, const FString& InTag) { UE_LOG(LogTemp, Log, TEXT("[%s] 이름 %s 순번 %d"), *InTag, *InStudent->GetName(), InStudent->GetOrder()); } UMyGameInstance::UMyGameInstance() { } void UMyGameInstance::Init() { Super::Init(); FStudentData RawDataSrc(16, TEXT("이득우")); const FString SavedDir = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Saved")); UE_LOG(LogTemp, Log, TEXT("저장할 파일 폴더 : %s"), *SavedDir); { const FString RawDataFileName(TEXT("RawData.bin")); FString RawDataAbosolutePath = FPaths::Combine(*SavedDir, *RawDataFileName); UE_LOG(LogTemp, Log, TEXT("저장할 파일 전체 경로 : %s"), *RawDataAbosolutePath); FPaths::MakeStandardFilename(RawDataAbosolutePath); UE_LOG(LogTemp, Log, TEXT("변경할 파일 전체 경로 : %s"), *RawDataAbosolutePath); FArchive* RawFileWriterAr = IFileManager::Get().CreateFileWriter(*RawDataAbosolutePath); if (nullptr != RawFileWriterAr) { *RawFileWriterAr << RawDataSrc; RawFileWriterAr->Close(); delete RawFileWriterAr; RawFileWriterAr = nullptr; } FStudentData RawDataDest; FArchive* RawFileReaderAr = IFileManager::Get().CreateFileReader(*RawDataAbosolutePath); if (nullptr != RawFileReaderAr) { *RawFileReaderAr << RawDataDest; RawFileReaderAr->Close(); delete RawFileReaderAr; RawFileReaderAr = nullptr; UE_LOG(LogTemp, Log, TEXT("[RawData] 이름 %s 순번 %d"), *RawDataDest.Name, RawDataDest.Order); } } StudentSrc = NewObject<UStudent>(); StudentSrc->SetName(TEXT("이득우")); StudentSrc->SetOrder(59); { const FString ObjectDataFileName(TEXT("ObjectData.bin")); FString ObjectDataAbsolutePath = FPaths::Combine(*SavedDir, *ObjectDataFileName); FPaths::MakeStandardFilename(ObjectDataAbsolutePath); TArray<uint8> BufferArray; FMemoryWriter MemoryWriterAr(BufferArray); StudentSrc->Serialize(MemoryWriterAr); if (TUniquePtr<FArchive> FileWriterAr = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(*ObjectDataAbsolutePath))) { *FileWriterAr << BufferArray; FileWriterAr->Close(); } TArray<uint8> BufferArrayFromFile; if (TUniquePtr<FArchive> FileReaderAr = TUniquePtr<FArchive>(IFileManager::Get().CreateFileReader(*ObjectDataAbsolutePath))) { *FileReaderAr << BufferArrayFromFile; FileReaderAr->Close(); } FMemoryReader MemoryReaderAr(BufferArrayFromFile); UStudent* StudentDest = NewObject<UStudent>(); StudentDest->Serialize(MemoryReaderAr); PrintStudentInfo(StudentDest, TEXT("ObjectData")); } { const FString JsonDataFileName(TEXT("StudentJsonData.txt")); FString JsonDataAbsolutePath = FPaths::Combine(*SavedDir, *JsonDataFileName); FPaths::MakeStandardFilename(JsonDataAbsolutePath); TSharedRef<FJsonObject> JsonObjectSrc = MakeShared<FJsonObject>(); FJsonObjectConverter::UStructToJsonObject(StudentSrc->GetClass(), StudentSrc, JsonObjectSrc); FString JsonOutString; TSharedRef<TJsonWriter<TCHAR>> JsonWriterAr = TJsonWriterFactory<TCHAR>::Create(&JsonOutString); if (FJsonSerializer::Serialize(JsonObjectSrc, JsonWriterAr)) { FFileHelper::SaveStringToFile(JsonOutString, *JsonDataAbsolutePath); } FString JsonInString; FFileHelper::LoadFileToString(JsonInString, *JsonDataAbsolutePath); TSharedRef<TJsonReader<TCHAR>> JsonReaderAr = TJsonReaderFactory<TCHAR>::Create(JsonInString); TSharedPtr<FJsonObject> JsonObjectDest; if (FJsonSerializer::Deserialize(JsonReaderAr, JsonObjectDest)) { UStudent* JsonStudentDest = NewObject<UStudent>(); if (FJsonObjectConverter::JsonObjectToUStruct(JsonObjectDest.ToSharedRef(), JsonStudentDest->GetClass(), JsonStudentDest)) { PrintStudentInfo(JsonStudentDest, TEXT("JsonData")); } } } SaveStudentPackage(); LoadStudentPackage(); 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::SaveStudentPackage() const { // 패키지 로드 UPackage* StudentPackage = ::LoadPackage(nullptr, *PackageName, LOAD_None); if (StudentPackage) // 패키지 있다면 로드 { StudentPackage->FullyLoad(); // 안에 가지고 있는 에셋을 모두 다 로딩 } 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); // 서브오브젝트 10개 생성 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")); }
패키지 저장
- void UMyGameInstance::SaveStudentPackage() const
패키지 로드
- void UMyGameInstance::LoadStudentPackage() const
실행화면


에셋 참조와 로딩
에셋 정보의 저장과 로딩 전략
게임 제작 단계에서 애셋 간의 연결 작업을 위해 직접 패키지를 불러 할당하는 작업은 부하가 큼.
- 애셋 로딩 대신 패키지와 오브젝트를 지정한 문자열을 대체해 사용. 이를 오브젝트 경로라고 함.
- 프로젝트 내에 오브젝트 경로 값은 유일함을 보장함.
- 그렇기에 오브젝트 간의 연결은 오브젝트 경로 값으로 기록될 수 있음.
- 오브젝트 경로를 사용해 다양한 방법으로 애셋을 로딩할 수 있음.
애셋의 로딩 전략
- 프로젝트에서 애셋이 반드시 필요한 경우: 생성자 코드에서 미리 로딩
- 런타임에서 필요한 때 바로 로딩하는 경우: 런타임 로직에서 정적 로딩
- 런타임에서 비동기적으로 로딩하는 경우: 런타임 로직에서 관리자를 사용해 비동기 로딩

에디터에서 애셋을 지정할 때마다 항상 로딩해야 하는가?
오브젝트 경로 (Object Path)
- 패키지 이름과 애셋 이름을 한 데 묶은 문자열
- 애셋 클래스 정보는 생략할 수 있음
- 패키지 내 데이터를 모두 로드하지 않고 오브젝트 경로(Object Path)를 사용해 필요한 애셋만 로드할 수 있음.
{ 애셋 클래스 정보 }'{패키지 이름}.{애셋 이름}'
또는
{패키지 이름}.{애셋 이름}
언리얼 공식문서
강참조: 직접 프로퍼티 참조 (UPROPERTY 매크로), 생성 시간 참조(ConstructorHelpers)
1. 직접 프로퍼티 참조: UPROPERTY 매크로를 통해 노출
- 게임플레이 클래스에는 UPROPERTY 를 노출시키면 디자이너가 특정 에셋에 블루프린트 상속을 통해서 또는 월드에 배치한 인스턴스를 통해서 아키타입을 지정한다.
/** construction start sound stinger */ UPROPERTY(EditDefaultsOnly, Category=Building) USoundCue* ConstructionStartStinger;
2. 생성 시간 참조
- 주어진 프로퍼티에 대해 로드해야 하는 에셋을 프로그래머가 정확히 알고 있어, 그 프로퍼티를 오브젝트의 생성시 일부분으로 설정하는 경우
- ConstructorHelpers 라는 특수 클래스가 사용되는데, 생성 단계 도중 오브젝트와 오브젝트의 클래스를 찾는 것이다.
/** gray health bar texture */ UPROPERTY() class UTexture2D* BarFillTexture; AStrategyHUD::AStrategyHUD(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { static ConstructorHelpers::FObjectFinder<UTexture2D> BarFillObj(TEXT("/Game/UI/HUD/BarFill")); ... BarFillTexture = BarFillObj.Object; ... }
약참조. 간접 프로퍼티 참조(TSoftObjectPtr)
- 에셋 로드 시점을 쉽게 제어할 수 있는 방법은 TSoftObjectPtr 을 사용하는 것이다.
- 디자이너의 경우, 직접 프로퍼티 레퍼런스인 것처럼 작업할 수 있다. 하지만 직접 포인터 레퍼런스 대신, 에셋의 로드 여부에 대한 안전 검사가 가능한 템플릿 코드와 함께 프로퍼티가 스트링으로 저장된다.
- TSoftObjectPtr 을 사용하려면 에셋을 수동으로 로드해야 한다.
- 템플릿으로 된 LoadObject<>() 메서드나 StaticLoadObject() 나 FStreamingManager 를 사용하여 오브젝트를 로드할 수 있다 (상세 정보는 비동기 에셋 로딩 문서를 참고). 처음 두 메서드는 에셋을 동기식으로 로드하여 프레임 속도가 출렁일 수 있으니, 게임플레이에 영향을 끼치지 않을 것이 확실한 것에만 사용해야 한다.
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category=Building) TSoftObjectPtr<UStaticMesh> BaseMesh; UStaticMesh* GetLazyLoadedMesh() { if (BaseMesh.IsPending()) { const FSoftObjectPath& AssetRef = BaseMesh.ToStringReference(); BaseMesh = Cast< UStaticMesh>(Streamable.SynchronousLoad(AssetRef)); } return BaseMesh.Get(); }
위 코드는 UStaticMesh 의 TSoftObjectPtr 을 사용하여 실행시간에 메시를 지연시켜 로드하고 있다. 에셋은 오브젝트의 로드 여부를 검사합니다. 로드되지 않은 경우, FStreamingManager 를 사용한 동기성 로드가 일어난다. 아니면 TSoftObjectPtr 안의 UStaticMesh 포인터가 호출자에게 반환된다.
UClass 를 유예식으로 로드하려는 경우, TAssetSubclassOf 템플릿 유형 클래스 전용 버전을 대체하여 TSoftClassPtr 과 같은 접근법을 사용할 수 있다. 이 함수는 지정된 에셋을 참조하는 것과 같은 역할을 하지만, 인스턴스 대신 에셋에 대한 UClass 를 참조한다.
오브젝트 검색/로드
실행시간에 스트링을 만들어 그 오브젝트로의 레퍼런스를 구하는 방식
1. 생성 또는 로드된 UObject 만 사용하려는 경우: FindObject<>()
AFunctionalTest* TestToRun = FindObject<AFunctionalTest>(TestsOuter, *TestName);
2. 이미 로드되어 있지 않은 오브젝트를 로드: LoadObject<>()
GridTexture = LoadObject<UTexture2D>(NULL, TEXT("/Engine/EngineMaterials/DefaultWhiteGrid.DefaultWhiteGrid"), NULL, LOAD_None, NULL);
3. UClass 로드: LoadObject<>()
DefaultPreviewPawnClass = LoadClass<APawn>(NULL, *PreviewPawnName, NULL, LOAD_None, NULL); if (!DefaultPreviewPawnClass->IsChildOf(APawn::StaticClass())) { DefaultPreviewPawnClass = nullptr; }
애셋 스트리밍 관리자 (Streamable Manager)
- 애셋 비동기 로딩을 지원하는 관리자 객체
- 콘텐츠 제작과 무관한 싱글턴 클래스에 FStreamableManager를 선언해두면 좋음.
- GameInstance는 좋은 선택지
- FStreamableManager를 활용해 애셋의 동기/비동기 로딩을 관리할 수 있음.
- 다수의 오브젝트 경로를 입력해 다수의 애셋을 로딩하는 것도 가능함.
전체코드
GameInstance.h
#pragma once #include "CoreMinimal.h" #include "Engine/GameInstance.h" #include "Engine/StreamableManager.h" #include "MyGameInstance.generated.h" struct FStudentData { FStudentData() {} FStudentData(int32 InOrder, const FString& InName) : Order(InOrder), Name(InName) {} friend FArchive& operator<<(FArchive& Ar, FStudentData& InStudentData) { Ar << InStudentData.Order; Ar << InStudentData.Name; return Ar; } int32 Order = -1; FString Name = TEXT("홍길동"); }; 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; };
학생 오브젝트 로드
- void LoadStudentObject() const;
GameInstance.cpp
#include "MyGameInstance.h" #include "Student.h" #include "JsonObjectConverter.h" #include "UObject/SavePackage.h" // "/Game"은 Game에서 사용되는 에셋들을 모아놓은 대표 폴더를 의미한다. const FString UMyGameInstance::PackageName = TEXT("/Game/Student"); const FString UMyGameInstance::AssetName = TEXT("TopStudent"); void PrintStudentInfo(const UStudent* InStudent, const FString& InTag) { UE_LOG(LogTemp, Log, TEXT("[%s] 이름 %s 순번 %d"), *InTag, *InStudent->GetName(), InStudent->GetOrder()); } 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() { Super::Init(); FStudentData RawDataSrc(16, TEXT("이득우")); const FString SavedDir = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Saved")); UE_LOG(LogTemp, Log, TEXT("저장할 파일 폴더 : %s"), *SavedDir); { const FString RawDataFileName(TEXT("RawData.bin")); FString RawDataAbosolutePath = FPaths::Combine(*SavedDir, *RawDataFileName); UE_LOG(LogTemp, Log, TEXT("저장할 파일 전체 경로 : %s"), *RawDataAbosolutePath); FPaths::MakeStandardFilename(RawDataAbosolutePath); UE_LOG(LogTemp, Log, TEXT("변경할 파일 전체 경로 : %s"), *RawDataAbosolutePath); FArchive* RawFileWriterAr = IFileManager::Get().CreateFileWriter(*RawDataAbosolutePath); if (nullptr != RawFileWriterAr) { *RawFileWriterAr << RawDataSrc; RawFileWriterAr->Close(); delete RawFileWriterAr; RawFileWriterAr = nullptr; } FStudentData RawDataDest; FArchive* RawFileReaderAr = IFileManager::Get().CreateFileReader(*RawDataAbosolutePath); if (nullptr != RawFileReaderAr) { *RawFileReaderAr << RawDataDest; RawFileReaderAr->Close(); delete RawFileReaderAr; RawFileReaderAr = nullptr; UE_LOG(LogTemp, Log, TEXT("[RawData] 이름 %s 순번 %d"), *RawDataDest.Name, RawDataDest.Order); } } StudentSrc = NewObject<UStudent>(); StudentSrc->SetName(TEXT("이득우")); StudentSrc->SetOrder(59); { const FString ObjectDataFileName(TEXT("ObjectData.bin")); FString ObjectDataAbsolutePath = FPaths::Combine(*SavedDir, *ObjectDataFileName); FPaths::MakeStandardFilename(ObjectDataAbsolutePath); TArray<uint8> BufferArray; FMemoryWriter MemoryWriterAr(BufferArray); StudentSrc->Serialize(MemoryWriterAr); if (TUniquePtr<FArchive> FileWriterAr = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(*ObjectDataAbsolutePath))) { *FileWriterAr << BufferArray; FileWriterAr->Close(); } TArray<uint8> BufferArrayFromFile; if (TUniquePtr<FArchive> FileReaderAr = TUniquePtr<FArchive>(IFileManager::Get().CreateFileReader(*ObjectDataAbsolutePath))) { *FileReaderAr << BufferArrayFromFile; FileReaderAr->Close(); } FMemoryReader MemoryReaderAr(BufferArrayFromFile); UStudent* StudentDest = NewObject<UStudent>(); StudentDest->Serialize(MemoryReaderAr); PrintStudentInfo(StudentDest, TEXT("ObjectData")); } { const FString JsonDataFileName(TEXT("StudentJsonData.txt")); FString JsonDataAbsolutePath = FPaths::Combine(*SavedDir, *JsonDataFileName); FPaths::MakeStandardFilename(JsonDataAbsolutePath); TSharedRef<FJsonObject> JsonObjectSrc = MakeShared<FJsonObject>(); FJsonObjectConverter::UStructToJsonObject(StudentSrc->GetClass(), StudentSrc, JsonObjectSrc); FString JsonOutString; TSharedRef<TJsonWriter<TCHAR>> JsonWriterAr = TJsonWriterFactory<TCHAR>::Create(&JsonOutString); if (FJsonSerializer::Serialize(JsonObjectSrc, JsonWriterAr)) { FFileHelper::SaveStringToFile(JsonOutString, *JsonDataAbsolutePath); } FString JsonInString; FFileHelper::LoadFileToString(JsonInString, *JsonDataAbsolutePath); TSharedRef<TJsonReader<TCHAR>> JsonReaderAr = TJsonReaderFactory<TCHAR>::Create(JsonInString); TSharedPtr<FJsonObject> JsonObjectDest; if (FJsonSerializer::Deserialize(JsonReaderAr, JsonObjectDest)) { UStudent* JsonStudentDest = NewObject<UStudent>(); if (FJsonObjectConverter::JsonObjectToUStruct(JsonObjectDest.ToSharedRef(), JsonStudentDest->GetClass(), JsonStudentDest)) { PrintStudentInfo(JsonStudentDest, TEXT("JsonData")); } } } 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::SaveStudentPackage() const { // 패키지 로드 UPackage* StudentPackage = ::LoadPackage(nullptr, *PackageName, LOAD_None); if (StudentPackage) // 패키지 있다면 로드 { StudentPackage->FullyLoad(); // 안에 가지고 있는 에셋을 모두 다 로딩 } 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); // 서브오브젝트 10개 생성 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")); } void UMyGameInstance::LoadStudentObject() const { const FString TopSoftObjectPath = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName); // 애셋의 오브젝트 경로 // 로딩: 패키지를 로딩하지 않기 때문에 nullptr값을 첫번째 인자 넣음. 두번째 인자로 경로 정보를 넣음. UStudent* TopStudent = LoadObject<UStudent>(nullptr, *TopSoftObjectPath); PrintStudentInfo(TopStudent, TEXT("LoadObject Asset")); }
학생 오브젝트 로드
- void UMyGameInstance::LoadStudentObject() const;
비동기 로딩
- void UMyGameInstance::Init()
실행화면

< 생성자에서 에셋 로딩 코드 추가한 버젼 >


만약 위의 파일을 지우고 언리얼 엔진 에디터를 실행하면 아래와 같은 메시지를 띄우며 에디터가 열리지 않는다.

따라서 에셋이 빠지지 않도록 주의해야 한다.
< 비동기 로드 추가 버젼 >

'⭐ Unreal Engine > UE 개념정리 - 언리얼의 이해' 카테고리의 다른 글
[UE] 언리얼 빌드 시스템 (4) | 2024.09.02 |
---|---|
[UE] 언리얼 오브젝트 관리 I - 직렬화 (UE Object Management I - Serialization) (0) | 2024.07.03 |
[UE] 언리얼 엔진의 메모리 관리 (Memory Management in UE) (0) | 2024.07.02 |
[UE] 언리얼 구조체와 TMap (1) | 2024.07.01 |
[UE] 언리얼 C++의 TArray, TSet, TMap 자료구조 라이브러리와 활용방법 (0) | 2024.04.20 |
댓글
이 글 공유하기
다른 글
-
[UE] 언리얼 빌드 시스템
[UE] 언리얼 빌드 시스템
2024.09.02 -
[UE] 언리얼 오브젝트 관리 I - 직렬화 (UE Object Management I - Serialization)
[UE] 언리얼 오브젝트 관리 I - 직렬화 (UE Object Management I - Serialization)
2024.07.03 -
[UE] 언리얼 엔진의 메모리 관리 (Memory Management in UE)
[UE] 언리얼 엔진의 메모리 관리 (Memory Management in UE)
2024.07.02 -
[UE] 언리얼 구조체와 TMap
[UE] 언리얼 구조체와 TMap
2024.07.01
댓글을 사용할 수 없습니다.