概要
UnrealEngine の Commandlet についてのメモです。
BPの設定変更を試してみます。
▼関連
UE4 Commandletでアセットを一括修正してみる
環境
Windows10
Visual Studio 2017
UnrealEngine 4.25
参考
以下を参考にさせて頂きました、ありがとうございます。
UnrealEngine : UCommandlet
UnrealEngine : Commandlets
関連ソース
"Engine\Source\Runtime\Engine\Classes\Commandlets\Commandlet.h"
作成例
ブループリントの設定をコマンドレットにて書き換えてみます。
元となるBP
サードパーソンのC++で新規作成した AMyProjectCharacter
クラスに対し、テスト用のメンバ AnimClassTest
を追加します。アニメーションブループリントを想定します。配列にしているのは特に意味はありません。
UCLASS(config=Game)
class AMyProjectCharacter : public ACharacter
{
GENERATED_BODY()
public:
// ..省略..
// テスト用
UPROPERTY(Category = "Test", EditAnywhere, BlueprintReadWrite)
TArray<TSubclassOf<class UAnimInstance>> AnimClassTest;
};
これを親とするBP ThirdPersonCharacter
は以下の様になっています。
追加された AnimClassTest
が確認できます。
ここを書き換えてみます。
BP書き換え用コマンドレットクラス
対象BPを探し出してデフォルトオブジェクトを取得し、書き換えます。
(ファイル検索時のアセットレジストリのフィルタの使い方が微妙です。)
また予めコンストラクタで書き換えるクラスを FClassFinder
で探します。クラスなので、パスには _C
を付ける必要があります。(もしくは拡張子を省略)
#pragma once
#include "CoreMinimal.h"
#include "Commandlets/Commandlet.h"
#include "MyCommandlet.generated.h"
UCLASS()
class UMyCommandlet : public UCommandlet
{
GENERATED_BODY()
public:
UMyCommandlet(const FObjectInitializer& ObjectInitializer);
virtual int32 Main(const FString& CmdLineParams) override;
// 対象アセットリストを取得する
void GetAssetList(TArray<FAssetData>& _AssetList) const;
// 参照保持するクラス
TArray<TSubclassOf<UAnimInstance>> AnimBPList;
};
コンストラクタで書き換え先のクラスを取得します。
ファイル名を配列にしているのはこのコード例では無意味です。直接Findしてしまってもいいです。
変更したBPに対し、MarkPackageDirty
を付けていますが、コマンドレットの実行では不要かと思います。
#include "MyCommandlet.h"
#include "AssetData.h"
#include "AssetRegistryModule.h"
#include "AssetRegistryHelpers.h"
#include "Misc/FileHelper.h"
#include "UObject/Object.h"
#include "UObject/Class.h"
#include "./MyProjectCharacter.h"
DEFINE_LOG_CATEGORY_STATIC(LogTextMyCommandlet, Log, All);
// コンストラクタ
UMyCommandlet::UMyCommandlet(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
LogToConsole = false;
// 書き換えるファイルパス
const TArray<const TCHAR*> _AnimBPList = {
TEXT("AnimBlueprint'/Game/Mannequin/Animations/ThirdPerson_AnimBP.ThirdPerson_AnimBP_C'"),
};
AnimBPList.SetNum(1);
for (int32 _Lp = 0; _Lp < AnimBPList.Num(); _Lp++) {
ConstructorHelpers::FClassFinder<UAnimInstance> find_class(_AnimBPList[_Lp]);
if (find_class.Succeeded()) {
// アニムインスタンスクラス保持
AnimBPList[_Lp] = find_class.Class;
}
}
}
// メイン処理
int32 UMyCommandlet::Main(const FString& CmdLineParams)
{
TArray<FAssetData> _AssetList;
TArray<UPackage*> _PackagesToSave;
// アセットリストの取得
GetAssetList(_AssetList);
// アセット変更
for (auto _AssetData : _AssetList) {
// BPにキャスト
UBlueprint* _Blueprint = Cast<UBlueprint>(_AssetData.GetAsset());
if( _Blueprint ){
// クラスデフォルトオブジェクトを取得
auto _MyChara = _Blueprint->GeneratedClass->GetDefaultObject<AMyProjectCharacter>();
if (_MyChara) {
// 変数を書き換える
if( _MyChara->AnimClassTest.Num() > 0){
_MyChara->AnimClassTest[0] = (UClass*)(AnimBPList[0]);
}
// 編集フラグを立てる
_Blueprint->MarkPackageDirty();
// 変更したパッケージ追加
_PackagesToSave.Add(_AssetData.GetPackage());
}
}
}
// アセットセーブ
for (const auto& _It : _PackagesToSave) {
// 保存ファイル名(Chopで文字操作して無理矢理作成しています)
const auto _FileName = FString::Printf(TEXT("%s%s%s"), *FPaths::GameContentDir(), *_It->GetFullName().RightChop(14), *FPackageName::GetAssetPackageExtension());
// パッケージ保存
bool _Ret = UPackage::SavePackage(_It, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *_FileName);
}
return(0);
}
// 対象アセットリストを取得する
void UMyCommandlet::GetAssetList(TArray<FAssetData>& _AssetList) const
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
IFileManager& _FileMgr = IFileManager::Get();
FARFilter Filter;
Filter.bRecursivePaths = true;
// 対象パス(Contentフォルダ全体を対象にするなら "/Game/" を指定)
Filter.PackagePaths.Add(TEXT("/Game/"));
// 対象クラス指定
Filter.ClassNames.Add(TEXT("Blueprint"));
AssetRegistry.GetAssets(Filter, _AssetList);
}
実行テスト
エンジンフォルダにあるexeに uproject と-run
でコマンドラインクラス名を指定します。MyCommandlet
のうち、Commandlet
の部分は省略できます。
"Engine\Binaries\Win64\UE4Editor-Cmd.exe" "MyProject\MyProject.uproject" -run=My -stdout -UTF8Output
結果
テスト用の変数 AnimClassTest
が書き変わります。
クラスだけではなく、もちろん変数なども同様の方法で書き換え可能です。
備考
起動するエンジンのバージョン違い
*.uproject
のエンジンバージョンを確認しましょう。
{
"FileVersion": 3,
"EngineAssociation": "4.24",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "MyProject",
"Type": "Runtime",
"LoadingPhase": "Default"
}
]
}
C++側からのコマンドレット呼び出し
ログを出力するだけのコマンドレット MyTest1Commandlet
と MyTest2Commandlet
を呼び出す場合のコード例。
// メイン処理
int32 UMyCommandlet::Main(const FString& CmdLineParams)
{
// コマンドレット1呼び出し
ExecuteCommandlet(FString("MyTest1Commandlet"), FString("-stdout -UTF8Output"));
// コマンドレット2呼び出し
ExecuteCommandlet(FString("MyTest2Commandlet"), FString("-stdout -UTF8Output"));
return(0);
}
// コマンド実行
void UMyCommandlet::ExecuteCommandlet(const FString& _CommaneletName, const FString& _Params)
{
// コマンドレットクラスを探す
UClass* _CommandletClass = FindObject<UClass>(ANY_PACKAGE, *_CommaneletName, false);
if( ::IsValid(_CommandletClass) ){
// コマンドレットオブジェクトを作成
UCommandlet* _Commandlet = NewObject<UCommandlet>(GetTransientPackage(), _CommandletClass);
if( ::IsValid(_Commandlet) ){
// 実行
_Commandlet->Main(_Params);
}
}
}
この方法だと、エディタ1回の起動で複数コマンドレットを呼び出せます。
まとめ
コマンドレットの有用な使い方を模索中です。
多数のブループリントを条件によって書き換える場合などには使えると思います。