C++でData Validationを行うまでの道のり

Data Validationとは

「ゲーム開発してるとこういうの欲しくなるよね」という機能が、
UEでは、あれもこれも公式プラグインとして公開されています。
Data Validationもまさにそれ。
プロジェクト内のアセットがルールに従っているか自動でチェックするための機能です。

エディター上での作業時にチェックしてくれるのはもちろん、コマンドラインからの実行も可能です。

docs.unrealengine.com5.3/ja/data-validation-in-unreal-engine

概要の把握やBPで行う場合は、おかずさんのブログ記事を参考にするのがオススメです。
今回は、C++で行う場合にいくつか戸惑う箇所があったので紹介します。
UE初心者なので、もっと簡単な方法があればコメントください。

何はともあれValidation用のコードを書く

ドキュメントに沿ってコードを書きます。
UEditorValidatorBaseを継承したC++クラスを作って素直に書くだけ。

SampleValidator.cpp
#include "DataValidator/SampleValidator.h"

bool USampleValidator::CanValidateAsset_Implementation(UObject* InAsset) const
{
	// /Game/Path以下のアセットを対象にする
	return InAsset->GetPathName().StartsWith(TEXT("/Game/Path/"));
}

EDataValidationResult USampleValidator::ValidateLoadedAsset_Implementation(UObject* InAsset, TArray<FText>& ValidationErrors)
{
	EDataValidationResult Result = EDataValidationResult::Valid;

	// 名前がSM_から始まってるか?
	FString Name = InAsset->GetName();
	if (!Name.StartsWith("SM_"))
	{
		Result = EDataValidationResult::Invalid;
		AssetFails(InAsset, FText::FromString("!Name.StartsWith(\"SM_\")"), ValidationErrors);
	}

	// StaticMeshか?
	UStaticMesh* StaticMesh = Cast<UStaticMesh>(InAsset);
	if (StaticMesh == nullptr)
	{
		Result = EDataValidationResult::Invalid;
		AssetFails(InAsset, FText::FromString("StaticMesh == nullptr"), ValidationErrors);
	}

	// AssetFailsでreturnせず、このように最後に判定することで複数のエラーをまとめて表示できる。オススメ。
	if (Result == EDataValidationResult::Valid)
	{
		AssetPasses(InAsset);
	}
	return Result;
}
SampleValidator.h
#pragma once

#include "CoreMinimal.h"
#include "EditorValidatorBase.h"
#include "SampleValidator.generated.h"

UCLASS()
class PROJECT_API USampleValidator : public UEditorValidatorBase
{
	GENERATED_BODY()

public:
	virtual bool CanValidateAsset_Implementation(UObject* InAsset) const override;
	virtual EDataValidationResult ValidateLoadedAsset_Implementation(UObject* InAsset, TArray<FText>& ValidationErrors) override;
};

Editor上ではこれだけで動きます!!めっちゃ簡単!!
と感動したのも束の間、ゲームのビルドがコケるようになります。

ビルドエラーの原因を探る

エラー内容は以下の通り。
Unable to instantiate module 'UnrealEd': Unable to instantiate UnrealEd module for non-editor targets.
どうやら、DataValidationが依存しているUnrealEdというエディター専用のモジュールが、
Gameビルド時にも参照されてしまっているようです。

自動的にuproject内のAdditionalDependenciesに"DataValidator" が追加されているのが原因なのですが、
もちろんこれを消すだけではEditor上でもビルドエラーに逆戻り。

Lyraのコードを参考にしたところ、Editor専用のモジュールを新しく作ってしまうのが
(面倒だけど)一番シンプルに落ち着きそうなので、モジュールを分けることにします。

モジュールを分ける

Unityでいうところのasmdefを置いてプロジェクトを分けるみたいなことですね。

モジュールの作り方はドキュメントに従っていけば簡単です。
uprojectに書くTypeをEditorにすれば、Editorビルド時にだけ参照されるモジュールの完成!
先ほど書いたValidatorのコードも新しく作ったモジュールに移動させます。

これでエディター上ではValidationを走らせられ、ゲームビルドも正常に通るようになりました!

次はCI上で実行してみる

さて、手動で実行できるようになったら次は自動化。
コマンドラインから実行してみます。
この1行を実行するだけ。
UnrealEditor-Cmd.exe <PROJECT_NAME>.uproject -run=DataValidation
(さらに -stdout をつけるとGUIでダイアログ出たりしなくなるのでオススメ。)

このコマンド、手元では動くのですが別マシンでは
Incompatible or missing module: (さっき作ったモジュール名)
というエラーが出ました。
そのモジュールがビルドされてないから読めないってことですね。

DataValidationを実行する前にこんな感じでEditorをビルドしてやりましょう。
Build/BatchFiles/Build.bat ProjectName Win64 Development -Project=MyProject.uproject' -TargetType=Editor
最後の -TargetType=Editor が重要。

おしまい

アセットのバリデーションなんてなんぼやっても良いですからね。