エンジニアっぽくなりたい

UnityやUnrealEngine 4などでなにか役に立ちそうなことを発信していく。

UE4 プロジェクト内のアセットを取得したい

エディターで何かしら機能を作っているときに、プロジェクトのアセットを取得したいことってありますよね。
ピンポイントにファイルを取得するというよりは、フォルダ指定とかしてその中の特定の種類のアセットを取得するような場合の話です。 そんな時に便利な奴がいるらしいのでちょっと使ってみようと思います。

目標


エディター上から特定のアセットを取得する。
また、その使い道を考える。

環境

・UE 4.26.1 (エンジンビルドはしていません)
・VisualStudio 2019 16.7.2


やってみる

今回の主な登場人物は「AssetRegistry」です。
基本的にAssetRegistryにお願いしてゲットする感じでいきます。

クラスから取得する場合

まずは簡単に使えるクラスを指定する取得方法について記述します。
例としてBlueprintを取得してダイアログで表示する処理を試します。

~
#include "AssetRegistryModule.h"
~
    FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
    IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();

    TArray<FAssetData> AssetDatas;
    AssetRegistry.GetAssetsByClass(UBlueprint::StaticClass()->GetFName(), AssetDatas);

    FString text = TEXT("");
    for (int i=0;i<AssetDatas.Num();i++)
    {
        text += AssetDatas[i].AssetName.ToString();
        text += TEXT("\n");
    }

    FText DialogText = FText::FromString(text);

    FMessageDialog::Open(EAppMsgType::Ok, DialogText);
~

こうすると次の画像のようになる

f:id:naoxgames:20210501025011p:plain
Blueprintたち

こんな感じで取れました。

フィルターして取得する場合

FARFilterを使ってフィルターした状態で取得する方法です。 例として「StarterContent/Blueprints」内のBlueprintをダイアログで表示する処理を試します。

~
#include "AssetRegistryModule.h"
~
    FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
    IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();

    FARFilter Filter;
    Filter.ClassNames.Add(FName("Blueprint"));
    Filter.PackagePaths.Add(FName("/Game/StarterContent/Blueprints"));

    TArray<FAssetData> AssetDatas;
    AssetRegistry.GetAssets(Filter, AssetDatas);

    FString text = TEXT("");
    for (int i=0;i<AssetDatas.Num();i++)
    {
        text += AssetDatas[i].AssetName.ToString();
        text += TEXT("\n");
    }

    FText DialogText = FText::FromString(text);

    FMessageDialog::Open(EAppMsgType::Ok, DialogText);

~

こうすると次の画像のようになる

f:id:naoxgames:20210501034011p:plain
Blueprintたち2
こんな感じで取れました。


使い道を考える

ここまではもはやUnrealEngineのドキュメントに載っているものなので大したことではないです。
じゃあこれを使ってどんなことをやれそうか考えてみます。
私は業務でシーケンサー触ることが多いので、今回はシーケンサーで考えてみます。
※より詳しいシーケンサーでの扱いは別の投稿で行います。今回はAssetRegistryとシーケンサーをどう絡ませるかの入り口程度で考えてください。

ISequencerを取得

ISequencerを取得できると、現在開いているシーケンサーの設定状態を取得できるので、UMovieSceneを取得すれば設定状態の取得や書き換えも簡単にできるようになります。

~
    FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
    IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();

    TArray<FAssetData> AssetDatas;
    AssetRegistry.GetAssetsByClass(ULevelSequence::StaticClass()->GetFName(), AssetDatas);

    for (FAssetData AssetData : AssetDatas)
    {
        ULevelSequence* LevelSequence = Cast<ULevelSequence>(AssetData.GetAsset());

        UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
        IAssetEditorInstance* AssetEditor = AssetEditorSubsystem->FindEditorForAsset(LevelSequence, true);
        FLevelSequenceEditorToolkit* LevelSequenceEditor = (FLevelSequenceEditorToolkit*)AssetEditor;
        if (LevelSequenceEditor != nullptr)
        {
            ISequencer* Sequencer = LevelSequenceEditor->GetSequencer().Get();
            return Sequencer;
        }
    }

    return nullptr;
~


シーケンサーを開いた状態で上記の処理を行うとISequencerを取得できます。

LevelSequenceチェック・一括編集機能

ULevelSequenceを取得できると、そこに設定されている子シーケンサーにアクセスできます。
ObjectBindingやTrackの情報を取得できるので、Jenkinsとかで定期的におかしな値が入ってないか検出したり、必要に応じてトラックの情報を書き換えたり、などで活用できそうです。

~
    FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
    IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();

    TArray<FAssetData> AssetDatas;
    AssetRegistry.GetAssetsByClass(ULevelSequence::StaticClass()->GetFName(), AssetDatas);

    for (FAssetData AssetData : AssetDatas)
    {
        ULevelSequence* LevelSequence = Cast<ULevelSequence>(AssetData.GetAsset());
        UMovieScene* MovieScene = LevelSequence->GetMovieScene();
        for (int i = 0; i < MovieScene->GetSpawnableCount(); i++)
        {
            FMovieSceneSpawnable& Spawnable = MovieScene->GetSpawnable(i);
            FMovieSceneBinding* Binding = MovieScene->FindBinding(Spawnable.GetGuid());
            Binding->GetTracks();
            //~~~なんかする~~~
        }
    }
~


こうすることでObjectBindigにアクセスして内容を編集したりできます。

じゃあどういうときに使うのか?
パッと思いつく例としては

キャラのアニメーションのスロット名を変更したくなったときを考えます。
シーケンサーでアニメーションのトラックにスロット名を指定しているため関連するすべてのシーケンサーを編集する必要があります。
これを今回の対応を行うことで、
 すべてのシーケンサーを取得し
 関係するスケルタルメッシュを使っているObjectBindingを探し
 Animationトラックがついている場合に、スロット名を変更する
という対応を一括で行うことができますね。

これは意外と便利なのでは??


思ったこと

もっと便利な使い道があるかもしれませんが、ひとまず自分が思いつくシーケンサー絡みで使ってみました。
他にももっと有用な使い方があると思うので、思いつき次第また書くかもしれません。
ちなみにこの処理って半端ない数のシーケンサーがあるプロジェクトだとすごく重いのかな?どうなんだろう。


ここまでの内容は以下のリポジトリに上げてあります。 github.com

正しい対応方法やより良い方法があればご指摘お願いいたします!!!