#はじめに
私が以前作ったPDFを読み込むプラグインで、PDFを選択する方法としてファイルピッカーを作成したので今回はファイルピッカーBlueprintノードを作っていきたいと思います。PDFプラグインをエディタから使えるようにして、簡単なEditorUtilityWidgetを作ってみた。#UE4 https://t.co/o6oZZGcuhP pic.twitter.com/JX8Z0uyQFP
— Naotsun (@Naotsun_UE) October 24, 2019
#つくるもの
色々なソフトやアプリケーションで見かけるファイルピッカー(C#で言うOpenFileDialog)を作ります。
また、タイトルにもある通り実行時とエディタ時どちらでも動作するようにします。
機能は以下の通りです。
入力ピン | 型 | 説明 |
---|---|---|
Dialog Title | FString | ファイルピッカーのウィンドウのタイトル |
Default Path | FString | ファイルピッカー起動時に開かれるディレクトリのファイルパス |
Default File | FString | ファイルピッカー起動時に選択されるファイルのファイルパス |
File Type | FString | ファイルピッカーに表示するファイルの拡張子のフィルター文字列(C#と同じ) |
Is Multiple | bool | 複数選択を許可するか |
出力ピン | 型 | 説明 |
---|---|---|
Successful | Exec | ユーザーが何かしらのファイルを選択した場合実行される |
Canceled | Exec | ユーザーがキャンセルボタンを押した場合実行される |
File Path | TArray | 選択したファイルのファイルパス |
#つくってみる
まずは、BlueprintFunctionLibraryを継承したクラスを作成します。作成したら、ヘッダーに以下の列挙型と作成したクラスに以下のメンバ関数を定義します。
###列挙型
UENUM()
enum class EDialogResult : uint8
{
Successful, Cancelled
};
###OpenFileDialog関数の宣言
UFUNCTION(BlueprintCallable, Category = "OpenFileDialog", meta = (ExpandEnumAsExecs = "OutputPin"))
static void OpenFileDialog(
EDialogResult& OutputPin,
TArray<FString>& FilePath,
const FString& DialogTitle = TEXT("Open File Dialog"),
const FString& DefaultPath = TEXT(""),
const FString& DefaultFile = TEXT(""),
const FString& FileType = TEXT("All (*)|*.*"),
bool IsMultiple = false
);
なぜ列挙型を定義したのかについてですが、これは複数の出力ピンを持つノードを作るためです。関数の引数にこの列挙型の変数を定義し、その変数名をUFUNCTIONマクロ内のmetaでExpandEnumAsExecs = 列挙型の変数名として指定することで実装できます。また、Blueprintノードで表示されるピンの名前は列挙型の値の名前になります。
ちなみに、デフォルト値の横にコメントを記述するとUnrealHeaderToolが正しく動作しなくなりビルドできないので気を付けましょう。
こういう風に書くとコメントが原因でえUnrealHeaderToolから引数のデフォルト値を正しくパースできないみたいなエラーが出る pic.twitter.com/1KZvnztaNr
— Naotsun (@Naotsun_UE) November 4, 2019
次に、この関数の中身についてみていきましょう。
まずは、今回作成する機能を作るために必要なヘッダーファイルをインクルードします。
###必要なヘッダーファイル
#include "Engine.h"
#include "Misc/Paths.h"
#include "Developer/DesktopPlatform/Public/IDesktopPlatform.h"
#include "Developer/DesktopPlatform/Public/DesktopPlatformModule.h"
#include "Editor/MainFrame/Public/Interfaces/IMainFrameModule.h"
IDesktopPlatform.hとDesktopPlatformModule.hはIDesktopPlatformという今回作成する機能を実装するために必要なクラスを使う際に必要なヘッダーファイルです。
Path.hは取得したファイルパスを加工するために必要なヘッダーファイルです。
Engine.hとIMainFrameModule.hについては後で紹介します。
次に実装部分についてですが、実はBlueprintには公開されていないだけで機能自体は実装されています。UE4のエディタではImportボタンを押すとアセットがインポートできますが、あの時にファイルピッカーが起動するのでそこで使われている処理ではないかと思います。
以下が関数の実装になります。
###OpenFileDialog関数の実装部分
void UMyBlueprintFunctionLibrary::OpenFileDialog(
EDialogResult& OutputPin,
TArray<FString>& FilePath,
const FString& DialogTitle,
const FString& DefaultPath,
const FString& DefaultFile,
const FString& FileType,
bool IsMultiple
)
{
//ウィンドウハンドルを取得
void* windowHandle = GetWindowHandle();
if (windowHandle)
{
IDesktopPlatform* desktopPlatform = FDesktopPlatformModule::Get();
if (desktopPlatform)
{
//ダイアログを開く
bool result = desktopPlatform->OpenFileDialog(
windowHandle,
DialogTitle,
DefaultPath,
DefaultFile,
FileType,
(IsMultiple ? EFileDialogFlags::Type::Multiple : EFileDialogFlags::Type::None),
FilePath
);
if (result)
{
//相対パスを絶対パスに変換
for (FString& fp : FilePath)
{
fp = FPaths::ConvertRelativePathToFull(fp);
}
OutputPin = EDialogResult::Successful;
return;
}
}
}
OutputPin = EDialogResult::Cancelled;
}
まず、ファイルピッカーを起動するにはFDesktopPlatformModuleのGet関数でIDesktopPlatfomを取得し、IDesktopPlatformのOpenFileDialog関数を呼び出す必要があります。引数には先程の引数の他にParentWindowHandleなるものが必要で、これがゲームとエディタの両方で使えるようにするために重要な要素になります。
そこで上記のソースコードにもある通り、ゲームとエディタでそれぞれ対応したParentWindowHandleを取得するGetWindowHandle関数を定義します。
ここでの注意点ですが、取得したファイルパスが相対パスな時があるので、FPathsのConvertRelativePathToFull関数で絶対パスに変換しておきましょう。
###GetWindowHandle関数の宣言
protected:
//ウィンドウハンドルを取得する
static void* GetWindowHandle();
###GetWindowHandle関数の実装部分
void* UMyBlueprintFunctionLibrary::GetWindowHandle()
{
//エディタの場合
if (GIsEditor)
{
IMainFrameModule& MainFrameModule = IMainFrameModule::Get();
TSharedPtr<SWindow> MainWindow = MainFrameModule.GetParentWindow();
if (MainWindow.IsValid() && MainWindow->GetNativeWindow().IsValid())
{
return MainWindow->GetNativeWindow()->GetOSWindowHandle();
}
}
//実行時の場合
else
{
if (GEngine && GEngine->GameViewport)
{
return GEngine->GameViewport->GetWindow()->GetNativeWindow()->GetOSWindowHandle();
}
}
return nullptr;
}
まず、ParentWindowHandleについてですが、これはファイルピッカーのウィンドウが終了した時に処理を戻す親のウィンドウのことです。エディタであればエディタ内のウィンドウが、ゲームであれば実行しているゲームのウィンドウがこれに当たります。
これらの機能を使用するには先程のEngine.hとIMainFrameModule.hが必要になります。
また、エディタかどうかはグローバル変数のGIsEditorを確認することで判断することができます。
ちなみに、子のウィンドウが起動する形になるためフルスクリーンモードの場合、起動したウィンドウが正常に表示されません。ゲームなどで使う際は気を付けましょう。
最後にエディタモジュールを作れば完成です。エディタモジュールについてはヒストリアさんが詳しく解説されているのでそちらを参照してください。
[UE4] モジュールについて|株式会社ヒストリア
これでゲームとエディタで使えるファイルピッカーを起動するBlueprintノードを実装することができました。
おつかれさまでした!
#おわりに
既にエンジンのソースコードに機能が実装されていてBlueprintに公開するだけだったので思ったよりは簡単だったのではないでしょうか。他にもディレクトリのファイルパスを取得するOpenDirectoryDialogなんて関数もあったりするので気になった機能があればいじってみると面白いかもしれません。
完成バージョン(プラグイン)は以下でダウンロードできます。
https://github.com/Naotsun19B/FilePicker
明日は、@fukusuke8gouさんの「ビンゴマシーンをつくろう」です。お楽しみに!