LoginSignup
5

More than 3 years have passed since last update.

UnrealC++でゲームとエディタで使えるファイルピッカーを作る

Last updated at Posted at 2019-12-17

はじめに


私が以前作ったPDFを読み込むプラグインで、PDFを選択する方法としてファイルピッカーを作成したので今回はファイルピッカーBlueprintノードを作っていきたいと思います。

つくるもの

キャプチャ.PNG
色々なソフトやアプリケーションで見かけるファイルピッカー(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を継承したクラスを作成します。作成したら、ヘッダーに以下の列挙型と作成したクラスに以下のメンバ関数を定義します。

列挙型

MyBlueprintFunctionLibrary.h
UENUM()
enum class EDialogResult : uint8
{
    Successful, Cancelled
};

OpenFileDialog関数の宣言

MyBlueprintFunctionLibrary.h
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が正しく動作しなくなりビルドできないので気を付けましょう。

次に、この関数の中身についてみていきましょう。
まずは、今回作成する機能を作るために必要なヘッダーファイルをインクルードします。

必要なヘッダーファイル

MyBlueprintFunctionLibrary.cpp
#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関数の実装部分

MyBlueprintFunctionLibrary.cpp
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関数の宣言

MyBlueprintFunctionLibrary.h
protected:
    //ウィンドウハンドルを取得する
    static void* GetWindowHandle();

GetWindowHandle関数の実装部分

MyBlueprintFunctionLibrary.cpp
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さんの「ビンゴマシーンをつくろう」です。お楽しみに!

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
5