โš™๏ธ ์—”์ง„/๐Ÿซ ์–ธ๋ฆฌ์–ผ

[Unreal] ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ ๊ด€๋ฆฌ2 - ํŒจํ‚ค์ง€

peewoong 2024. 5. 8. 15:30

1. ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ ํŒจํ‚ค์ง€

  • ๋‹จ์ผ ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ๊ฐ€์ง„ ์ •๋ณด๋Š” ์ €์žฅํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์˜ค๋ธŒ์ ํŠธ๋“ค์ด ์กฐํ•ฉ๋˜์–ด ์žˆ๋‹ค๋ฉด?
    • ์ €์žฅ๋œ ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ์ฐพ๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์€?
    • ๋ณต์žกํ•œ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„ ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ์ €์žฅ๊ณผ ๋ถˆ๋Ÿฌ๋“ค์ด๋Š” ๋ฐฉ๋ฒ•์„ ํ†ต์ผํ•ด์•ผ ํ•œ๋‹ค.
  • ์–ธ๋ฆฌ์–ผ ์—”์ง„์€ ์ด๋ฅผ ์œ„ํ•ด ํŒจํ‚ค์ง€(UPackage) ๋‹จ์œ„๋กœ ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๊ด€๋ฆฌํ•จ
  • ํŒจํ‚ค์ง€์˜ ์ค‘์˜์  ๊ฐœ๋…
    • ์–ธ๋ฆฌ์–ผ ์—”์ง„์€ ๋‹ค์–‘ํ•œ ๊ณณ์—์„œ ๋‹จ์–ด ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Œ
    • ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๊ฐ์‹ผ ํฌ์žฅ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์˜๋ฏธํ•จ ๐Ÿ‘‰ ํฌ์ŠคํŒ…์˜ ์ฃผ์ œ
    • ๋˜ํ•œ ๊ฐœ๋ฐœ๋œ ์ตœ์ข… ์ฝ˜ํ…์ธ ๋ฅผ ์ •๋ฆฌํ•ด ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ ๋งŒ๋“œ๋Š” ์ž‘์—…์„ ์˜๋ฏธํ•จ ์˜ˆ) ๊ฒŒ์ž„ ํŒจํ‚ค์ง•
    • DLC์™€ ๊ฐ™์ด ํ–ฅํ›„ ํ™•์žฅ ์ฝ˜ํ…์ธ ์— ์‚ฌ์šฉ๋˜๋Š” ๋ณ„๋„์˜ ๋ฐ์ดํ„ฐ ๋ฌถ์Œ์„ ์˜๋ฏธํ•˜๊ธฐ๋„ ํ•จ ์˜ˆ) pkg ํŒŒ์ผ
  • ๊ตฌ๋ถ„์„ ์œ„ํ•ด ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ ํŒจํ‚ค์ง€๋กœ ๋ถ€๋ฅด๋Š” ๊ฒƒ๋„ ๊ณ ๋ คํ•œ๋‹ค.

 

๐ŸŸฉ ํŒจํ‚ค์ง€์™€ ์—์…‹

  • ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ ํŒจํ‚ค์ง€๋Š” ๋‹ค์ˆ˜์˜ ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ํฌ์žฅํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•˜๋Š” ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ
    • ๋ชจ๋“  ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ๋Š” ํŒจํ‚ค์ง€์— ์†Œ์†๋˜์–ด ์žˆ์Œ ์˜ˆ) Transient Package
  • ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ ํŒจํ‚ค์ง€์˜ ์„œ๋ธŒ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์—์…‹(asset)์ด๋ผ๊ณ  ํ•˜๋ฉฐ ์—๋””ํ„ฐ์—๋Š” ์ด๋“ค์ด ๋…ธ์ถœ๋จ
  • ๊ตฌ์กฐ์ƒ ํŒจํ‚ค์ง€๋Š” ๋‹ค์ˆ˜์˜ ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์†Œ์œ ํ•  ์ˆ˜ ์žˆ์œผ๋‚˜, ์ผ๋ฐ˜์ ์œผ๋กœ๋Š” ํ•˜๋‚˜์˜ ์—์…‹๋งŒ ๊ฐ€์ง
  • ์—์…‹์€ ๋‹ค์ˆ˜์˜ ์„œ๋ธŒ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ชจ๋‘ ์–ธ๋ฆฌ์–ผ ์˜ค๋ธŒ์ ํŠธ ํŒจํ‚ค์ง€์— ํฌํ•จ๋จ
    • ํ•˜์ง€๋งŒ ์—๋””ํ„ฐ์—๋Š” ๋…ธ์ถœ๋˜์ง€ ์•Š์Œ


2. ์—์…‹ ์ •๋ณด์˜ ์ €์žฅ๊ณผ ๋กœ๋”ฉ ์ „๋žต

  • ๊ฒŒ์ž„ ์ œ์ž‘ ๋‹จ๊ณ„์—์„œ ์—์…‹ ๊ฐ„์˜ ์—ฐ๊ฒฐ ์ž‘์—…์„ ์œ„ํ•ด ์ง์ ‘ ํŒจํ‚ค์ง€๋ฅผ ๋ถˆ๋Ÿฌ ํ• ๋‹นํ•˜๋Š” ์ž‘์—…์€ ๋ถ€ํ•˜๊ฐ€ ํผ
    • ์—์…‹ ๋กœ๋”ฉ ๋Œ€์‹  ํŒจํ‚ค์ง€์™€ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ง€์ •ํ•œ ๋ฌธ์ž์—ด์„ ๋Œ€์ฒดํ•ด ์‚ฌ์šฉ. ์ด๋ฅผ ์˜ค๋ธŒ์ ํŠธ ๊ฒฝ๋กœ๋ผ ํ•จ
    • ํ”„๋กœ์ ํŠธ ๋‚ด์— ์˜ค๋ธŒ์ ํŠธ ๊ฒฝ๋กœ ๊ฐ’์€ ์œ ์ผํ•จ์„ ๋ณด์žฅํ•จ
    • ๊ทธ๋ ‡๊ธฐ์— ์˜ค๋ธŒ์ ํŠธ ๊ฐ„์˜ ์—ฐ๊ฒฐ์€ ์˜ค๋ธŒ์ ํŠธ ๊ฒฝ๋กœ ๊ฐ’์œผ๋กœ ๊ธฐ๋ก๋  ์ˆ˜ ์žˆ์Œ
    • ์˜ค๋ธŒ์ ํŠธ ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•ด ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ์—์…‹์„ ๋กœ๋”ฉํ•  ์ˆ˜ ์žˆ์Œ
  • ์—์…‹์˜ ๋กœ๋”ฉ ์ „๋žต
    • ํ”„๋กœ์ ํŠธ์—์„œ ์—์…‹์ด ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•œ ๊ฒฝ์šฐ : ์ƒ์„ฑ์ž ์ฝ”๋“œ์—์„œ ๋ฏธ๋ฆฌ ๋กœ๋”ฉ
    • ๋Ÿฐํƒ€์ž„์—์„œ ํ•„์š”ํ•œ ๋•Œ ๋ฐ”๋กœ ๋กœ๋”ฉํ•˜๋Š” ๊ฒฝ์šฐ : ๋Ÿฐํƒ€์ž„ ๋กœ์ง์—์„œ ์ •์  ๋กœ๋”ฉ
    • ๋Ÿฐํƒ€์ž„์—์„œ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋กœ๋”ฉํ•˜๋Š” ๊ฒฝ์šฐ : ๋Ÿฐํƒ€์ž„  ๋กœ์ง์—์„œ ๊ด€๋ฆฌ์ž๋ฅผ ์‚ฌ์šฉํ•ด ๋น„๋™๊ธฐ ๋กœ๋”ฉ

 

๐ŸŸฉ ์˜ค๋ธŒ์ ํŠธ ๊ฒฝ๋กœ

  • ํŒจํ‚ค์ง€ ์ด๋ฆ„๊ณผ ์—์…‹ ์ด๋ฆ„์„ ํ•œ ๋ฐ ๋ฌถ์€ ๋ฌธ์ž์—ด
  • ์—์…‹ ํด๋ž˜์Šค ์ •๋ณด๋Š” ์ƒ๋žตํ•  ์ˆ˜ ์žˆ์Œ
  • ํŒจํ‚ค์ง€ ๋‚ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๋กœ๋“œํ•˜์ง€ ์•Š๊ณ  ์˜ค๋ธŒ์ ํŠธ ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•ด ํ•„์š”ํ•œ ์—์…‹๋งŒ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์Œ
  • {์—์…‹ ํด๋ž˜์Šค ์ •๋ณด}'{ํŒจํ‚ค์ง€ ์ด๋ฆ„}.{์—์…‹ ์ด๋ฆ„}' or {ํŒจํ‚ค์ง€ ์ด๋ฆ„}.{์—์…‹ ์ด๋ฆ„}

 

๐ŸŸฉ ์—์…‹ ์ŠคํŠธ๋ฆฌ๋ฐ ๊ด€๋ฆฌ์ž(Steamable Manager)

  • ์—์…‹์˜ ๋น„๋™๊ธฐ ๋กœ๋”ฉ์„ ์ง€์›ํ•˜๋Š” ๊ด€๋ฆฌ์ž ๊ฐ์ฒด
  • ์ฝ˜ํ…์ธ  ์ œ์ž‘๊ณผ ๋ฌด๊ด€ํ•œ ์‹ฑ๊ธ€ํ„ด ํด๋ž˜์Šค์— FStreamableManager๋ฅผ ์„ ์–ธํ•ด๋‘๋ฉด ์ข‹์Œ
    • GameInstance๋Š” ์ข‹์€ ์„ ํƒ์ง€
  • FStreamableManager๋ฅผ ํ™œ์šฉํ•ด ์—์…‹์˜ ๋™๊ธฐ/๋น„๋™๊ธฐ ๋กœ๋”ฉ์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ
  • ๋‹ค์ˆ˜์˜ ์˜ค๋ธŒ์ ํŠธ ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•ด ๋‹ค์ˆ˜์˜ ์—์…‹์„ ๋กœ๋”ฉํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•จ

 

๐ŸŒŸ MyGameInstance(h, cpp)

// Fill out your copyright notice in the Description page of Project Settings.

#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;
};
// Fill out your copyright notice in the Description page of Project Settings.


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

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 RawDataAbsolutePath = FPaths::Combine(*SavedDir, *RawDataFileName);
      UE_LOG(LogTemp, Log, TEXT("์ €์žฅํ•  ํŒŒ์ผ ์ „์ฒด ๊ฒฝ๋กœ : %s"), *RawDataAbsolutePath);
      FPaths::MakeStandardFilename(RawDataAbsolutePath);
      UE_LOG(LogTemp, Log, TEXT("๋ณ€๊ฒฝํ•  ํŒŒ์ผ ์ „์ฒด ๊ฒฝ๋กœ : %s"), *RawDataAbsolutePath);

      FArchive* RawFileWriterAr = IFileManager::Get().CreateFileWriter(*RawDataAbsolutePath);
      if (nullptr != RawFileWriterAr) {
         *RawFileWriterAr << RawDataSrc;
         RawFileWriterAr->Close();
         delete RawFileWriterAr;
         RawFileWriterAr = nullptr;
      }

      FStudentData RawDataDest;
      FArchive* RawFileReaderAr = IFileManager::Get().CreateFileReader(*RawDataAbsolutePath);
      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"));
   }
    
   // json ์ง๋ ฌํ™”
   {
      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;

   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->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);
   UStudent* TopStudent = LoadObject<UStudent>(nullptr, *TopSoftObjectPath);
   PrintStudentInfo(TopStudent, TEXT("LoadObject Asset"));
}