UE4 エディター処理でプログレスバーを表示する
エディター系処理で結構長い時間がかかる処理をするときに、プログレスバーとか出して、今これくらいだよーって表示したくなることありますよね。
例えばエディターでレベルとか開くときに以下のような表示がされたりします。
こういうのを自分で操作するときも使いたい!
ということでやってみました。
目標
エディター処理でプログレスバーを表示してみる。
ついでに「キャンセル」とか、今処理中のファイル名とかも表示してみる。
環境
・UE 4.27.0 (Gitのコミット:99b6e203a15d04fc7bbbf554c421a985c1ccb8f1 をビルド)
・VisualStudio 2019 16.11.2
調べる
よくわからないので調査する。
とりあえず確定でプログレスバーが表示される操作を探して、その処理を追いかけてみます。
始めに画像を貼ったように「レベルを開く」と絶対表示されます。
いつものように「エディタの環境設定」→「一般」→「未分類」→「UIの拡張点の表示」にチェックを入れて再起動します。
ファイルから「レベルを開く」を見ると「OpenLevel」と記述されているので、コードから「OpenLevel」を探します。
このあたりの調査は過去に以下の記事で行いました。
naoxgames.hatenablog.jp
この時の調査結果でFileHelpers.cppの
bool FEditorFileUtils::LoadMap(const FString& InFilename, bool LoadAsTemplate, bool bShowProgress)
の処理を通ることがわかっています。
明らかに怪しい「bShowProgress」という引数を追ってると以下の処理に入る様子
GEditor->Exec( NULL, *LoadCommand );
この処理である「UEditorEngine::Exec」の中を、「FEditorFileUtils::LoadMap」の「LoadCommand」を元に追うと、
UEditorEngine::HandleMapCommand
UEditorEngine::Map_Load
int32 bShowProgress = 1; FParse::Value(Str, TEXT("SHOWPROGRESS="), bShowProgress); ~ FScopedSlowTask SlowTask(100, LocalizedLoadingMap, bShowProgress != 0); SlowTask.MakeDialog(); SlowTask.EnterProgressFrame(10, FText::Format( NSLOCTEXT("UnrealEd", "LoadingMapStatus_CleaningUp", "{0} (Clearing existing world)"), LocalizedLoadingMap ));
というコードにたどり着きます。
「FScopedSlowTask」がプログレスバーの処理をやってくれているようです。
これを調べてみるといろいろ出てくる。
それらを参考に目標の要件を満たしてみる。
やってみる
今回は以下の記事で対応したレベルを開いて一括変換を行う、という処理に対してプログレスバーを付けてみようと思います。
naoxgames.hatenablog.jp
過去の内容と今回対応する内容を混ぜたものが以下の感じです。
内容は、すべてのレベルを開いて、すべてのActorの「ActorHiddenInGame」をtrueにするというもの。
MyBlueprintFunctionLibrary.h
#pragma once #include "CoreMinimal.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "MyBlueprintFunctionLibrary.generated.h" /** * */ UCLASS(BlueprintType) class PROJECT_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UFUNCTION(BlueprintCallable) static void SetAllHiddenForAllLevels(); UFUNCTION(BlueprintCallable) static void OpenLevelForEditor(const FAssetData AssetData); UFUNCTION(BlueprintCallable) static void SaveCurrentLevel(); };
MyBlueprintFunctionLibrary.cpp
#include "MyBlueprintFunctionLibrary.h" #include "FileHelpers.h" #include "AssetRegistryModule.h" #include "Misc/ScopedSlowTask.h" #include "Kismet/GameplayStatics.h" #include "Editor.h" void UMyBlueprintFunctionLibrary::SetAllHiddenForAllLevels() { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry")); IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); TArray<FAssetData> AssetDatas; AssetRegistry.GetAssetsByClass(UWorld::StaticClass()->GetFName(), AssetDatas); FScopedSlowTask LevelTask(AssetDatas.Num()); //プログレスバー表示 LevelTask.MakeDialog(true); for (int i = 0; i < AssetDatas.Num(); i++) { //キャンセル処理 if (LevelTask.ShouldCancel()) { break; } //プログレスバー更新 LevelTask.EnterProgressFrame(1, FText::FromString(AssetDatas[i].GetFullName())); OpenLevelForEditor(AssetDatas[i]); if (!GEditor) { continue; } TArray<AActor*> Actors; UGameplayStatics::GetAllActorsOfClass(GEditor->GetEditorWorldContext(false).World(),AActor::StaticClass(), Actors); for (AActor* Actor : Actors) { Actor->SetActorHiddenInGame(true); } SaveCurrentLevel(); } } void UMyBlueprintFunctionLibrary::OpenLevelForEditor(const FAssetData AssetData) { const FString FileToOpen = FPackageName::LongPackageNameToFilename(AssetData.PackageName.ToString(), FPackageName::GetMapPackageExtension()); const bool bLoadAsTemplate = false; const bool bShowProgress = false; FEditorFileUtils::LoadMap(FileToOpen, bLoadAsTemplate, bShowProgress); } void UMyBlueprintFunctionLibrary::SaveCurrentLevel() { FEditorFileUtils::SaveCurrentLevel(); }
また、プロジェクトの依存関係に以下を追加します。(.Build.cs)
PublicDependencyModuleNames.AddRange(new string[] { "UnrealEd" });
これの「UMyBlueprintFunctionLibrary::SetAllHiddenForAllLevels()」をEditorUtilityWidgetなどで呼び出しましょう。
実行!!!!
自分で作ったエディター処理にプログレスバーを表示してみた。(すべてのレベルに処理する的なことをしてます。) pic.twitter.com/v5qBtikxBR
— なお@エンジニアっぽくなりたい (@naoyengineer) 2022年4月12日
という具合で、すべてのレベルを処理する際にプログレスバーを表示して、一つ毎に表示されている文字列を処理中のレベル名にできました。
コードのコメントにも書いてますが、キャンセル用の処理を書くことで、キャンセルボタン押したときの挙動を作成できます。
今回の対応でもキャンセルは有効です。
思ったこと
まぁググればすぐに出るような内容ですが、エンジンから該当の処理見つけt改めてちゃんとやってみましたっていう感じです。
かなり長い処理には、途中でキャンセルしたりできるのは結構有効だと思われます。
正しい対応方法やより良い方法があればご指摘お願いいたします!!!