[UE C++] Data Table
1. 创建结构体
USTRUCT(BlueprintType)
struct FTableStruct :public FTableRowBase
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere)
FString TestString;
UPROPERTY(EditAnywhere)
float Damage;
};
结构体创建完成之后,回到Editor创建Data Table,比较简单,此处省略
UStruct 的用法可以参考这一篇文章: UE C++ Struct
2. 加载Data Table
首先声明DataTable变量
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "DataTable")
UDataTable* TestDataTable;
2.1 ConstructorHelpers::FObjectFinder
注意必须在构造函数中实现
static ConstructorHelpers::FObjectFinder<UDataTable> TableType(TEXT("DataTable'/Game/Data/Da_Test.Da_Test'"));
if (TableType.Succeeded())
{
TestDataTable = TableType.Object;
}
资源的 Path 可以通过,右键点击资源 Copy Reference 获得
2.2 StaticLoadObject
TestDataTable = Cast<UDataTable>(StaticLoadObject(UDataTable::StaticClass(), NULL, TEXT("DataTable'/Game/Data/Da_Test.Da_Test'")));
2.3 LoadObject
TestDataTable = LoadObject<UDataTable>(this, TEXT("DataTable'/Game/Data/Da_Test.Da_Test'"));
观察LoadObject代码,发现其实调用的就是StaticLoadObject
template< class T >
inline T* LoadObject( UObject* Outer, const TCHAR* Name, const TCHAR* Filename=nullptr, uint32 LoadFlags=LOAD_None, UPackageMap* Sandbox=nullptr )
{
return (T*)StaticLoadObject( T::StaticClass(), Outer, Name, Filename, LoadFlags, Sandbox );
}
DataTable也是一种资源,有很多种加载方式,资源的加载方式可以看这篇文章UE 资源加载
3. 读取数据
3.1 FindRow
if (TestDataTable)
{
for (FName RowName : TestDataTable->GetRowNames())
{
FTableStruct* DataTableRowInfo = TestDataTable->FindRow<FTableStruct>(RowName, TEXT("TestDataTableContext"));
//use row
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Yellow, \
FString::Printf(TEXT("TestString: %s \n Damage: %f"),*DataTableRowInfo->TestString, DataTableRowInfo->Damage));
}
}
3.2 GetAllRows
if (TestDataTable)
{
TArray<FTableStruct*> DataTableRowInfos;
TestDataTable->GetAllRows<FTableStruct>(TEXT("TestDataTableContext"), DataTableRowInfos);
for (FTableStruct* DataTableRowInfo : DataTableRowInfos)
{
//use row
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Yellow, \
FString::Printf(TEXT("TestString: %s \n Damage: %f"), *DataTableRowInfo->TestString, DataTableRowInfo->Damage));
}
}
3.3 GetRowMap
if (TestDataTable)
{
for (auto iter : TestDataTable->GetRowMap())
{
FName RowName = iter.Key;
FTableStruct* DataTableRowInfo = (FTableStruct*)iter.Value;
//use row
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Yellow, \
FString::Printf(TEXT("TestString: %s \n Damage: %f"), *DataTableRowInfo->TestString, DataTableRowInfo->Damage));
}
}
4. 写入数据
AddRow
可以RunTime使用
if (TestDataTable)
{
FTableStruct* DataTableRowInfo = new FTableStruct();
DataTableRowInfo->Damage = 30.f;
DataTableRowInfo->TestString = FString("Three");
TestDataTable->AddRow(FName("Third"), *DataTableRowInfo);
}
4. CSV
4.1 CSV To DataTable
CSV内容如下
---,TestString,Damage
1,"OneOne",100
2,"TwoTwo",200
3,"ThreeThree",300
UE 会自动将第一列作为Row Name,第一列的名字可以是 ---
,也可以是 -
,或者其他中英文字符,甚至可以为空
,TestString,Damage
1,"OneOne",100
2,"TwoTwo",200
3,"ThreeThree",300
4.1.2填充现有的DataTable
声明函数 CallInEditor
UFUNCTION(CallInEditor)
void FillDataTable();
定义函数
void FillDataTable()
{
UDataTable* TestDataTable = LoadObject<UDataTable>(this, TEXT("DataTable'/Game/Data/Test.Test'"));
if (TestDataTable)
{
FString CSVPath{ FPaths::ProjectDir() + "DataImport/TestCSV.csv" };
if (FPlatformFileManager::Get().GetPlatformFile().FileExists(*CSVPath))
{
UDataTableFunctionLibrary::FillDataTableFromCSVFile(TestDataTable, CSVPath);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("CSV Failed"));
}
}
}
注意事项:
- 确保要填充的DataTable文件为空,即里面不含内容,不然Editor会崩溃
- 这个方法只能在Editor中使用,无法在Runtime使用 (使用Editor Utility),原因是FillDataTableFromCSVFile()为Editor Only
#include "Kismet/DataTableFunctionLibrary.h"
- 如果你将Editor Only的函数声明在了Runtime的Actor中打包会产生问题
4.1.3生成新的DataTable
这个方法可以在Runtime使用
FString CSVPath{ FPaths::ProjectDir() + "DataImport/TestCSV.csv" };
if (FPlatformFileManager::Get().GetPlatformFile().FileExists(*CSVPath))
{
//加载CSV文件
FString CSVData;
FFileHelper::LoadFileToString(CSVData, *CSVPath);
//创建package
FString DataTableName = "DT_CreateTest";
FString PackagePath = "/Game/Data/";
PackagePath += DataTableName;
UPackage* MyPackage = CreatePackage(nullptr, *PackagePath);
//创建DataTable
UDataTable* DT_CreateTest = NewObject<UDataTable>(MyPackage, *DataTableName, EObjectFlags::RF_Public | RF_Standalone);
//初始化DataTable
DT_CreateTest->RowStruct = FTableStruct::StaticStruct();
DT_CreateTest->CreateTableFromCSVString(CSVData);
//结束创建
FAssetRegistryModule::AssetCreated(DT_CreateTest);
DT_CreateTest->GetOutermost()->MarkPackageDirty();
//打印信息
if (DT_CreateTest)
{
for (FName RowName : DT_CreateTest->GetRowNames())
{
FTableStruct* DataTableRowInfo = DT_CreateTest->FindRow<FTableStruct>(RowName, TEXT("TestDataTableContext"));
UE_LOG(LogTemp, Warning, TEXT("%s : %s \t Damage: %f"), \
* RowName.ToString(), *DataTableRowInfo->TestString, DataTableRowInfo->Damage);
}
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("CSV Failed"));
}
如果给UDataTable* 开始时就指向一个Asset实例,就可以达到 Runtime 修改DataTable的效果了(和4.1.2相比较)
FString CSVPath{ FPaths::ProjectDir() + "DataImport/TestCSV.csv" };
if (FPlatformFileManager::Get().GetPlatformFile().FileExists(*CSVPath))
{
FString CSVData;
FFileHelper::LoadFileToString(CSVData, *CSVPath);
UDataTable* TestDataTable = LoadObject<UDataTable>(this, TEXT("DataTable'/Game/Data/Test.Test'"));
TestDataTable->CreateTableFromCSVString(CSVData);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("CSV Failed"));
}
注意事项: 这种修改方式不要求填充的DataTable为空,会直接覆盖掉原来的内容
4.2 DataTable To CSV
Editor Only
UDataTable* TestDataTable = LoadObject<UDataTable>(this, TEXT("DataTable'/Game/Data/Da_Test.Da_Test'"));
if (TestDataTable)
{
FString CSVString = TestDataTable->GetTableAsCSV();
FString CSVPath{ FPaths::ProjectDir() + "DataImport/DataTableToCSV.csv" };
FFileHelper::SaveStringToFile(CSVString, *CSVPath, FFileHelper::EEncodingOptions::ForceUTF8);
}
注意事项:
- GetTableAsCSV()是Editor函数,但是如果你在Runtime调用了此函数也是可以 生效 的,但是 打包 会产生问题。所以可以利用这一特点,较为简单的为游戏在运行时生成大量的CSV文件来使用,然后打包阶段去除此函数
- 如果想要在Runtime真正实现DataTable To CSV的效果,可以参考这篇文章 动态读写DataTable
5. JSON
JSON的操作和CSV十分的相似,就是相应的API发生了变化,这里就不重复介绍了
总结
- CSV/JSON To DataTable 的Runtime方法很简单,UE提供了2种Runtime可供访问的API
再配合/** * Create table from CSV style comma-separated string. * RowStruct must be defined before calling this function. * @return Set of problems encountered while processing input */ ENGINE_API TArray<FString> CreateTableFromCSVString(const FString& InString); /** * Create table from JSON style string. * RowStruct must be defined before calling this function. * @return Set of problems encountered while processing input */ ENGINE_API TArray<FString> CreateTableFromJSONString(const FString& InString);
FFileHelper::LoadFileToString
就可以完成转化 - DataTable To CSV/JSON的Runtime方法较为复杂,可以参考如下文章:
DataTable to CSV
DataTable to JSON - 引擎对于DataTable的操作大多数局限在Editor中,所以在Editor中可以很方便的转化DataTable