LoginSignup
0
1

More than 3 years have passed since last update.

【UE4】プラグインをテンプレートから作る場合の留意事項(その4)

Last updated at Posted at 2020-04-07

【UE4】プラグインをテンプレートから作る場合の留意事項(その4)

■はじめに

●この記事は何?

【UE4】プラグインをテンプレートから作る場合の留意事項(その2)の続きです。
その3は独立してるので、その2であってます。)
上記の記事で扱わなかった、以下の点の対処方法について纏めたものです。

  • モジュールアンロード後、再びロードしても「Window」メニューに項目が戻らない

●対処方法?

結論から書いておきます。

  • エディタコードを編集して、メニューの再構築関数を実装する
  • 必要なタイミングで呼び出す

●環境

ツール名 バージョン
UE4 4.24.2
Visual Studio Community 2017
  • 記事内の UE4 Editor のメニュー項目名は言語設定が English 状態の内容となっています。
  • 必要であれば言語設定を変更しておいてください。
    • 「エディタの環境設定 > 国際化 > 言語設定」を「English」に変更

●想定している読者

■対処方法

●行うこと

  • クラス ILevelEditor
    • メニューの再構築関数 RegenerateMenu() を純粋仮想で宣言
  • 上記の派生クラス SLevelEditor
    • RegenerateMenu() をオーバーライド宣言
    • メニューの配置先の SBox のウィークポインタ MenuWidgetBox を定義
    • 初期化処理 Initialize() を編集し、メニュー構築時に MenuWidgetBox を初期化するように編集
    • RegenerateMenu() を実装

●具体的なコード

外部モジュールからアクセス可能な、レベルエディタの基底クラスである ILevelEditor に、
publicRegenerateMenu() を純粋仮想で宣言します。

ILevelEditor.h
//省略...
class ILevelEditor : public SCompoundWidget, public IToolkitHost
{
//省略...
public:
    virtual void RegenerateMenu() = 0; // これを追加
};
//省略...

外部モジュールからアクセス不可能な、レベルエディタの実装クラスである SLevelEditor に、
publicRegenerateMenu() をオーバーライドで宣言します。
また、private でメンバ変数 MenuWidgetBox を定義します。

SLevelEditor.h
//省略...
class SLevelEditor
    : public ILevelEditor
{
//省略...
    virtual void RegenerateMenu() override; // これを追加
//省略...
private:
    TWeakPtr<SBox> MenuWidgetBox; // これを追加
//省略...
};
//省略...

初期化処理 Initialize() を編集し、メニュー構築時に MenuWidgetBox を初期化するように編集

SLevelEditor.cpp
//省略...
void SLevelEditor::Initialize( const TSharedRef<SDockTab>& OwnerTab, const TSharedRef<SWindow>& OwnerWindow )
{
//省略...
    TSharedRef<SWidget> Widget1 = FLevelEditorMenu::MakeLevelEditorMenu(LevelEditorCommands, SharedThis(this));

    ChildSlot
    [
        SNew( SVerticalBox )
        +SVerticalBox::Slot()
        .AutoHeight()
        [
            SNew( SOverlay )

            +SOverlay::Slot()
            [
//              SNew(SBox)  // ここを変更
                SAssignNew(MenuWidgetBox, SBox) // これを追加
                .AddMetaData<FTagMetaData>(FTagMetaData(TEXT("MainMenu")))
                [
                    Widget1
                ]
            ]
//省略...
}
//省略...

Technical Note:このあたりの仕組みに関して
ILevelEditorSCompoundWidget の派生クラスですので、SLevelEditor 自体がウィジェットの一種となっています。
SLevelEditor::Initialize() にて、 ChildSlot から始まるスレートの構成コードが含まれています。
必要な部分だけ読み解くと以下のようなツリー構成になっています。

SVerticalBox
    └─SOverlay
        └─SOverlay
            └─SBox  MenuWidgetBox でウィークポインタを保存
                └─Widget1   FLevelEditorMenu::MakeLevelEditorMenu() で構築されたメニュー本体

SBox の子は SBox::SetContext() を使うことで差し替えられます。
その為、 MenuWidgetBox で参照先を保持しておき、必要なタイミングで差し替えられようにしています。

SLevelEditor::RegenerateMenu() を実装します。

SLevelEditor.cpp
//省略...
void SLevelEditor::RegenerateMenu()
{
    if (LevelEditorCommands && MenuWidgetBox.IsValid())
    {
        if (auto MenuWidgetBoxPtr = MenuWidgetBox.Pin())
        {
            TSharedRef<SWidget> Widget1 = FLevelEditorMenu::MakeLevelEditorMenu(LevelEditorCommands, SharedThis(this));
            MenuWidgetBoxPtr->SetContent(Widget1);
        }
    }
}
//省略...

Technical Note:エディタコードの編集が必要だった理由
メニューを構築するための関数 FLevelEditorMenu::MakeLevelEditorMenu() が外部モジュールからアクセスできないためです。
それ以外の部分に関してはなんとかアクセスする方法があるため、上記関数だけアクセス経路を用意すれば、それ以外はモジュール側で書くことも可能です。
ただ、それをしてしまうとエディタの内部実装に近い部分をモジュール側に置くことになるため、機能の分け方としてスッキリしません。
いずれにせよエディタ側のコードを編集する必要があるのは変わらないため、今回はエディタコード側で実装しています。

エディタ側のコードの変更は以上です。
ビルドの方法等は割愛します。

用意した関数 RegenerateMenu() を必要なタイミングで呼び出すようにします。
RefreshLevelEditorToolBar()RefreshLevelEditorMenuAndToolBar() と改め、
StartupModule()ShutdownModule() の中で呼び出せばよいでしょう。

SLevelEditor.cpp
//省略...
void RefreshLevelEditorMenuAndToolBar()
{
//省略...
    //ここから追加
    auto LevelEditorWeak = LevelEditorModule.GetLevelEditorInstance();
    if (LevelEditorWeak.IsValid())
    {
        if (auto LevelEditor = LevelEditorWeak.Pin())
        {
            LevelEditor->RegenerateMenu();
        }
    }
    //ここまで追加
}
//省略...
void FESWSampleModule::StartupModule()
{
//省略...
    RefreshLevelEditorMenuAndToolBar(); //これを追加
}
//省略...
void FESWSampleModule::ShutdownModule()
{
//省略...
    RefreshLevelEditorMenuAndToolBar(); //これを追加
    FESWSampleStyle::Shutdown();
//省略...
}
//省略...

以上で、モジュールアンロード後、再びロードした際「Window」メニューに項目が戻るようになります。

■結果

モジュールのアンロードとロードを繰り返し行った際にメニューやツールバーが再構築されるようになったので、
プラグイン作成中にメニュー項目やツールバー上のボタンの調整がやりやすくなりました。

■あとがき

その1からその4までの対応を行うことで、モジュール開発中の、ロードとアンロードした際のモヤモヤする部分が解消できました。
ここまでやっておけば、本来作ろうとしていたモジュール実装に専念できるでしょう。

ここまで読んでいただきありがとうございました!

■もう少し突っ込んだ話

その1の時に挙がっていた、「登録したデリゲート残りっぱなし」の件について、
ここまでの対応をした結果どうなっているのでしょうか?改善しているのでしょうか?
また、それ以外にも、モジュール側で確保したリソースが残っていたりしないのでしょうか?

実際問題、どちらにせよ動作に大きな影響はありませんが、気になる方は
【UE4】プラグインをテンプレートから作る場合の留意事項(その5)で扱っていますのでそちらへどうぞ。
繰り返しになりますが、このシリーズで一番重い記事です。

■関連記事


おしまい。

0
1
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
0
1