본문 바로가기
Unreal/기본

[Unreal] DataTable을 코드에서 수정하고 저장

by 카피마스터 2025. 1. 5.

수정 및 저장 처리(PIE에서도 동작)

// FSavePackageArgs 사용시 필요 헤더
#include "Windows/AllowWindowsPlatformTypes.h"
#include "windows.h" // any native windows header
#include "Windows/HideWindowsPlatformTypes.h"
#include "UObject/SavePackage.h"

// UEditorLoadingAndSavingUtils를 사용하여 저장시 필요한 헤더
#include "FileHelpers.h"

bool ChangeAndSaveTable()
{
	FString filePath = TEXT("C:/Dev/Projects/TestProject/Content/Tables/NPCTable.uasset");

	// 게임 베이스(/Game/Tables/NPCTable.uasset) 경로로 변경
	FString gameBasePath = ConvertGameBasePath(filePath);
	if (true == gameBasePath.IsEmpty()) {
		return false;
	}

	// 대상 테이블로드를 위해 레퍼런스(/Game/Tables/NPCTable.NPCTable) 경로로 변경
	FString fileName = FPaths::GetBaseFilename(gameBasePath);
	gameBasePath = gameBasePath.Replace(TEXT("uasset"), *fileName);

	// 테이블 로드
	UDataTable* dataTable = Cast<UDataTable>(StaticLoadObject(UDataTable::StaticClass(), nullptr, *gameBasePath));
	if (nullptr == dataTable) {
		return false;
	}

	UPackage* package = dataTable->GetPackage();
	if (nullptr == package) {
		return false;
	}

	// 대상 테이블의 row 구조체가 처리할 구조체인지 체크
    // 여기서는 FNPCTableRow 구조체
	const UScriptStruct* rowStruct = dataTable->GetRowStruct();
	if (nullptr == rowStruct || false == rowStruct->IsChildOf(FNPCTableRow::StaticStruct())) {
		return false;
	}

	const TMap<FName, uint8*>& rowMap = dataTable->GetRowMap();
	for (auto& pair : rowMap)
	{
		FNPCTableRow* row = (FNPCTableRow*)(&pair.Value[0]);

		// 정보 수정
		row->NPCName = TEXT("test");
	}

	
	FSavePackageArgs saveArgs;
	saveArgs.TopLevelFlags = RF_Public | RF_Standalone;
	saveArgs.Error = GWarn;

	bool isSuccess = UPackage::SavePackage(package, dataTable, *filePath, saveArgs);
	//isSuccess = UEditorLoadingAndSavingUtils::SavePackages({ package }, false);		// 이것도 저장이 가능

	return isSuccess;
}

 

1. 특정 경로(C:/Dev/Projects/TestProject/Content/Tables/NPCTable.uasset)의 DataTable의 정보( FNPCTableRow구조체)를 변경하고 저장

 

2-1. UPackage::SavePackage 사용

FSavePackageArgs를 사용하려면 SavePackage.h 헤더가 필요한데 추가시 에러가 발생하기 때문에 

AllowWindowsPlatformTypes.h, windows.h, HideWindowsPlatformTypes.h 헤더도 추가가 필요하다

 

2-2. UEditorLoadingAndSavingUtils:: SavePackages 사용

FileHelpers.h 헤더가 필요하고, [프로젝트].Build.cs에 UnrealEd 모듈 추가가 필요(참고)

 

3. ConvertGameBasePath()는 상대/절대 경로를 /Game/  기반의 경로로 변경하는 함수(참고)

 

데이터가 변경되고 아직 저장되지 않은 상태 표시(더티 플래그) 처리

bool ChangeAndMarkTable()
{
	FString filePath = TEXT("C:/Dev/Projects/TestProject/Content/Tables/NPCTable.uasset");

	// 게임 베이스(/Game/Tables/NPCTable.uasset) 경로로 변경
	FString gameBasePath = ConvertGameBasePath(filePath);
	if (true == gameBasePath.IsEmpty()) {
		return false;
	}

	// 대상 테이블로드를 위해 레퍼런스(/Game/Tables/NPCTable.NPCTable) 경로로 변경
	FString fileName = FPaths::GetBaseFilename(gameBasePath);
	gameBasePath = gameBasePath.Replace(TEXT("uasset"), *fileName);

	// 테이블 로드
	UDataTable* dataTable = Cast<UDataTable>(StaticLoadObject(UDataTable::StaticClass(), nullptr, *gameBasePath));
	if (nullptr == dataTable) {
		return false;
	}

	UPackage* package = dataTable->GetPackage();
	if (nullptr == package) {
		return false;
	}

	// 대상 테이블의 row 구조체가 처리할 구조체인지 체크
	const UScriptStruct* rowStruct = dataTable->GetRowStruct();
	if (nullptr == rowStruct || false == rowStruct->IsChildOf(FNPCTableRow::StaticStruct())) {
		return false;
	}

	const TMap<FName, uint8*>& rowMap = dataTable->GetRowMap();
	for (auto& pair : rowMap)
	{
		FNPCTableRow* row = (FNPCTableRow*)(&pair.Value[0]);

		// 정보 수정
		row->NPCName = TEXT("test");
	}
	
    // 플래그 설정
	bool isSuccess = dataTable->MarkPackageDirty();
    
	return isSuccess;
}

 

 

 

PIE에서는 동작하지 않는데 MarkPackageDirty() 함수안에 보면 GIsPlayInEditorWorld가 true일 경우 처리되지 않게 되어있다

 

다음처럼 잠시 false로 만들어주면 처리는 된다

GIsPlayInEditorWorld = false;
dataTable->MarkPackageDirty();
GIsPlayInEditorWorld = true;