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

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

UE4シーケンサーのメニューに項目を追加したい(3)~Object Binding Context Menu~

シーケンサー上の動的置き換えに行く前にObjectBindingのメニューに項目を追加していじれる対応をします。

目標


シーケンサーのエディター拡張を行い、ObjectBindingのメニューに項目を追加したい。
今回はSpawnable設定のObjectBindingに対して、「FMovieSceneSpawnable」構造体の詳細を表示するのを目標とします。
また、対応は引き続きPluginで行います。
メニューを追加するのはこの辺
f:id:naoxgames:20211130224247p:plain
※エンジン改造はしません。

前回の続きで対応するため、以下もご一読ください。
naoxgames.hatenablog.jp
naoxgames.hatenablog.jp
一連の対応を上げているGitリポジトリです。
この記事のコードはすべてこのリポジトリに追加で進めています。
github.com

環境

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

やってみる

作戦としては基本的にはいつも通り、拡張点からエンジンソースを検索して似たような処理を行います。

UIの拡張点の表示

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

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


その後エディターを再起動します。
すると、エディターの各所に緑色の文字が表示されるようになっていると思います。
今回対応したいObjectBindingのメニューでは以下のように表示されています。
f:id:naoxgames:20211130225242p:plain

「Spawnable」で探してみます。


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

「Spawnable」でエンジンのコードを検索すると、「SequencerObjectBindingNode.cpp」の「FSequencerObjectBindingNode::BuildContextMenu」で使われていることがわかります。
ここの処理にある「SequencerModule.GetObjectBindingContextMenuExtensibilityManager()」から「Extenders」を取得して「MenuBuilder」に登録しています。
ということで、ここに登録すればよさそうな雰囲気です。


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

以下のように対応します。
まずは登録するメニューの中身の処理を作ります。

SequenceExtensionEd.h

~
private:
~
    void AddObjectBindingContextMenuExtention(FMenuBuilder& MenuBuilder);
    void AddSpawnablePropertyMenu(FMenuBuilder& MenuBuilder, UMovieScene* MovieScene, ISequencer* Sequencer);
~
private:
~
    FGuid SelectedObjectBinding;
~

SequenceExtensionEd.cpp

~

//構造体の詳細表示
#include "IStructureDetailsView.h"

~

void FSequencerExtensionEdModule::AddObjectBindingContextMenuExtention(FMenuBuilder& MenuBuilder)
{
    ISequencer* OpenSequencer = GetCurrentSequencer();
    if (OpenSequencer == nullptr)
    {
        return;
    }
    SelectedObjectBinding = FGuid();
    TArray<FGuid> guids;
    OpenSequencer->GetSelectedObjects(guids);
    if (guids.Num() == 1)
    {
        SelectedObjectBinding = guids[0];
    }

    if (!SelectedObjectBinding.IsValid())
    {
        return;
    }

    UMovieScene* MovieScene = OpenSequencer->GetFocusedMovieSceneSequence()->GetMovieScene();
    FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(SelectedObjectBinding);
    if (!Spawnable)
    {
        return;
    }
    MenuBuilder.BeginSection("OverrideObjectBinding", FText::FromString("OverrideObjectBinding"));

    AddSpawnablePropertyMenu(MenuBuilder, MovieScene, OpenSequencer);

    MenuBuilder.EndSection();
}


void FSequencerExtensionEdModule::AddSpawnablePropertyMenu(FMenuBuilder& MenuBuilder, UMovieScene* MovieScene, ISequencer* Sequencer)
{

    FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(SelectedObjectBinding);

    auto PopulateSubMenu = [this, Spawnable](FMenuBuilder& SubMenuBuilder)
    {
        FPropertyEditorModule& PropertyEditor = FModuleManager::Get().LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");

        FDetailsViewArgs DetailsViewArgs(false, false, false, FDetailsViewArgs::HideNameArea, true);
        DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic;
        DetailsViewArgs.bShowOptions = false;
        DetailsViewArgs.ColumnWidth = 0.55f;

        FStructOnScope* StructOnScope = new FStructOnScope(FMovieSceneSpawnable::StaticStruct(), (uint8*)Spawnable);
        TSharedRef<IStructureDetailsView> StructureDetailsView = PropertyEditor.CreateStructureDetailView(
            DetailsViewArgs,
            FStructureDetailsViewArgs(),
            MakeShareable(StructOnScope)
        );
        
        TSharedRef< SWidget > DetailsViewWidget = StructureDetailsView->GetWidget().ToSharedRef();

        SubMenuBuilder.AddWidget(DetailsViewWidget, FText(), true, false);
    };

    MenuBuilder.AddSubMenu(FText::FromString("SpawnableProperty"), FText(), FNewMenuDelegate::CreateLambda(PopulateSubMenu));
}

~


「IStructureDetailsView」を使って構造体の詳細表示をしています。
エンジンソースを検索すると使っている箇所がいくつかあるので参考にしてみました。(わりと丸コピな部分があります。)
※セクション名が「OverrideObjectBinding」になっているのは次にやろうとしてることに合わせた名前にしてしまいました、気にしないでください。

上記で選択したObjectBindingの「FMovieSceneSpawnable」の詳細を表示する処理ができました。
次は登録を行います。

SequenceExtensionEd.h

~
private:
~
    TSharedPtr<FExtender> ObjectBindingContextMenuExtender;
~

SequenceExtensionEd.cpp

~
void FSequencerExtensionEdModule::StartupModule()
{
~
    //ObjectBindingContext
    ObjectBindingContextMenuExtender = MakeShareable(new FExtender);
    ObjectBindingContextMenuExtender->AddMenuExtension(
        "Spawnable",
        EExtensionHook::After,
        PluginCommands,
        FNewMenuDelegate::CreateRaw(this, &FSequencerExtensionEdModule::AddObjectBindingContextMenuExtention)
    );
    SequencerModule.GetObjectBindingContextMenuExtensibilityManager()->AddExtender(ObjectBindingContextMenuExtender);
}

void FSequencerExtensionEdModule::ShutdownModule()
{
~
    SequencerModule.GetObjectBindingContextMenuExtensibilityManager()->RemoveExtender(ObjectBindingContextMenuExtender);
~
}


これで準備は完了です。

実行!!!!


f:id:naoxgames:20211130231751p:plain
ObjectBindingのメニューに追加されました。
ちゃんと「FMovieSceneSpawnable」の内容も表示されていて編集もできます。


思ったこと

これがあればエンジン改造で「FMovieSceneSpawnable」になにか追加してそれをいじったりすることも、その内容をランタイムの何かの手がかりに使うこともできて便利な妄想しかできない。


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

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