こんにちは!そしてお久しぶりです!
エンジニアの桒村です。
前回書かせていただいたブログから気づけば実に1年半ほどの年月が経っていました。時間が過ぎていくのは早いものですね!
さてさて前置きはこれぐらいにして、今回は前編と後編に分けて、UnrealEngine5におけるプラグインの開発方法について、作成手順から公開までの一連の流れをお話しさせていただきます。
まず初めに...
プラグインとは?...と思われる方がいらっしゃるかもしれないので、軽く説明させていただくと、
ずばり!!!「エンジンの機能を拡張・追加するモジュール」のことを指します。
こちらの画像のようにUnrealEngineにはあらかじめ標準で数多くのプラグインが用意されています。


そして...なんと弊社では「SPCRJointDynamics」と呼ばれる、キャラクターのスカートなどの揺れものの動きをクロスシミュレーションという形で再現することができるプラグインが無料で配布されています!

詳しくは、以下のGitHubよりリポジトリデータを確認することができますのでぜひ覗いてみてください!
可愛い女の子のスカートをひらひらしたい!髪の毛を揺らしたい!などの要求にこたえれるかもしれませんよ(^^♪
github.com
作業環境
・windows 10
・UnrealEngine 5.6.0
・VisualStudio 2022
プロジェクト設定
まずは、UnrealEngine5.6.0を起動して、プロジェクトを作成してください。

プロジェクト名は適当で大丈夫ですが、今回はプラグイン開発をメインとするプロジェクトと定義させていただくため、「Projects_DevPlugin」にしました!
今回もコードを参照するということで、BP(ブループリント)ではなく、C++を選択していただけたらと思います。
プラグインの新規作成手順について
次に、プラグインを新規で作成する手順になります。
エディタから編集 < プラグインをクリックして、プラグインのタブを開き、左上に用意されている『+追加』のボタンをクリックします。

すると、新しいプラグインというタブが開きますので、各テンプレートの中から、新規で作成したいプラグインのタイプに合わせて選択します。

今回は「アニメーションシーケンス内のNotify(通知)にフィルターをつけて効率的に管理・確認できるエディタ拡張」といったものを追加するので、テンプレートは、「①エディタ スタンドアロン ウィンドウ」を選択します。
次に、②プラグイン名「NotifyFilterTool」を入力し、「プラグインを作成」のボタンをクリックします。
③の「記述子データ」については、必ずしも設定する必要があるわけではないですが、今回は公開フローまでをお話しするため、一部だけ設定しています。

せっかくなので、記述子データの各項目についても軽く記載しておきます。
| Author | プラグインの開発者 | |
|---|---|---|
| Description | プラグインの説明文 | |
| Author URL | 開発者の紹介URL | |
| Is Beta Version | ベータ版かどうか |
プラグインの作成ができたら、プロジェクトのディレクトリ階層に新規でPluginsフォルダが作成され、その中に先ほど作成したプラグインのフォルダが入っている状態になります。
(Pluginsフォルダが元々存在している場合は、そちらのフォルダ内に新規で作成したプラグインのフォルダが入っている状態になります)

ここまで確認できたらプラグインの新規作成は完了となります。
プラグインの機能実装
それでは、アニメーションシーケンス内のNotify(通知)にフィルター機能を実装していきます。
プラグインを新規で作成しただけで何も変更を加えていない状態ですと、レベルエディタ上からウィンドウタブの中に新しく「NotifyFilterTool」という項目が追加されています。

こちらの項目をクリックしてみると、真っ黒のウィンドウが表示されているのみになります。
これからこちらのウィンドウにフィルター機能を実装していきます。

今回作成するプラグインの使用用途としては、アニメーションエディタ側となるため、レベルエディタ上ではなく、アニメーショエディタ上のウィンドウタブから「NotifyFilterTool」という項目が表示されるように修正をしていきます。
NotifyFilterTool.cppを開き、RegisterMenus関数内を以下のコードのように修正していきます。
void FNotifyFilterToolModule::RegisterMenus() { // Owner will be used for cleanup in call to UToolMenus::UnregisterOwner FToolMenuOwnerScoped OwnerScoped(this); // アニメーションエディタのメインメニュー「Window」に拡張 UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("AssetEditor.AnimationEditor.MainMenu.Window"); if (!Menu) { return; } // 大項目として「FilterTool」を追加 const FName SectionName = "NotifyToolSection"; // すでに存在するか確認してからセクションを追加(重複回避) if (!Menu->FindSection(SectionName)) { Menu->AddSection(SectionName, FText::FromString("FilterTool")); } // コマンド追加(NotifyFilterTool) FToolMenuSection& Section = Menu->FindOrAddSection(SectionName); Section.AddMenuEntryWithCommandList(FNotifyFilterToolCommands::Get().OpenPluginWindow, PluginCommands); }
表示結果はこちらのようになっています。
アニメーションエディター上のウィンドウタブから「NotifyFilterTool」が開けるようになっており、更に視認性を上げるために大項目として「FilterTool」という項目を追加しています。

続いて、「NotifyFilterTool」の中身を実装していきますが、まずは先ほどの真っ黒のウィンドウを今回の仕様に合わせて、こちらの見た目になるようにUI部分を実装していきます。

先ほど開いたNotifyFilterToolのOnSpawnPluginTab関数内に、こちらのUIをそれぞれ追加していきます。
・AnimNotifyやAnimNotifyStateのリスト表示とチェックボックス
・フィルターを反映させるためのボタン
・フィルターをクリアにするボタン
TSharedRef<SDockTab> FNotifyFilterToolModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs) { FText WidgetText = FText::Format( LOCTEXT("WindowWidgetText", "Add code to {0} in {1} to override this window's contents"), FText::FromString(TEXT("FNotifyFilterToolModule::OnSpawnPluginTab")), FText::FromString(TEXT("NotifyFilterTool.cpp")) ); TArray<FString> NotifyClasses; TArray<FString> NotifyStateClasses; GetAllAnimNotifyClasses(NotifyClasses, NotifyStateClasses); // アニメーションシーケンスに実際に存在するNotifyクラス名セットを作る TSet<FString> UsedNotifyClasses; TSet<FString> UsedNotifyStateClasses; CurrentAnimSequence = GetOpenedAnimSequence(); if (CurrentAnimSequence) { GetUsedNotifyClassesFromAnimSequence(CurrentAnimSequence, UsedNotifyClasses, UsedNotifyStateClasses); } return SNew(SDockTab) .TabRole(ETabRole::NomadTab) [ SNew(SScrollBox) + SScrollBox::Slot() [ SNew(SVerticalBox) // AnimNotify セクション + SVerticalBox::Slot() .AutoHeight().Padding(10) [ SNew(STextBlock) .Text(FText::FromString(TEXT("AnimNotify"))) .Font(FCoreStyle::Get().GetFontStyle("HeadingMedium")) ] + SVerticalBox::Slot().AutoHeight().Padding(10) [ GenerateCheckboxList(NotifyClasses, NotifyCheckBoxes, UsedNotifyClasses) ] // AnimNotifyState セクション + SVerticalBox::Slot() .AutoHeight().Padding(10) [ SNew(STextBlock) .Text(FText::FromString(TEXT("AnimNotifyState"))) .Font(FCoreStyle::Get().GetFontStyle("HeadingMedium")) ] + SVerticalBox::Slot().AutoHeight().Padding(10) [ GenerateCheckboxList(NotifyStateClasses, NotifyStateCheckBoxes, UsedNotifyStateClasses) ] // ボタン群 横並び + SVerticalBox::Slot() .AutoHeight().Padding(10) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth().Padding(0, 0, 10, 0) // 右に余白 [ SNew(SButton) .Text(FText::FromString(TEXT("すべてのフィルターをクリア"))) .OnClicked_Lambda([this]() { for (const FCheckBoxWithLabel& Item : NotifyCheckBoxes) { if (Item.CheckBox.IsValid()) { Item.CheckBox->SetIsChecked(ECheckBoxState::Unchecked); } } for (const FCheckBoxWithLabel& Item : NotifyStateCheckBoxes) { if (Item.CheckBox.IsValid()) { Item.CheckBox->SetIsChecked(ECheckBoxState::Unchecked); } } ClearNotifyVisibilityFilter(); return FReply::Handled(); }) ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(SButton) .Text(FText::FromString(TEXT("選択した通知のタイプをフィルター"))) .OnClicked_Lambda([this]() { ApplyNotifyVisibilityFilter(); return FReply::Handled(); }) ] ] ] ]; }
上記のコードを見てみると、別の関数を呼び出している部分もありますが、UIの実装は一旦これで完成となります。
こちらで呼び出している各関数については、この後に記載しています。
続いて、追加したUIに対して中身の実装についてです。
実装の中身をひとつひとつ説明していくと長くなるため関数ごとに機能概要の説明までとさせていただきます。
今回作成したプラグインについては、後日公開予定なので詳細を知りたい方は、直接プラグインを手元で開いていただければと思います!(o^―^o)ニコ