[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