UE4を配置ツールとして利用する

今回のネタはUE4ネタではあるものの、UE4でゲームを作る人にはあんまり意味がない内容となっております。

もしかしたら業務で利用する人が嬉しいかもしれませんが、多くの人にはどうでもいい内容でしょう。

特にエピックさんは一銭の得にもならないので、最初に謝っておきます。ごめんなさい。

UE4を使ってゲームを作れる環境にある方はそのままUE4を使えばいいのですが、誰もがUE4を使ってゲーム開発をする業務に携わっているわけではありません。

もちろんUnityを使う業務であればそれを利用すればOKなのですが、UE4やUnityといったゲームエンジンを使用できない業務の方もおられるでしょう。

具体的にはPS3Xbox360WiiU3DS、Vita、X68、MSXといったハードウェアで開発している方々ですね。

PS3Xbox360は海外ではマルチプラットフォームから外されてきていますが、日本ではPS3がまだまだ現役で、PS4の売上も十分とはいえない状態ではPS4PS3のマルチはまだまだ発売されそうです。

WiiUはまあ置いとくとして、3DSは日本では主戦場の1つですし、Vitaも日本では無視できないハードと言えます。

X68とMSX? ま、それはそれ。

これらのハードでゲームを作成する場合、ゲームエンジンに頼れない場合もありますし、マルチプラットフォームの対象になると対応ハードではUE4を、それ以外では自社エンジンを、といった作りはあまりしないでしょう。

金銭的な問題やゲーム内容がゲームエンジンに合っていないなどの理由からゲームエンジンの採用を見送る場合もあります。

ですが、このような場合でもUE4などのゲームエンジンを使うことは可能です。

以前UE3を使っていたという同僚は、UE3のエディタが非常に良く出来ているので配置ツールとして使っていたそうです。

そう、つまり、レベルデザインツールとしてUE4を使う、というのは有効な手段でもあるのです。

というわけで、今回はUE4レベルデザインを行い、そこで配置されたアクターの情報を外部ファイルに出力するプラグインの簡単な作成方法を紹介します。

今回は配置されたアクターの名前とトランスフォーム情報のみを出力していますが、やろうと思えばメッシュアセットの名前や自身のゲームに必要な情報を取得することも可能でしょう。

というわけで、続きからで簡単な作成方法を見て行きましょう。

まず、今回はUE4.8にインテグレートされたプラグインである、Plugin Creatorを利用してみます。

まだベータ版となっていますが十分使えます。

UE4.8 Preview 4を立ち上げ、C++プロジェクトを作成してください。C++プロジェクトであればなんでも構いません。

プロジェクトをビルド、立ち上げが終了したらメニューから [ウィンドウ] → [Plugins] を選択し、[Editor] カテゴリにある [Plugin Creator] を有効にします。

ue320.jpg 

ベータバージョンだけどいいかい?的なメッセージが出ますが、[はい] で先に進みましょう。エディタの再起動を促されるので再起動します。

再起動後、メニューの [ファイル] に [New Plugin] という項目が追加されているので、こちらをクリックします。

ue321.jpg

するとPlugin Creatorが起動しますので、必要な項目を埋めます。

ue322.jpg

必要な項目はプラグインの名前くらいですが、必要に応じて作者やバージョンなども記述しましょう。

今回は簡単のため、Toolbar Buttonとして作成します。

[Create Plugin] ボタンを押すとプラグインのフォルダ、ソースコードなどが作成され、プロジェクトが再生成されます。

再生成前にVisual Studioは落としておいた方がいいかもしれませんが、落とさなくてもあとでプロジェクトの再読み込みを促されるだけです。

今回のプラグインは "MyButton" という名前にしてみました。

プラグインが作成されたらVisual Studioを立ち上げ直します。

すると [Plugin] フォルダと、今回作成したプラグインソースコードなどが追加されているはずです。

ue323.jpg

ここまで来たらプロジェクトをビルドし、再度エディタを立ち上げましょう。

作成したプラグインはデフォルトでONになっているので、ツールバーにボタンが追加されています。

ue324.jpg

この状態ではボタンを押しても意味がありません。コードが書かれていないからです。

ボタンを押した際の処理は MyButton.cpp に定義されている void FMyButtonModule::PluginButtonClicked() 関数に記述します。

ヘッダファイルとしては以下の2つをインクルードします。

#include "DesktopPlatformModule.h"

#include "IMainFrameModule.h"

関数に記述する処理はこのようになります。

void FMyButtonModule::PluginButtonClicked()

{

    // ダイアログを表示するため、必要なモジュール等を修得

    IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();

    void* ParentWindowWindowHandle = nullptr;

    IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));

    const TSharedPtr<SWindow>& MainFrameParentWindow = MainFrameModule.GetParentWindow();

    if ( MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid() )

    {

        ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle();

    }

    // ファイル保存ダイアログを立ち上げる

    TArray<FString> OutFilenames;

    DesktopPlatform->SaveFileDialog(

        ParentWindowWindowHandle,

        TEXT("Plugin Test Export"),

        TEXT(""),

        TEXT(""),

        TEXT("Text File (*.txt)|*.txt"),

        EFileDialogFlags::None,

        OutFilenames

        );

    // 出力ファイルが決定された場合

    if (OutFilenames.Num() > 0)

    {

        // エディタで使用されているワールドクラスを取得

        UWorld* World = GWorld.GetReference();

        if (World)

        {

            // 出力ファイルを書き込み用で開く

            IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();

            IFileHandle* FileHandle = PlatformFile.OpenWrite(*OutFilenames[0]);

            // ワールドに登録されているすべてのアクターをイテレート

            for (TActorIterator<AActor> it(World); it; ++it)

            {

                // 名前

                FString name = it->GetName();

                FString output = name + TEXT("\n");

                // トランスフォーム情報

                FTransform transform = it->GetTransform();

                FVector pos = transform.GetTranslation();

                FQuat rot = transform.GetRotation();

                FVector scale = transform.GetScale3D();

                FText posT = FText::Format(LOCTEXT("", "\tposition : {0}, {1}, {2}\n"), FText::AsNumber(pos.X), FText::AsNumber(pos.Y), FText::AsNumber(pos.Z));

                FText rotT = FText::Format(LOCTEXT("", "\trotation : {0}, {1}, {2}, {3}\n"), FText::AsNumber(rot.X), FText::AsNumber(rot.Y), FText::AsNumber(rot.Z), FText::AsNumber(rot.W));

                FText scaleT = FText::Format(LOCTEXT("", "\tposition : {0}, {1}, {2}\n"), FText::AsNumber(scale.X), FText::AsNumber(scale.Y), FText::AsNumber(scale.Z));

                output += posT.ToString();

                output += rotT.ToString();

                output += scaleT.ToString();

                // 文字列をUTF8に変換してテキスト出力

                FTCHARToUTF8 toUTF8(*output);

                FileHandle->Write(reinterpret_cast<const uint8*>(toUTF8.Get()), toUTF8.Length());

            }

            // ファイルハンドルを削除するとファイルクローズとなる

            delete FileHandle;

        }

    }

}

メソッド内には一切のコードが書かれていないので、すべてコピーしてください。

何をやっているのかを簡単に説明すると、最初に DesktopPlatform を取得し、この機能を利用してファイル保存ダイアログを立ち上げます。

Windowsであればいつものダイアログが出るはずですし、MacLinuxでもそれぞれの環境に応じたダイアログが出てくると思われます。

ダイアログで出力ファイルを決定すると OutFilenames 配列に1つ以上のファイルパスが入ってくるので、この配列の一番最初のファイルを開きます。

この場合も fopen() 関数や CreateFile() 関数は呼ばないようにしましょう。

プラットフォームの違いを吸収しているクラスである IPlatformFile インターフェースがありますので、これを取得して OpenWrite() メソッドで書き込み用のファイルを開きます。

なお、何らかの理由でファイルが開けない場合は IFileHandle が nullptr になるので、本来であればきちんとNULLチェックを行いましょう。

アクターのイテレーションはUWorldがあれば簡単にできます。

GEngine->GetWorld() でもUWorldは取得できますが、ゲーム中では nullptr が帰ってきます。

GWorldというグローバル変数を利用すると有効なUWorldが取得できるようなので、基本的にはこちらを使いましょう。

TActorIterator のテンプレート引数で必要なアクターの方を指定しますが、今回はすべてのアクターを取得するために AActor を指定しています。

名前とトランスフォーム以外に、例えば登録されているメッシュ名などの情報が必要でしたら、コンポーネントの有無などをチェックするようにすればOKでしょう。

名前やトランスフォーム情報を FString 形式の文字列に変換したあと、UTF8に変換して出力を行っています。

UTF8なので日本語も表示できますので、コメントなどが必要なら出力してしまいましょう。

最後に FileHandle を削除すればファイルを閉じます。

これだけではビルドは通らないので、必要なモジュールの記述を行います。

MyButton.Build.cs の PrivateDependencyModuleName.AddRange の部分に4つのモジュールを追加します。

Engine

CoreUObject

DesktopPlatform

MainFrame

これらを追加したらビルドしましょう。

ビルドが通るはずなので、起動したらボタンを押してみます。

保存するファイルを決定すればテキスト形式で配置されているアクターの情報が出力されるはずです。

アウトライナー上には表示されていないアクターも出力されますが、これらはシステム側で追加されているものです。

不要であれば何らかの方法で排除しておきましょう。

というわけで、今回のネタは終了です。

このような使い方は普通はしないでしょうけど、レベルデザインするツールがない、という方は検討してみてはどうでしょうか。

また、そんな使い方をしないよ、という方でも Plugin Creator を使いたくなったのではないでしょうか?

これまでのプラグイン作成はかなり面倒だったので、このツールによって簡単にプラグイン作成できるようになったのは嬉しいですね。