ユーザー定義オブジェクトのアセット化で楽をするというhistoriaさんの記事を見て…
データテーブルからデータアセットを作ってみようと思ったら意外と躓いたことがあったのでまとめてみます。
参考記事にも記載されていますが、複数のテーブルから一つのアセットを作成したり、IDを使ってリソースの検索を行う処理を書く際に有益かもです。
環境
UE4 4.21.0
Visual Studio Community 2017
参考記事
下準備
検証するためのプロジェクト作成。
新規プロジェクトタブから基本コードのプロジェクトを作成します。
c++クラス作成
上にリンクを貼ったhistoriaさんの記事に倣ってDataAssetを継承したc++クラスを作成します。
名前はMyDataAssetで簡単な敵データっぽいアセットを仮作成してみます。
MyDataAsset.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "Engine/DataTable.h"
#include "MyDataAsset.generated.h"
// ================================
// データテーブル型の構造体
// Engine/DataTable.hをincludeしておくこと。
// ================================
USTRUCT()
struct FMyData : public FTableRowBase
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
int ID;
UPROPERTY(EditAnywhere)
int Attack;
UPROPERTY(EditAnywhere)
int HP;
};
// ================================
// インゲームで使用するデータアセットの1レコードにあたる構造体
// ================================
USTRUCT(BlueprintType)
struct FMyDataAssetRecord
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int ID;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int Attack;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int HP;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FText Name;
};
// ================================
// インゲームで使用するデータアセットクラス
// ================================
UCLASS()
class MYPROJECT_API UMyDataAsset : public UDataAsset
{
GENERATED_BODY()
public:
#if WITH_EDITORONLY_DATA
UPROPERTY(EditAnywhere, Category = "Test")
UDataTable* DataTable;
#endif
UFUNCTION(meta = (CallInEditor = "true"))
void Import();
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TArray<FMyDataAssetRecord> Data;
};
データテーブルを作る際の元となるクラスの作成を行う際は、継承元にFTableRowBaseを設定します。
今回インゲームで使用する想定のアセットとして作ったのはその下の構造体からです。
UDataAssetを継承したUMyDataAssetが一つのデータアセットとして作成できます。
今回はほぼデータテーブルと同じ型の
#if WITH_EDITORONLY_DATA
#endif
で囲まれている部分に読み込み元として設定するデータテーブルのポインタを定義することで、読み込みに使用するデータテーブルはエディタ上でしか使用しないよう設定できます。
MyDataAsset.cpp
#include "MyDataAsset.h"
void UMyDataAsset::Import()
{
#if WITH_EDITORONLY_DATA
if (! DataTable ||
! DataTable->GetRowStruct()->IsChildOf(FMyData::StaticStruct()))
{
return;
}
Data.Empty();
auto Names = DataTable->GetRowNames();
for (int i = 0; i < Names.Num(); i ++)
{
auto record = DataTable->FindRow<FMyData>(Names[i], FString());
FMyDataAssetRecord asset;
asset.ID = record->ID;
asset.Attack = record->Attack;
asset.HP = record->HP;
asset.Name = FText::FromName(Names[i]);
Data.Add(asset);
}
#endif
}
実装しているのはImportメソッドのみで、この中にDataTableからDataAssetへの変換を記載しています。
変換の際にはもちろんDataTableを使用するためヘッダの時と同様に
#if WITH_EDITORONLY_DATA
#endif
この記載をしておきます。
そしてこの後が躓いた点
躓き1:DataTableの型チェック
historiaさんの記事を見ていて、ヘッダ側にはUDataTableのポインタが記載されています。
今回もそれに倣っているのですが、当然UDataTableのポインタには今回自分が作った形式以外のDataTableを設定できてしまいます。
さてどうチェックしたものか。というのが躓きポイント。
解決方法
DataTable->GetRowStruct()->IsChildOf(FMyData::StaticStruct())
これでDataTableに使用されている構造体が適切であるかの確認ができます。
躓き2:DataTableの1レコードを取得する方法
UDataTableの定義の中を見るに、データは
TMap型で保持されているようです。実際にデータを取得してくる際はuint8*を拾ってキャストか…などと思っていましたが便利な関数がありました。
解決方法
auto Names = DataTable->GetRowNames();
for (int i = 0; i < Names.Num(); i ++)
{
auto record = DataTable->FindRow(Names[i], FString());
}
これで、FMyDataに変換された状態のレコードが取得できます。
テスト
上記のコードを記述した後、エディタ上でコンパイルを行いコードの内容を反映します。
その後コンテンツブラウザから今回作成したデータテーブルとデータアセットを作成します。
適当なデータを入れておきます。
スケルトンとかワイバーンとか。簡単な敵のデータ。
今更ながらEnemyDataとか名前を付けておけばよかった。画像取り直すのがめんdなんでもないです。
開くと作成したImportメソッドがボタンを押すことで呼び出せること、DataTableを設定する枠があることを確認できると思います。
枠にDataTableを設定し、Importボタンを押すことでデータの読み込みが行えるはずです。
まとめ
データの管理を行う上でIDを割り振りたいシーンは多いと思いますし、IDから検索したいと思うことも多いと思います。
そんな拡張をする際の助けになればと。
でもこの方法だと、インゲームで使用しないデータテーブルが量産されるというデメリットもありますね。
作る手間はありますが、factoryを作成してエディタ上にドラッグアンドドロップでアセットを作る方法の方が良いのかも。
その方法についても今度記事書こうかと思っていたり。