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

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

UE4シーケンサーのメニューに項目を追加したい(2)

目標


UE4シーケンサーのエディター拡張を行い、メニューの項目を追加してシーケンサーに対して独自の処理を行うための入り口を作りたい。
※エンジン改造はしません。
今回はシーケンサーのトラックメニューに追加する方法になります。
具体的には以下の画像の箇所

f:id:naoxgames:20210508013755p:plain
トラックメニュー

前回の続きで対応するため、以下もご一読ください。
naoxgames.hatenablog.jp

環境

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

やってみる

作戦としては前回同様UI拡張点からコードを検索して、似た処理を真似てみます。
前回のプラグインに処理を追加して対応します。

UIの拡張点の表示

前回同様UIの拡張点の表示を行いましょう。
UE4エディターの[編集]→[エディタの環境設定]→[UIの拡張点の表示]にチェックを入れます。
該当箇所を確認すると

f:id:naoxgames:20210508015105p:plain
拡張点
「ObjectBindings」ということがわかりました。

エンジンから類似処理を探す

「ObjectBindings」でエンジンのコードを検索すると、SSequencer.cppSSequencer::GetContextMenuContentで使われていることがわかります。
FSequencer::BuildAddObjectBindingsMenuを見ると、ObjectBindings[i]->BuildSequencerAddMenuでメニューの登録がされています。
トラックメニューに表示されるにはこのObjectBindingsに登録されている必要があるようです。

ObjectBindingsに登録する処理を探すとFSequencer::InitSequencerObjectBindings.Addされているのが確認できます。
EditorObjectBindingDelegatesの要素数分登録されているので、FSequencer::InitSequencerの引数のEditorObjectBindingDelegatesがどこでどう設定されているか確認します。

FSequencer::InitSequencerを検索するとFSequencerModuleのCreateSequencerで呼び出しています。
EditorObjectBindingDelegatesに登録する処理はFSequencerModuleのRegisterEditorObjectBindingで行っています。


っということで、必要な情報は揃いました。

モジュール設定

SequenceExtensionEd.Build.csに以下を追加します。

~
        PublicDependencyModuleNames.AddRange(
            new string[]
            {
                ~
                "Sequencer",
                ~
            }
            );
~


シーケンサーモジュールに拡張メニュー追加

トラックメニューに追加する処理を作成します。

ObjectBindingExtension.h

#include "CoreMinimal.h"

#include "ISequencerEditorObjectBinding.h"
/**
 * 
 */
class ISequencer;
class AActor;

class FObjectBindingExtension : public ISequencerEditorObjectBinding
{
public:

    FObjectBindingExtension(TSharedRef<ISequencer> InSequencer);

    static TSharedRef<ISequencerEditorObjectBinding> OnCreateActorBinding(TSharedRef<ISequencer> inSequencer);

    // ISequencerEditorObjectBinding interface
    virtual void BuildSequencerAddMenu(FMenuBuilder& MenuBuilder) override;
    virtual bool SupportsSequence(UMovieSceneSequence* InSequence) const override;

private:

    void AddObjectBindingMenuExtension();

private:
    TWeakPtr<ISequencer> Sequencer;
};



ObjectBindingExtension.cpp

#include "ObjectBindingExtension.h"

#include "Misc/Guid.h"
#include "EditorStyleSet.h"
#include "LevelSequence.h"

#define LOCTEXT_NAMESPACE "FObjectBindingExtension"

FObjectBindingExtension::FObjectBindingExtension(TSharedRef<ISequencer> InSequencer)
    : Sequencer(InSequencer)
{ }

TSharedRef<ISequencerEditorObjectBinding> FObjectBindingExtension::OnCreateActorBinding(TSharedRef<ISequencer> inSequencer)
{
    return MakeShareable(new FObjectBindingExtension(inSequencer));
}

void FObjectBindingExtension::BuildSequencerAddMenu(FMenuBuilder& MenuBuilder)
{
    MenuBuilder.AddMenuEntry(
        LOCTEXT("ObjectBindingEx", "ObjectBindingEx"),
        LOCTEXT("ObjectBindingToolTip", "ObjectBindingToolTip"),
        FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.ColumnViewAssetIcon"),
        FUIAction(FExecuteAction::CreateRaw(this, &FObjectBindingExtension::AddObjectBindingMenuExtension)));
}

bool FObjectBindingExtension::SupportsSequence(UMovieSceneSequence* InSequence) const
{
    return InSequence->GetClass() == ULevelSequence::StaticClass();
}

void FObjectBindingExtension::AddObjectBindingMenuExtension()
{
    FText DialogText = FText::FromString("ObjectBindingMenuExtension");
    FMessageDialog::Open(EAppMsgType::Ok, DialogText);
}

#undef LOCTEXT_NAMESPACE



SequencerExtensionEd.h

~
private:
~
    FDelegateHandle ObjectBindingDelegateHandle;
~



SequencerExtensionEd.cpp

~
#include "ObjectBindingExtension.h"
~
void FSequencerExtensionEdModule::StartupModule()
{
~
    ObjectBindingDelegateHandle = SequencerModule.RegisterEditorObjectBinding(FOnCreateEditorObjectBinding::CreateStatic(&FObjectBindingExtension::OnCreateActorBinding));
~
}
~
void FSequencerExtensionEdModule::ShutdownModule()
{
~
        SequencerModule.UnRegisterEditorObjectBinding(ObjectBindingDelegateHandle);
~
}



実行!!!!

f:id:naoxgames:20210508031343p:plain
トラックメニュー
f:id:naoxgames:20210508031502p:plain
メニュー押したとき

シーケンサーのトラックメニューに項目が指定した名前で追加され、ダイアログが表示されました。


思ったこと

前回とは違った追加の仕方で、メニューごとに違うとやりにくいですね。
以前に書いた記事で、ISequencerを取得する対応を紹介しています。
naoxgames.hatenablog.jp これと組み合わせることでメニューからできることの幅が広がると思います。


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

この記事の続き naoxgames.hatenablog.jp

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


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

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


UE4レベル移動時に特定のPlayerStartから開始する方法

エディターでの作業時やどのレベルから移動してきたかに応じてレベルの開始位置を指定したい場合があると思います。
特に設定をしていないPlayerStartを複数配置したレベルに移動すると、どのPlayerStartから始まるかは実行のたびに変わります。

じゃあどうしたらいいんですか!!!


目標


レベルに移動時、どのPlayerStartから開始するかを指定できる方法を模索する。


環境

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


結論

いきなり結論ですが、既存のエンジンの仕組みで対応が可能です。
OpenLevelノードの「LevelName」に「[レベルのパス]#[対象のPlayerStartのPlayerStartTag]」と指定することで実現できます。

f:id:naoxgames:20210320015240p:plain
OpenLevel

手順

例として、Contentフォルダ直下に配置された「Test」というレベルに、PlayerStartが2つあるとします。

まずは配置したPlayerStartの詳細から以下にある「PlayerStartTag」に識別する名前を設定します。配置したものすべて違うものにしましょう。
ここでは「PlayerStart1」「PlayerStart2」と設定します。

f:id:naoxgames:20210320014242p:plain
PlayerStartTag

次にOpenLevelノードを配置し、「LevelName」に設定をします。

[レベルのパス]#[対象のPlayerStartのPlayerStartTag]

・レベルのパス → Contentフォルダを/Game/に置き換えたファイルのパスです。例では「/Game/Test」となります。※Test.TestとかTest.umapとかではない。

・対象のPlayerStartのPlayerStartTag → 配置されたPlayerStartに設定した「PlayerStartTag」の文字列。

f:id:naoxgames:20210320020010p:plain
OpenLevel


この設定でレベル移動してみると想定したPlayerStartから開始できるのがわかります。

調べる

処理の流れを確認してみます。
やはりエンジンコードを追うしかありませんね。。。

「OpenLevel」を探すと「GameplayStatics.cpp」にたどり着きます。
「UGameplayStatics::OpenLevel」の中を追うと「LevelName」と「Options」をコマンドとして「GEngine->SetClientTravel」に入れてます。
さらに中に行くと「Context.TravelURL」にコマンドを入れて処理が終わってます。どうなっている!?

「TravelURL」で検索すると「UEngine::TickWorldTravel」で使われている様子。
さらに追うと「UEngine::Browse」に「FURL」に変換して使っている様子。
その後「UEngine::LoadMap」→「ULocalPlayer::SpawnPlayActor」→「UWorld::SpawnPlayActor」→「AGameModeBase::Login」→「AGameModeBase::InitNewPlayer」と来て、やっと核心に迫ります。(ここまでは、それっぽいところにブレイク張って頑張って追いかけましょう。)

たどり着いたのは「AGameModeBase::FindPlayerStart」です。
ここでPlayerStartを探しています。その手掛かりに使っているのが「FURL」のメンバ「Portal」です。

ではこの「Portal」はどうやって取得しているのか、検索します。
すると「FURL::FURL」で取得しており、処理の流れを見ると、「#」の後に指定したものが「Portal」に設定されるようです。


っということで、処理の流れを追いかけて、なぜ#[PlayerStartTag]でこの挙動になるのかがわかりましたね!!
この挙動について、ネットで検索をいろいろしてみましたが驚くほど情報が出てこず、なかなか使わないのか、この方法は非推奨なのか、具体的なことはわかりませんが、とりあえずはコードを見た上でこの対応で問題ないと思われます。



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

UE4マテリアルで体の一部から消えていく表現を作りたい

目標


キャラクターのかっこいい消え方を考える。
UE4のマテリアルでGANTZのような体の一部からじわじわ消えていく表現を作りたい。
GANTZ風となると、上から下へ消えていくのではなく、任意の部位から、さらに複数個所同時に消え始めるようにしたい。


環境

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

やってみる

作戦としては、
・対象のモデルの足元から頭の先までを0~1で取得できるようにする。
・どの部分から消え始めるのかを指定するパラメーター用画像を作って挙動を作る(今回はCurveAtlasを使います)。
・時間経過で0~1の値を与えると徐々に消えていくようにする。
という感じでしょうか。
やってみましょう。


マテリアルを作る

まずはコンテンツブラウザ上でマテリアルを作成します。
コンテンツブラウザ上で右クリック→メニューから「マテリアル」を選択してマテリアルを作成します。

f:id:naoxgames:20210223003147p:plain
マテリアル作成

足元から頭の先までを0~1で取得できるようにする

体の位置を0~1で取得できるようにします。
イメージとしては以下のような情報が欲しいわけです。

f:id:naoxgames:20210223011110p:plain
体の位置情報

この対応、デザイナーにお願いすれば一発だったりします。
上記のような画像がある場合はそれで問題ないです。
私はなんとかして上記の情報を取得しようと思います。

マテリアルで以下のような処理にして、BP側から値を渡す作戦にします。
f:id:naoxgames:20210223010138p:plain
体の位置取得

BPは「Character」を継承して「SkeletalMeshComponent」からマテリアルを取得して「CreateDynamicMaterialInstance」をして各パラメーターを設定しています。
f:id:naoxgames:20210223010509p:plain
体の位置取得BP
これで取得できました。
※ActorPositionを使用せずわざわざパラメーターにしているのは、アクターの座標が必ず足元というわけではないため指定するようにしています。

消え方のパラメーター作成

消え方を指定するパラメーターを作成します。
イメージとしては以下のようなものになります。

f:id:naoxgames:20210223012458p:plain
消え方情報
黒いところから白いところにむかって消えていくという情報になります。上記の場合は胸あたりと膝あたりからそれぞれ上下に消え始めていくパラメーターになります。
これもデザイナーが用意してくれることもありそうですが、今回は自分で用意します。

以下のように、コンテンツブラウザ上で右クリック→メニューから「その他」→「カーブ」と「カーブアトラス」を作成します。
f:id:naoxgames:20210223011753p:plain
カーブアトラス作成

カーブを作成する際は「CurveLinearColor」を選択します。
f:id:naoxgames:20210223012045p:plain
カーブ選択

カーブを開き、Rの線を以下のように設定します。
f:id:naoxgames:20210223014321p:plain
カーブ設定
G,B,Aの値は今回は使用しません。

カーブアトラスを開き、「詳細」タブの「カーブ」の項目にある「GradientCurve」の「+」ボタンを選択して先ほど作成したカーブを指定します。
f:id:naoxgames:20210223013133p:plain
カーブアトラス設定

これで消え方の設定は完了です。

消える挙動を作成

基本的にはカーブに設定した黒いところから消え始めて白いところが後から消えていくという処理になります。
以下のように対応してみました。

f:id:naoxgames:20210223205730p:plain
消える挙動
赤く囲った箇所について説明していきます。

オパシティマスクを使って非表示にするため、詳細から「BlendMode」を「Masked」にします。

カーブアトラスを使うには「CurveAtlasRowParameter」と打つと、上記の「Param」のようなノードが作成されます。
ノードの詳細設定は以下のように行います。
今回はRに値を設定したため、赤いピンを使います。
f:id:naoxgames:20210223031342p:plain
CurveAtlasRowParameter

カーブから取得した値は「0~1」のため、「0」の部分も始めは「1」以上であってほしいので「1」を足しています。
この値が「1」未満になったときにマスクする対象とします。

時間経過が「1」のときにすべての値が「1」未満になってほしいため、「0.001」という小さな値を減算しておきます。(無理矢理!)
そうすることで全体が「0.999~1.999」の値になり、時間が経過すると「カーブの値(0.999~1.999) - 経過時間(0~1)」となります。

そのままだと時間経過「0」の段階で「0.999」の部分がマスクされてしまうため、時間経過が「0」のときはマスクをしないようにします。(無理矢理!!)

※ベースカラーはひっそりと黒を指定しています。GANTZだし。

これでマテリアル側の基本的な挙動は対応できました。
BP側でこれのTimeParamに0~1の値を設定することで動作が確認できます。 動作を確認してみましょう。
f:id:naoxgames:20210223032933p:plain
消える処理
挙動は問題なさそうです。

GANTZっぽくする

ここまでの対応だけではGANTZっぽさが弱いように感じます。
消え始める部分に色を付けることでもう少し表現を近づけてみます。
以下のように対応します。

f:id:naoxgames:20210223211715p:plain
GANTZ風処理

赤く囲った箇所が追加した処理です。

2つの値(上記画像の左下のほうにある「1.02」と「1」)で指定した範囲内の場合、エミッシブカラーに青色を指定していて、消え始めているところから離れるほど光が弱くなっている処理になります。
これにより、消え始める一定範囲は青く光るような表現になります。
f:id:naoxgames:20210223212333p:plain
GANTZっぽくなったかも
先ほどよりもGANTZっぽい表現ができているように感じます。
f:id:naoxgames:20210321021618g:plain
GATNTZっぽい
f:id:naoxgames:20210223212156p:plain
光の具合
消え始めるところと離れているところで色が若干違うのも確認できます。


思ったこと

意外といい感じにできているように見えますが課題もあります。
断面の表現やマスクしている箇所にエフェクトを出したい場合などの対応はマテリアルで対応すると難しいと思われます。
マスク対象のサイズによって調整する必要があったりもします。
いい方法が思いついたらまた上げてみようと思います。


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


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

UE4シーケンサーのメニューに項目を追加したい(1)

目標


UE4シーケンサーのエディター拡張を行い、メニューの項目を追加してシーケンサーに対して独自の処理を行うための入り口を作りたい。
※エンジン改造はしません。
今回はシーケンサーツールバーにメニューを追加する方法になります。
具体的には以下の画像の箇所

f:id:naoxgames:20210214181503p:plain
シーケンサーツールバー


環境

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

やってみる

作戦としてはシーケンサーツールバー部分のコードを参考に、似た処理を行うプラグインを作成していきます。

UIの拡張点の表示

まずはエディター拡張する部分の拡張点を知る必要があります。
UE4エディターの[編集]→[エディタの環境設定]をクリックし、以下の画像のように[UIの拡張点の表示]にチェックを入れます。

f:id:naoxgames:20210214184933p:plain
UIの拡張点の表示

その後エディターを再起動します。
すると、エディターの各所に緑色の文字が表示されるようになっていると思います。
今回対応したいシーケンサーツールバーでは以下のように表示されています。
f:id:naoxgames:20210214185901p:plain
ツールバー拡張点

「Level Sequence Separator」ということがわかりました。

エンジンから類似処理を探す

「Level Sequence Separator」でエンジンのコードを検索すると、SSequencer.cppで使われていることがわかります。
その直後に処理されているものが名前からしシーケンサーツールバーメニューのようですので、そのあたりを参考に対応してみます。(正確に確認したい場合はエンジンでこの部分を変更してみると変化が確認できます。)
※エンジン改造で問題ない場合はこのまま他のメニュー処理と同様の処理を記述することで対応できます。

プラグインでメニュー追加

今回はエンジン改造ではなくプラグインで対応をしてみます。
UE4エディターの[編集]→[プラグイン]→[新しいプラグイン]を選択してプラグインを作成します。
ひとまず今回の用途に近い「エディタツールバーのボタン」を元にして作成してみます。
当記事で説明するプラグイン名は「SequenceExtensionEd」とします。

モジュール設定

SequenceExtensionEd.Build.csに以下を追加します。
・Sequencer・・・・ISequencerModuleへのアクセス
・EditorStyle・・・・シーケンサーで使われている既存のアイコン使用のため(不要であれば削除)

~
        PrivateDependencyModuleNames.AddRange(
            new string[]
            {
                ~
                "Sequencer",
                "EditorStyle",
            }
            );
~


シーケンサーモジュールに拡張メニュー追加

エンジンから類似処理を探した際に処理を行っていたSSequencer::MakeToolBar()を見てみると、SequencerModuleを取得し、SequencerModule.GetToolBarExtensibilityManager()->GetAllExtenders()という処理で取得したものをツールバーに追加しています。
プラグインからも同様にSequencerModuleを取得してみると、GetToolBarExtensibilityManager()->AddExtenderという処理があります。ここに処理を追加できればツールバーに登録してくれそうです。

以下のようにStartupModuleでSequencerModuleを取得し、FExtenderのAddToolBarExtensionに追加するメニューを設定します。
今回は追加したメニューを選択したときにダイアログを表示する処理にしてみます。

SequenceExtensionEd.cpp

~
#include "Sequencer/Public/ISequencerModule.h"
~
void FSequencerExtensionEdModule::StartupModule()
{
    ~
    TSharedPtr<FUICommandList> ToolbarCommands = MakeShareable(new FUICommandList);
    ISequencerModule& SequencerModule = FModuleManager::GetModuleChecked<ISequencerModule>("Sequencer");
    ToolBarExtender = MakeShareable(new FExtender);
    ToolBarExtender->AddToolBarExtension(
        "Level Sequence Separator",
        EExtensionHook::After,
        ToolbarCommands,
        FToolBarExtensionDelegate::CreateRaw(this, &FSequencerExtensionEdModule::AddToolBarExtention)
    );
    SequencerModule.GetToolBarExtensibilityManager()->AddExtender(ToolBarExtender);
    ~
}

void FSequencerExtensionEdModule::AddToolBarExtention(FToolBarBuilder& ToolBarBuilder)
{
    ToolBarBuilder.AddComboButton(
        FUIAction(),
        FOnGetContent::CreateRaw(this, &FSequencerExtensionEdModule::MakeToolbarExtensionMenu),
        LOCTEXT("SequencerExtension", "ToolBarExtension"),
        LOCTEXT("SequencerExtension", "ToolBarExtension"),
        FSlateIcon(FEditorStyle::GetStyleSetName(), "Sequencer.Actions"),   //仮でアクションと同じアイコン
        false);
}

TSharedRef<class SWidget> FSequencerExtensionEdModule::MakeToolbarExtensionMenu()
{
    FMenuBuilder MenuBuilder(true, MakeShareable(new FUICommandList));
    MenuBuilder.BeginSection("SequencerToolBarExtension");

    MenuBuilder.AddMenuEntry(
        LOCTEXT("SequencerExtension", "ToolBarMenu"),
        LOCTEXT("SequencerExtension", "ToolBarMenu"),
        FSlateIcon(FEditorStyle::GetStyleSetName(), "Sequencer.Actions"),
        FUIAction(FExecuteAction::CreateLambda(
            []()
            {
                FText DialogText = FText::FromString("ToolBarMenuExtension");
                FMessageDialog::Open(EAppMsgType::Ok, DialogText);  //メニューを選択したらダイアログを表示
            }
    )));

    MenuBuilder.EndSection();

    return MenuBuilder.MakeWidget();
}

SequenceExtensionEd.h

    void AddToolBarExtention(FToolBarBuilder& ToolBarBuilder);
    TSharedRef<class SWidget> MakeToolbarExtensionMenu();

    TSharedPtr<class FExtender> ToolBarExtender;


実行!!!!

f:id:naoxgames:20210214210134p:plain
拡張メニュー

f:id:naoxgames:20210214210029p:plain
ダイアログ

シーケンサーツールバーに項目が指定した名前・アイコンで追加され、ダイアログが表示されました。

思ったこと

UE4案件はなんだかエンジン改造しないとダメ的な空気感が強い印象がありますが、やりようによってはプラグインで吸収できる箇所もそこそこありそうですね。(どうしようもないことも多々ありますが!!!)
エディター拡張をする分には、エンジンのコードを参考にすればなんとかなるものが多いと思われます。


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

この記事の続き
naoxgames.hatenablog.jp

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