BLOGブログ

2018.07.18UE4UE/ Editor拡張

[UE4] コンテンツブラウザからレベルブループリントを編集する方法

レベルブループリントはレベル全体に渡るグローバルな処理を記述できるブループリントとなっています。
そのため、通常のブループリントとは一部の機能が異なっていたり、ブループリントエディタの開き方も通常のブループリントとは異なります。
公式のレベルブループリントドキュメントはこちら

BPエディタの開き方

通常のブループリントの開き方は

  • コンテンツブラウザでアセットをダブルクリック
  • 右クリックのメニューからEdit
  • Toolbar->Blueprints->Open Blueprint Class…で開く
  • File->Open Asset…(ctrl+p)で選択

などでブループリントエディタを開くことができますが、レベルブループリントは上記の方法で開くとエディタを開くのではなく、そのレベルを読み込んでレベルの編集を行おうとします。
Open Blueprint Class…には候補としても現れません。

レベルプループリントを開くには、現在開いているレベルなら
Toolbar->Blueprints->Open Level Blueprint
サブレベルに設定していれば、
Toolbar->Blueprints->Sub-Levels
から開けます。

もしくはLevelsウィンドウのコントローラーマークをクリックしても開けます。

この記事ですること

レベルブループリントのエディタを開くためには前述の通りの方法を取りますが、該当のレベルを開いてPersistent Levelに設定する必要があり、ただレベルブループリントを開きたいだけだとしても開発の規模が大きくなってくると読み込みに非常に多くの時間がかかってしまいます。
読み込み時間を最小限にするために、空のマップを開いて、レベルブループリントのみ編集するといった方針は取れません。
そこで今回はPersistentでもSubでもないレベルをコンテンツブラウザのLevelアセットを右クリックしてレベルブループリントを開けるようにしてしまおうという内容です。
C++によるプラグインでの実装になるので、C++はどうでもいいよという方はこちらでプラグインを配布しています。

プラグインの作成準備

Edit->PluginsからPluginsウィンドウを開き、右下のNew Pluginをクリックします。

テンプレートの中のBlankを選択し、プラグインの名称を決めてCreatePluginを行います。説明のためここではHogeと名付けます。
配布プラグインはAddOpenLevelBlueprintMenuPluginと長くなってしまったのでどうかいい名前をつけてください。

CreatePluginをおして、しばらく待つとプラグインのコードがテキストエディタで開きます。

プラグイン作成

プラグイン名.upluginを開いてModuleのTypeを”Developer”から”Editor”に変更します。

次にBuild.csを開き、PrivateDependencyModuleNamesに”UnrealEd”を追加します。

次にヘッダーを開き関数を追加します。

/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;

private:
static TSharedRef<FExtender> OnExtendContentBrowserAssetSelectionMenu(const TArray<FAssetData>& SelectedAssets);
static void CreateAssetMenu(FMenuBuilder& MenuBuilder, TArray<FAssetData> SelectedAssets);
static void OpenLevelBlueprint(TArray<FAssetData> SelectedAssets);
};
最後にc++の実装です

#include “Engine/World.h”
#include “LevelEditor.h”
#include “ContentBrowserModule.h”
#include “IContentBrowserSingleton.h”
#include “Misc/MessageDialog.h”
#include “Engine/LevelScriptBlueprint.h”
#include “Toolkits/AssetEditorManager.h”

#define LOCTEXT_NAMESPACE “FHogeModule”

namespace
{
FContentBrowserMenuExtender_SelectedAssets ContentBrowserExtenderDelegate;
FDelegateHandle ContentBrowserExtenderDelegateHandle;
}

void FHogeModule::StartupModule()
{
if (IsRunningCommandlet()) { return; }

FContentBrowserModule& ContentBrowserModule =
FModuleManager::LoadModuleChecked<FContentBrowserModule>(TEXT(“ContentBrowser”));

// アセット右クリックメニューへのExtender登録
ContentBrowserExtenderDelegate =
FContentBrowserMenuExtender_SelectedAssets::CreateStatic(
&FHogeModule::OnExtendContentBrowserAssetSelectionMenu
);
TArray<FContentBrowserMenuExtender_SelectedAssets>& CBMenuExtenderDelegates =
ContentBrowserModule.GetAllAssetViewContextMenuExtenders();
CBMenuExtenderDelegates.Add(ContentBrowserExtenderDelegate);
ContentBrowserExtenderDelegateHandle = CBMenuExtenderDelegates.Last().GetHandle();
}

void FHogeModule::ShutdownModule()
{
FContentBrowserModule* ContentBrowserModule =
FModuleManager::GetModulePtr<FContentBrowserModule>(TEXT(“ContentBrowser”));
if (nullptr != ContentBrowserModule)
{
TArray<FContentBrowserMenuExtender_SelectedAssets>& CBMenuExtenderDelegates =
ContentBrowserModule->GetAllAssetViewContextMenuExtenders();
CBMenuExtenderDelegates.RemoveAll([](const FContentBrowserMenuExtender_SelectedAssets& Delegate)
{ return Delegate.GetHandle() == ContentBrowserExtenderDelegateHandle; });
}
}

TSharedRef<FExtender> FHogeModule::OnExtendContentBrowserAssetSelectionMenu(const TArray<FAssetData>& SelectedAssets)
{
TSharedRef<FExtender> Extender(new FExtender());

for (auto ItAsset = SelectedAssets.CreateConstIterator(); ItAsset; ++ItAsset)
{
if ((*ItAsset).AssetClass == UWorld::StaticClass()->GetFName())
{
Extender->AddMenuExtension(
TEXT(“CommonAssetActions”),
EExtensionHook::First,
nullptr,
FMenuExtensionDelegate::CreateStatic(&FHogeModule::CreateAssetMenu, SelectedAssets)
);
return Extender;
}
}

return Extender;
}

void FHogeModule::CreateAssetMenu(FMenuBuilder& MenuBuilder, TArray<FAssetData> SelectedAssets)
{
int count = 0;
for (auto ItAsset = SelectedAssets.CreateConstIterator(); ItAsset; ++ItAsset)
{
if ((*ItAsset).AssetClass == UWorld::StaticClass()->GetFName())
{
count++;
}
}

FText toolTip = count == 1 ? LOCTEXT(“Open Level Blueprint”, “Open Level Blueprint”) : LOCTEXT(“Open Level Blueprints”, “Open Level Blueprints”);

MenuBuilder.AddMenuEntry(
toolTip,
LOCTEXT(“OpenLevelBlueprint_Tooltip”, “Open Level Blueprint. Note: This is unofficial”),
FSlateIcon(),
FUIAction(FExecuteAction::CreateStatic(&FHogeModule::OpenLevelBlueprint, SelectedAssets)),
NAME_None,
EUserInterfaceActionType::Button
);
}

void FHogeModule::OpenLevelBlueprint(TArray<FAssetData> SelectedAssets)
{
for (auto ItAsset = SelectedAssets.CreateConstIterator(); ItAsset; ++ItAsset)
{
UWorld* world = Cast<UWorld>((*ItAsset).GetAsset());
if (!world)
{
continue;
}

ULevel* level = world->GetCurrentLevel();
if (level == NULL)
{
return;
}

ULevelScriptBlueprint* LevelScriptBlueprint = level->GetLevelScriptBlueprint();
if (LevelScriptBlueprint)
{
FAssetEditorManager::Get().OpenEditorForAsset(LevelScriptBlueprint);
}
else
{
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT(“UnrealEd”, “UnableToCreateLevelScript”, “Unable to find or create a level blueprint for this level.”));
}
}
}

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FHogeModule, Hoge)
以上でレベルを右クリックした時にメニューに追加されました。

コードの内容について

エディタ実行時にのみ読み込ませるため、upluginではtypeにEditorを記述しました。
また、UnrealEdモジュールを使用しているため、Build.csにはUnrealEdを追加しています。
コードの流れとしては、アセットを右クリックした時にUWorldクラスのアセットだったら右クリックメニューの中にFHogeModule::OpenLevelBlueprint()を実行するメニューを追加するというものです。
ブループリントエディタを開くのにはFAssetEditorManager::Get().OpenEditorForAsset()を使用しています。
右クリックして選択したアセットはUWorldとなっているのでブループリントの本体ではありません。
FHogeModule::OpenLevelBlueprint()関数内で右クリックして選択したSelectedAssetsから->GetCurrentLevel()->GetLevelScriptBlueprint()としてブループリントを取得し、OpenEditorForAsset()に投げています。

おまけ メニューアイコンの追加

今回実装したOpen Level Blueprintにはアイコンが付いていないのでこのアイコンを付けてみます。

Build.csにEditorStyleを追加します。

FHogeModule::CreateAssetMenu()内のMenuBuilder.AddMenuEntry()でFSlateIcon()の引数を変更します。

FText toolTip = count == 1 ? LOCTEXT(“Open Level Blueprint”, “Open Level Blueprint”) : LOCTEXT(“Open Level Blueprints”, “Open Level Blueprints”);

MenuBuilder.AddMenuEntry(
toolTip,
LOCTEXT(“OpenLevelBlueprint_Tooltip”, “Open Level Blueprint. Note: This is unofficial”),
FSlateIcon(FEditorStyle::GetStyleSetName(), “Level.ScriptIcon16x”),
FUIAction(FExecuteAction::CreateStatic(&FHogeModule::OpenLevelBlueprint, SelectedAssets)),
NAME_None,
EUserInterfaceActionType::Button
);
}
以上でアイコンが追加されました。