LoginSignup
6
8

More than 3 years have passed since last update.

UE4 アセットを一括修正してみる

Posted at

概要

UnrealEngine のエディターでのアセット修正についてのメモです。
サンプルとしてEditorUtilityWidgetをつかったテクスチャアセットのパラメータを変更するツールを作成してみます。
関連事項として外部アプリケーションの実行も試してみました。

環境

Windows10
Visual Studio 2017
UnrealEngine 4.24

参考

以下を参考にさせて頂きました、ありがとうございます。

UnrealEngine : アセットレジストリ
第004回 UE4のアセットを一括修正する
[UE4] 独自に用意したデータアセットをより便利に活用する方法
UE4 Editorから外部アプリケーションの実行

アセットレジストリについて

エディタ上に存在するアセット情報を自動で収集しているコンテンツブラウザが使っているシステム。コードから利用することも可能です。

.cpp
#include "AssetRegistryModule.h"
#include "AssetRegistryHelpers.h"

FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
TArray<FAssetData> AssetData;

// クラスを指定してアセットリストを取得する
const UClass* Class = UStaticMesh::StaticClass();
AssetRegistryModule.Get().GetAssetsByClass(Class, AssetData);

FARFilter を使って名前やパスなどの条件からアセットリストを取得することもできます。

EditorUtilityWidgetについて

エディター上で動作するUMGです。
C++を使う場合はエディタモジュールで書かないとパッケージ時にエラーになります。

テクスチャアセットを一括で変換してみる

テスクチャアセットのパラメータを一括変換するツールを作成してみます。
内容はテスクチャアセットの Adjustments パラメータをいくつか書き換えます。

C++ コード

UEditorUtilityWidget を継承したクラスを作成します。
UMGでのイベントも用意し、起動時(NativePreConstruct)に登録します。

MyAssetChanger.h
#include "CoreMinimal.h"
#include "EditorUtilityWidget.h"
#include "MyAssetChanger.generated.h"

UCLASS()
class APPEDITOR_API UMyAssetChanger : public UEditorUtilityWidget
{
    GENERATED_BODY()

public:
    UMyAssetChanger(){}

protected:
    virtual void NativePreConstruct() override;

    // 対象アセットリストを取得する
    void GetAssetList(TArray<FAssetData>& _AssetList) const;
    // アセット書き換え
    bool ChangeAsset(FAssetData& _AssetData);

    // チェックボックスイベント
    UFUNCTION(BlueprintCallable)
    void ChangeCheckBox(bool bChange);

    // ボタンクリックイベント
    UFUNCTION(BlueprintCallable)
    void ClickedButton();

private:
    // セーブフラグ
    bool    bIsSave = false;
};

NativePreConstruct でWidgetのボタンとチェックボックスのイベントをバインドさせています。
GetAssetList で対象アセットリストを取得しています。
ChangeAsset で実際のアセットに対する変更処理をしています。

MyAssetChanger.cpp
#include "MyAssetChanger.h"
#include "AssetRegistryModule.h"
#include "FileHelpers.h"
#include "Components/Button.h"
#include "Components/CheckBox.h"

// 開始前処理
void UMyAssetChanger::NativePreConstruct()
{
    Super::NativePreConstruct();

    UButton* Button = Cast<UButton>(GetWidgetFromName("Button0"));
    if( ::IsValid(Button) && !Button->OnClicked.IsBound() ){
        Button->OnClicked.AddDynamic(this, &UMyAssetChanger::ClickedButton);
    }

    UCheckBox* CheckBox = Cast<UCheckBox>(GetWidgetFromName("CheckBox0"));
    if( ::IsValid(CheckBox) && !CheckBox->OnCheckStateChanged.IsBound() ){
        CheckBox->OnCheckStateChanged.AddDynamic(this, &UMyAssetChanger::ChangeCheckBox );
    }

}

// 対象アセットリストを取得する
void UMyAssetChanger::GetAssetList(TArray<FAssetData>& _AssetList) const
{
    FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
    IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();


    FARFilter Filter;
    // 対象パス(Contentフォルダ全体を対象にする)
    Filter.PackagePaths.Add(TEXT("/Game/"));
    Filter.bRecursivePaths = true;

    // 対象クラス指定
    Filter.ClassNames.Add(UTexture2D::StaticClass()->GetFName());

    AssetRegistry.GetAssets(Filter, _AssetList);

}

// アセット書き換え
bool UMyAssetChanger::ChangeAsset(FAssetData&   _AssetData)
{

    if (UTexture2D* _Texture = Cast<UTexture2D>(_AssetData.GetAsset())){
        // パラメータを変更する
        _Texture->AdjustBrightness = 1.5f;
        _Texture->AdjustSaturation = 0.9f;
        _Texture->AdjustRGBCurve = 2.0f;

        // 編集フラグ設定
        _Texture->MarkPackageDirty();

        return(true);
    }

    return(false);
}

// チェックボックスイベント
void UMyAssetChanger::ChangeCheckBox(bool bChange)
{   
    bIsSave = bChange;
}

// ボタンクリックイベント
void UMyAssetChanger::ClickedButton()
{
    TArray<FAssetData>  _AssetList;
    TArray<UPackage*>   _PackagesToSave;

    // アセットリストの取得
    GetAssetList(_AssetList);

    // アセット変更
    for(auto _AssetData : _AssetList){
        if( ChangeAsset(_AssetData) ){
            // 変更したパッケージ追加
            _PackagesToSave.Add(_AssetData.GetPackage());
        }
    }

    if( bIsSave ){
        // 編集ファイルを保存
        FEditorFileUtils::PromptForCheckoutAndSave( _PackagesToSave, true, true );
    }

}

ファイルの保存には FEditorFileUtils を使用しています。
PromptForCheckoutAndSave の第2引数はtrue時に編集フラグを立てたものだけセーブします、第3引数はtrue時に確認ウィンドウが表示されます。

FileHelpers.h
static EPromptReturnCode PromptForCheckoutAndSave( const TArray<UPackage*>& PackagesToSave, bool bCheckDirty, bool bPromptToSave, TArray<UPackage*>* OutFailedPackages = NULL, bool bAlreadyCheckedOut = false, bool bCanBeDeclined = true );

BPコード:EditorUtilityWidget

コンテンツブラウザ右クリックから[Editor Utilities] -> [Editor Utility Widget]を作成。

create_EUW.jpg

作成したアセットを開いて、[ファイル] -> [ブループリントの親を変更] を選択し、先のC++クラスに差し替えます。

次にボタンとチェックボックスを用意したWidgetを作成する、イベントグラフは何もしない。

MyAssetChangerBP.jpg

実行結果

作成したEditorWidget を右クリック -> [Run Editor Utility Widget]を実行。
run_EUW.jpg

ボタンをクリックすればテクスチャからパラメータ変更処理がかかります。チェックボックスをONにすれば、セーブ処理を行います。

run_tool.jpg

テクスチャビューワでパラメータが変更されているのが確認できます。
tex_param.jpg

その他

その他アセット修正関連で使えそうな事項です。

外部アプリケーションを実行する

外部アプリケーション呼び出しには FPlatformProcess::ExecProcessFPlatformProcess::CreateProcess を使用することで可能です。

以下 svn.exe という外部ツールをコマンドライン実行する例です。

.cpp
#include "FileHelpers.h"
#include "Paths.h"

// 外部ツールパスをワークディレクトリから辿る
FString _ExePath = FPaths::ConvertRelativePathToFull( FPlatformProcess::GetCurrentWorkingDirectory().Append(FString(TEXT("\\..\\"))) ) + FString(TEXT("svn.exe"));

int32   _Code;
FString _Output;

// 実行
auto _Result = FPlatformProcess::ExecProcess(*_ExePath, *(FString(TEXT("--version")) ), &_Code, &_Output, nullptr);
if (!_Result){
    UE_LOG(LogTemp, Error, TEXT("Error!"));
    return;
}

// 出力結果
if (!_Output.IsEmpty()){
    UE_LOG(LogTemp, Log, TEXT("%s"), *_Output);
}

テキストファイル出力

テキストファイル出力でログ出力やアセット変換用のバッチファイルなどを作成することができます。
以下、アセットデータのパスを出力するコード例。

.cpp
#include "FileHelpers.h"
#include "Misc/FileHelper.h"
#include "Paths.h"

// アセットデータからパスを取得
FString _FilePath = _AssetData.ObjectPath.ToString();

FString _OutputFilePath = FPaths::ConvertRelativePathToFull(FPaths::GameSavedDir()) + TEXT("/MyLog.txt");
FString _FileContent = FString::Printf(TEXT("%s\n"), *_FilePath );

// ファイル出力
FFileHelper::SaveStringToFile(_FileContent, *_OutputFilePath, FFileHelper::EEncodingOptions::AutoDetect, &IFileManager::Get(), EFileWrite::FILEWRITE_None);

対象ディレクトリを絞り込む

コンテンツフォルダの名前を条件にしてみるコード例。

.cpp
IFileManager& _FileMgr = IFileManager::Get();

// "Tex"というフォルダを探す
TArray<FString> _DirNames;
_FileMgr.FindFilesRecursive(_DirNames, *FPaths::ProjectContentDir(), TEXT("Tex"), true, true);

// フィルター
FARFilter Filter;
Filter.bRecursivePaths = true;      // サブフォルダも検索

// 検索したディレクトリ名を書き換える
for(FString _Str : _DirNames){
    FString _LeftStr;
    FString _RightStr;
    _Str.Split(TEXT("Content"), &_LeftStr, &_RightStr);

    // "Content"から"Game"に置き換え
    FString _Dir(TEXT("/Game"));
    _Dir += _RightStr;

    // 対象パス追加
    Filter.PackagePaths.Add( *_Dir );
}

// 対象クラス指定
Filter.ClassNames.Add(UTexture2D::StaticClass()->GetFName());

まとめ

サンプル程度のパラメータ変更は
[アセットアクション] -> [プロパティマトリクスで一斉編集]
を使った方が早いでしょうが、対象アセットが複数フォルダに散っている場合や、条件が細かいケースなどは使えない(使いにくい)ため、そのような場合に有用かと思います。
更にコンボボックスやツリーなどUMGの機能を使えば、より柔軟な作りにできるかと思います。

6
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
8