Suutaの秘密基地

動く...動く...

GDevelopっていうゲームエンジンを使ってみた

きっかけ

学校の授業で行われたunity始める前の練習としてこのエンジンを知った。今まで耳にしたことが無く、”unityのための練習”というフレーズからスクラッチみたいなものを想像していた。 インストールしてみると外観はかわいらしく安っぽく見えるが、必要最低限の機能と分かりやすいUIで個人的には好印象だった。


github.com

GDevelopは、フル機能のオープンソースゲーム開発ソフトウェアであり、特定のプログラミング言語の知識がなくてもHTML5およびネイティブゲームを作成できます。すべてのゲームロジックは、直感的で強力なイベントベースのシステムを使用して構築されています。

f:id:suuta1123:20201005100811j:plain


ドキュメントが乏しい

学校の授業ではもちろん操作法についていろいろ教えてもらった。初めに何をやるのかな?画面出力かな?と思っていたが、 始めに習った機能はAddForceという関数?で、指定の角度に指定の速度(ピクセル/秒)をオブジェクトに与える関数だった。 UE4でいうAddImpules()と同じ感じだと思う。授業ではこれを使い指定のキー入力時にAddForceで玉や板を動かしてブロック崩しを作った。 で、肝心の画面出力関数はどれだろう?と機能リストを軽く調べたが、そんなモノはなかった。printfがない...

さすがにしんどいなと思いネットを調べたが全くヒットしない。それだけでなく、GDevelop自体の情報が少なかった。 unity始める前の練習なのに、情報少ないのかよ。幸いなことに先生がドキュメントを作ってくださり、ある程度の機能は把握できた。 改めてunity・UE4の情報量と記事にまとめてくださっている方々に感謝です。


ロジックの組み方

GDevelopでは各シーン(タイトル・メニュー・ステージ...)ごとにロジックを書いていくことがメインになる。 逆に各オブジェクト(キャラクター・敵...)ごとにロジックを書くことはできない。 UE4でいえば各BPクラスオブジェクトごとではなく、レベルBPにのみロジックを書くことになる。 その代わりに、どのシーンからもアクセスできる外部イベントを使うことで、コントローラー、HUD、アニメーション などをモジュール化し、必要に応じてインクルードすることで各機能を利用できる。 変数にはグローバル変数、シーン変数、オブジェクト変数がある。Javascriptベースのため、型はない。

また、At The Beginning of the Scene(BeginPlay( )) やTrigger Once ( Do Once)などの組み込みイベントやシーケンス制御機能はあるが、 UE4のようなカスタムイベントは作れない。基本的には現在読み込んでいるシーンイベントを毎フレーム(仕様上、最大60FPS)回すことになる。


f:id:suuta1123:20201005134718j:plain 左側が条件式、右側が命令を記述する。
基本はビジュアルベースだが、Javascriptを使うことで詳細なエンジン機能にアクセスできる。


パッケージ

対応プラットフォームは Windows, Linux, HTML, Android, (iosは今後対応),
ビルドはオンラインサービスで専用のサーバーにて行われる。エンジンアップデート後など、メンテナンス が行われる場合があり、パーケージできない場合もあるので注意。


触ってみて

ビジュアルベースであり、プログラミング言語を知らなくてもロジックが組める点はUE4のブループリントに似ていた。 ゲームエンジンの足掛かりとしては必要十分な機能がそろっている。情報量が少ないとはいっても 日本語・文章の情報がないだけで、YouTubeには海外ニキがたくさんの動画をアップしてくれているので、途方に暮れることは ないと思う。また、個人的にはゲームパッド対応をしてほしいと思った。やっぱりゲームはコントローラーでプレイしたい。

UE4 レベルの読み込みインジケーターっぽいやつ

ローディング画面といえば

ローディング画面のロードアイコンといえば何か? グルグル回ってるやつ、アニメーションするNowLoadingの文字…など色々あると思いますが、なにを思い浮かべますか? 個人的には読み込み具合がにわかる横長のインジケータ型の印象が大きいです。横長のインジケータ型の最大の特徴は、現在の読み込み具合が視覚的に分かるところです。ただ、全然進まない(重いレベルをロードする際に)とイライラしますし、90%からなかなか進まないとかよくありますよね。事前データのダウンロードとかモバイル版のゲームではよく見かけますが、家庭用機のインゲームではあまり使われている印象がないです。 有名どころのゲームでいえば、モンハンワールド・SEKIRO・Fortniteなどで使われてますが、ロード時間が長いゲームに採用されるんでしょうか? ということで今回は、ProgressBarを使ってレベルの読み込みインジケーターっぽいやつを作ります。

やってみる

SLoadingScreen.h


SLoadingScreen.h


レベルの読み込み具合は GetAsyncLoadPercentage( ) を使って割合を取得します。非同期で読み込んでいるパッケージの割合を百分率で返し、引数で指定したパッケージが無い場合は-1が返ってきます。 これを使ってロード中のFAsyncPackageの読み込み具合を取得し、それをSprogressBarにバインドすることでインジケーターを更新します。 また、プロジェクトをパッケージして実行しないと動作しないことに注意してください。スタンドアローンでもダメです。 docs.unrealengine.com

ローディング画面の再生は毎度お馴染みのFLoadingScreenAttributesとMoviePlayerで再生を行うので、詳細は省略します。詳しくは下記事を参考にしてください。
suuta-blog.hatenablog.com

問題点

f:id:suuta1123:20200731225736p:plain

プロジェクトをパッケージングしないと確認できない

前述したように、プロジェクトをパッケージして実行しないと動作しません。正確にはEDL(Event Drive Loader)が有効になるパッケージ時(パッケージ設定でデフォルトでオンになっています)に動作します。スタンドアローンで確認出来るといいんですけど…

f:id:suuta1123:20200802142630j:plain

ロードが早すぎる

マジで速いです。ゴーストオブツシマの比じゃなく速いですw これならローディング画面いらないし、画面暗転の方が良いです。 テストのために重たいレベルを作ろうと思い、ElvenRuinsのアクターを適当にコピーし10,000アクターくらいのレベルを用意しました。 しかし、1秒くらいでロードが完了してしまいました... 実際に発売されるタイトル級のレベルを用意しないと検証できないんですかね? どれくらいのアクター数なんでしょうか。

おわりに

GetAsyncLoadPercentageを使ったロード進捗の取得は動作しましたが、パッケージングしないと確認できませんでした。 どうやらスタンドアローンやPIEでOpenLevelした時とは動作が異なるっぽい? エディターで確認したいなら、スタンドアローン時にFAsyncPackageを使用するようにするか、 GetAsyncLoadPercentage以外でロード進捗をカウントする方法をとらないといけないようです。どちらにせよエンジンコードいじらないといけないですね...

UE4 もっとローディング画面②

概要

前回の記事では、非同期ローディング画面のデザインをUMGから行えるようになったよ! っていう内容でした。ただ、ロードアイコンだけどうにかしたいな(笑)っていう課題もあった。 今回は自分なりのローディングアイコンへの回答と、その過程でいろいろ試したことの紹介です。

マテリアルで

f:id:suuta1123:20200701005433j:plain

始めに思い付いたのがマテリアルだった。前にマリオオデッセイ風のHPゲージを作ったので、それを再利用しました。 Time式で経過時間をとってテクスチャを回転させています。作るのにあたってヒストリアさんの記事を参考にさせていただきました。

historia.co.jp

で、ロード画面で(OpenLevel中に)動くのかどうかというと動かなかった... まぁ何となくそんな気はしてました。そんなに甘くないよね。 ただ、実行する度に回転の初期位置が変化しているので、一度動いてから停止しているのかなと思いました。気になって調べてみるとTime式に原因がある感じでした。 もんしょさんの記事によると、 Time式はパーシスタントレベルの経過時間を計測しているらしく、パーシスタントレベルが変わるとリセットされるみたいです。つまり、ロード画面表示からOpenLevel( )呼び出しまでの間は動作するけど、 肝心のロード中はパーシスタントレベルが読み込めてないから動作しないのかもしれません。

monsho.blog63.fc2.com

IgnorePauseが原因

ブログ書いてる時に気づいたのですが、Time式のIgnorePauseをTrueにするとロード中でもTime式が有効になるっぽいです。 PauseといったらSetGamePauseのPauseだと思っていて、あまり気にしてませんでした。マテリアルも使えるってことですね。

f:id:suuta1123:20200701145909j:plain

Widget

続いてWidgetです。UMGエディターに公開されたSlateみたいなもので、ThrobberやCircularThrobberと同じものです。ThrobberやCircularThrobberがロード中でも動くんだったら 動くだろうということで、SlateのSSpinningImageをWidget化しました。SImageを指定されたパラメータで回転させるだけのWidgetです。Imageはマテリアルでも使用した マリオオデッセイのHPゲージマスク用テクスチャを使用します。

SpinningImage.h


SpinningImage.cpp


予想どおり動いてくれた。動いてくれたのは良いですけど、良い方法ではないですよね。SlateいじるならUMGで編集する意味がないです。 Widget自身が複雑なアニメーションを必要とした場合に対応できなくなります。(Slateでのアニメーションが難しい) ただ回転するロードアイコンが必要な場合はこれでもいいんですけどね。

アニメーションで

一番最後に思い付いたのがUMGアニメーションだった。1番簡単だし、慣れてる方法だ。ただ、作る前から1つ懸念要素があった。もう耳にタコな話だが "ヒッチ(カクつき)が起きるかどうか?" ということ。LoadingScreenAttributesを使わず重たいレベル(ElvenRuins)をOpenLevelするとUMGのアニメーションは必ずヒッチが起きる。いくらSWidgetに変換したからといっても、もとはUMGだからヒッチは起きるだろうと思っていた。おそらくこの偏見のせいで試そうと思ったのが最後になったのかもしれない。だが、いざ実装してみると思いのほかヌルヌルと動いてくれた… っと思ったのはつかの間、定期的にアニメーションの動作が鈍くなった。この時は原因が分からなかったが、動いた満足感であまり気にしないことにした。実際、AAAタイトルでもヒッチが起きているタイトルもあったからだ。まぁ、それはただ処理落ちしてるだけかもしれません。それ以降からロード中に一切動かなくなることだけ避け、処理が重いタイミングだけはヒッチが起きても妥協できるようになった。

カーブシーケンサーが原因

Time式のIgnorePauseと同じようにアニメーションにも落とし穴があった。アニメーションにもTimeLineノードのようなカーブエディタもがあり、 デフォルトでカーブの値が補完されていて変な挙動になっていたようだ。カーブをリニアにすると思ったように動作してくれた。 UMGのデザインから確認すると補完されておらずリニアなアニメーションをしているのだが、実行すると補完されたアニメーションが再生されていたため気づかなかった。

まとめ

f:id:suuta1123:20200701000041p:plain

ブログ書きはじめる前までは 「Widget > アニメーション でマテリアルはつかえねぇな...」って結果だったから、用途に応じて使い分けよう! というまとめのはずだった。でも、全部動いてしまったので好き方法で実装しようという結論になってしまった。 現時点では設計とか負荷とか詳しいことはわかりませんが、いつかそれらを意識しながら実装出来るようになれたらいいなと思います。 今回はFFVII リメイクのローディングアイコンのトレースが出来たのでひとまず満足です(´ω`)

動画

それぞれの方法でローディング画面を再生

youtu.be

FFVII リメイクのローディング画面っぽいやつ

youtu.be

UE4 もっとローディング画面➀

概要

前の記事でActionRPGの非同期ローディング画面のサンプルをもとにカクつかないロード画面を作った。ただ、ロード画面UIのレイアウトを凝りたい時にSlateから作るのはしんどいので、 UMGで作ったUIをローディング画面で使えるようにした。また、その過程で得た知見も一緒にまとめてみた。

振り返り<カクつかないのが最低条件>

はじめてのローディング画面

f:id:suuta1123:20200624215349j:plain UE4ではじめてローディング画面を作った時はこんな感じだった。OpenLevelの前にローディングのUIを表示して2秒間Delayさせてそれっぽく見せていた。 もちろんOpenLevel中はヒッチが起きるので、ロードアイコンが止まってしまい …ローディング画面とは?

ActionRPGの非同期ローディング画面

次にヒッチが起きないように ということでActionRPGのサンプルと参考に非同期ローディング画面を試した。SlateとMoviePlayerを使用し、BlueprintFunctionLibrary経由でローディング画面を再生した。 Slateはメインのゲームスレッドとは別スレッドで動作するということで、OpenLevel中でもカクつかずにローディング画面が再生された。冒頭でも言ったように最低条件はクリアしたが、肝心のUIデザインが Slateからしか行えないというオチだった。Slateわかんねぇよ、ぴえん。 ただ、デフォルトで用意されているアニメーションのついたThrobber、CircularThrobber、SpiningImageなどを使えばいい感じに出来る。

やってみる

ActionRPGではメインGameモジュールの外部にLoadingScreenモジュールを作ってBlueprintFunctionLibrary経由でMediaPlayerでローディング画面を表示させた。今回も仕組みは同じだけど、LoadingScreenモジュールは作らない。


USuutaGameInstance.h


USuutaGameInstance.cpp

BindLoadingScreen( )ではエディターで指定したUMGを、UUserWidget::TakeWidget( )でSWidgetに変換。 BeginLoadingScreeとEndLoadingScreenでMoviePlayerの再生・停止を任意のタイミングで行える。 今回はFCoreUObjectDelegatesのPreLoadMapとPostLoadMapWithWorldにバインドし、 レベルのロード開始・終了のタイミングに自動で再生・停止を行うようにしてある。


BPFunctionLibrary.cpp

デリゲートせず、手動で再生・停止を行う時のためにBPFunctionLibraryにまとめる。また、デリゲートの場合は引数が決められているので FLoadingScreenAttributesの設定が面倒だが、手動の場合は引数で渡してやることができるので、オーバーロード関数を作るのもいいかもしれない。

エディター側の設定

UMGで編集

f:id:suuta1123:20200625003658j:plain

BP_SuutaGameInstance

f:id:suuta1123:20200625215443j:plain f:id:suuta1123:20200625215439j:plain クラスのデフォルトからロード画面に使うUMGを選択、Initの親関数呼び出しを行う

ロード画面の呼び出し

f:id:suuta1123:20200625002523j:plain これは手動で呼び出した場合のノードです。 デリゲートした場合はPlayUMGLoadingScreen( )を呼ばなくても、OpenLevel( )するだけでロード画面が再生されます。 スタンドアローンでプレイすることを忘れずに。ActionRPGの非同期と違って、デザインの反映が簡単に行えるようになってよかった。ただ、ロードアイコンがエンジンのWidgetなので自作のアイコンを用意できるようにしたい。

動画

youtu.be

参考

https://answers.unrealengine.com/questions/357232/view.htmlanswers.unrealengine.com

UE4 マリオオデッセイみたいなHPゲージを作る

はじめに

今更ながらマリオオデッセイをプレイしてみて作りたくなったので、こんな感じのやつを作っていく。
youtu.be

オパシティマスク用のテクスチャを用意する

f:id:suuta1123:20200224150020j:plain テクスチャサイズは自由ですが、テクスチャの中心から外周・内周までの半径の比率だけ決めておくと良いです。今回は1.0:0.8にしてあります。決めておかないと、マスクが大きすぎてHPゲージが表示されないなど、後々で困ります(困りました)。あとは、CompressinのCompression settingsをMasksにするのを忘れずに

HPゲージのマテリアルを作る

MF_CircleTop

f:id:suuta1123:20200224153506j:plain TextureCoordinateのTilingをU・Vともに2.0にする。

M_HP①

f:id:suuta1123:20200224155518j:plain HPゲージの色のついた部分の表示領域の調整です。テクスチャでマスクをかけるので、マスク領域にあった表示領域に調整してください。テクスチャを用意するときに決めた比率で設定すれば表示されると思います。

M_HP②

f:id:suuta1123:20200224160036j:plain 最後に、今回はUIで使うのでマテリアルのMaterialDomainをUserInterfaceに、BlendModeをMaskedにする。

BPからHPのパラメーター操作する

Beginplay

f:id:suuta1123:20200225162528j:plain PlayerController側にWidgetとDynamicMaterialInstanceの変数を用意し、GetDynamicMaterialでHPのマテリアルを取得する。

Set HUD HP

f:id:suuta1123:20200225162533j:plain HP設定用のパラメーターは0~1.0で、減った量である背景色(グレー)の割合を設定しています。 HPが0なら1.0、1なら0.67、2なら0.33、3なら0.0。 HPは0~3なので、1.0からHPを3で割った数値を引いた値をSetScalarParameterValueに渡しています。

動画


参考記事

ai-gaminglife.hatenablog.com
pafuhana1213.hatenablog.com
limesode.hatenablog.com

UE4 非同期ローディング画面 試す

概要

前回、画面がカクつかないようにローディング画面をつくったけど、ローディング画面もカクついてしまった。 そこで、UE4無料サンプルプロジェクト<ActionRPG>を参考に非同期ローディング画面を実装してみた。 その中で実装に手こずったことをまとめた。

非同期ローディング画面とは?


非同期ローディング画面については、こちらの記事で詳しいことが書かれています。簡単にいえば、カクつかないローディング画面です。 LoadStreamLevel()がメイン処理の裏でレベルをロードをしてくれるのと同じく、処理を止めずにロード画面を表示してくれます。

やってみる

プロジェクトを作る

【プロジェクト】: C++
【プロジェクト名】: LoadingScreen

f:id:suuta1123:20191207225758j:plain
SourceにAsyncLoadingフォルダを追加して
"AsyncLoading.h"
"AsyncLoading.cpp"
"AsyncLoading.Build.cs" を追加。

私はUE4エディターからC++クラスを生成するのではなく、VisualStudio側から追加しました。
どちらでも良いと思いますが一応。

モジュール

ここが一番理解できなかった所... 非同期ロードはMoviePlayerを使ってロード画面を表示している。
しかし、ただMoviePlayer.hをインクルードしようとしても上手くいかなかった。結論から言うと ビルドする際にモジュールとして登録されてなかったっぽい? まだよく分かっていない...

➀ LoadingScreen.uproject をいじる。

f:id:suuta1123:20191207232804j:plain

デフォルトで生成されたコードに

{
    "Name": "AsyncLoading",
    "Type": "ClientOnly",
    "LoadingPhase": "PreLoadingScreen"
}

を追加する。

② LoadingScreen.Build.cs AsyncLoading.Build.cs を書く。

f:id:suuta1123:20191207235135j:plain

using UnrealBuildTool;

public class LoadingScreen : ModuleRules
{
    public LoadingScreen(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

        PublicDependencyModuleNames.AddRange(
            new string[] {
                "Core",
                "CoreUObject",
                "Engine",
                "InputCore",
                "HeadMountedDisplay",
                "AsyncLoading",
                "MoviePlayer",
                "Slate",
                "SlateCore"
            }
        );
    }
}

f:id:suuta1123:20191207235141j:plain

using UnrealBuildTool;

public class AsyncLoading : ModuleRules
{
    public AsyncLoading(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

        PublicDependencyModuleNames.AddRange(
            new string[] {
                "Core",
                "CoreUObject",
                "Engine",
                "InputCore",
                "HeadMountedDisplay",
                "AsyncLoading",
                "MoviePlayer",
                "Slate",
                "SlateCore"
            }
        );
    }
}

まんまコピぺする。

※ 2020/3/24 追記

以下の③Generate Visual Studio project filesをする。の工程は
C++ソースの➀・②の”AsyncLoading.cpp”、”AsyncLoading.h” まで作った状態で行ってください。

③ Generate Visual Studio project files をする。

f:id:suuta1123:20191208000215p:plain これでモジュール設定は完了しました。
.Build.csファイルや .uprojectファイルにビルドするモジュール範囲を指定せず、
Generate Visual Studio project filesをするとVisual StudioにAsyncLoadingフォルダが作られないので注意

C++ソース

➀ AsyncLoading.h

f:id:suuta1123:20191208004004j:plain

#pragma once

#include "ModuleInterface.h"
#include "Modules/ModuleManager.h"


class IAsyncLoadingModule : public IModuleInterface
{
public:

    static inline IAsyncLoadingModule& Get()
    {
        return FModuleManager::LoadModuleChecked<IAsyncLoadingModule>("AsyncLoading");
    }

    virtual void StartInGameLoadingScreen(bool bPlayUntilStopped, float PlayTime) = 0;
    virtual void StopInGameLoadingScreen() = 0;
};
② AsyncLoading.cpp

コードが長いので文字だけ

#include "AsyncLoading.h"
#include "SlateBasics.h"
#include "SlateExtras.h"
#include "MoviePlayer.h"
#include "SThrobber.h"


struct FAsyncLoadingScreenBrush : public FSlateDynamicImageBrush, public FGCObject
{
    FAsyncLoadingScreenBrush(const FName InTextureName, const FVector2D& InImageSize)
        : FSlateDynamicImageBrush(InTextureName, InImageSize)
    {
        SetResourceObject(LoadObject<UObject>(NULL, *InTextureName.ToString()));
    }

    virtual void AddReferencedObjects(FReferenceCollector& Collector)
    {
        if (UObject* CachedResourceObject = GetResourceObject())
        {
            Collector.AddReferencedObject(CachedResourceObject);
        }
    }
};


class SAsyncLoadingScreen : public SCompoundWidget
{
public:
    SLATE_BEGIN_ARGS(SAsyncLoadingScreen) {}
    SLATE_END_ARGS()

    void Construct(const FArguments& InArgs)
    {
        static const FName LoadingScreenName(TEXT("/Game/UI/Texture/Loading"));
        LoadingScreenBrush = MakeShareable(new FAsyncLoadingScreenBrush(LoadingScreenName, FVector2D(512, 512)));

        FSlateBrush* BGBrush = new FSlateBrush();
        BGBrush->TintColor = FLinearColor(0.034f, 0.034f, 0.034f, 1.0f);

        ChildSlot
        [
            SNew(SOverlay)
            
            + SOverlay::Slot()
            .HAlign(HAlign_Fill)
            .VAlign(VAlign_Fill)
            [
                SNew(SBorder)
                .BorderImage(BGBrush)
            ]
            + SOverlay::Slot()
            .HAlign(HAlign_Center)
            .VAlign(VAlign_Center)
            [
                SNew(SImage)
                .Image(LoadingScreenBrush.Get())
            ]
            + SOverlay::Slot()
            .HAlign(HAlign_Fill)
            .VAlign(VAlign_Fill)
            [
                SNew(SVerticalBox)
                + SVerticalBox::Slot()
                .VAlign(VAlign_Bottom)
                .HAlign(HAlign_Right)
                .Padding(FMargin(30.0f))
                [
                    SNew(SCircularThrobber)
                    .Visibility(this, &SAsyncLoadingScreen::GetLoadIndicatorVisibility)
                    .NumPieces(15)
                    .Period(1.25f)
                    .Radius(40.f)
                ]
            ]
        ];
    }

private:

    EVisibility GetLoadIndicatorVisibility() const
    {
        bool Vis = GetMoviePlayer()->IsLoadingFinished();
        return GetMoviePlayer()->IsLoadingFinished() ? EVisibility::Collapsed : EVisibility::Visible;
    }

    TSharedPtr<FSlateDynamicImageBrush> LoadingScreenBrush;
};


class FAsyncLoadingModule : public IAsyncLoadingModule
{
public:

    virtual void StartupModule() override
    {
        LoadObject<UObject>(nullptr, TEXT("/Game/UI/Texture/Loading"));

        if (IsMoviePlayerEnabled())
        {
            CreateScreen();
        }
    }

    virtual bool IsGameModule() const override
    {
        return true;
    }

    virtual void StartInGameLoadingScreen(bool bPlayUntilStopped, float PlayTime) override
    {
        FLoadingScreenAttributes LoadingScreen;
        LoadingScreen.bAutoCompleteWhenLoadingCompletes = !bPlayUntilStopped;
        LoadingScreen.bWaitForManualStop = bPlayUntilStopped;
        LoadingScreen.MinimumLoadingScreenDisplayTime = PlayTime;
        LoadingScreen.WidgetLoadingScreen = SNew(SAsyncLoadingScreen);
        GetMoviePlayer()->SetupLoadingScreen(LoadingScreen);
    }

    virtual void StopInGameLoadingScreen() override
    {
        GetMoviePlayer()->StopMovie();
    }

    virtual void CreateScreen()
    {
        FLoadingScreenAttributes LoadingScreen;
    }

};

IMPLEMENT_GAME_MODULE(FAsyncLoadingModule, AsyncLoading);

ほとんどActionRPGのコードです。

static const FName LoadingScreenName(TEXT("/Game/UI/Texture/Loading"));
LoadObject<UObject>(nullptr, TEXT("/Game/UI/Texture/Loading"));

パスの文字列は自身のプロジェクトにあったものを指定してください。
ローディング画面の中心に表示される画像です。

③ MyBlueprintFunctionLibrary.h

次は、BPファンクションライブラリを作っておきます。
BPで実際に使う関数です。 ちなみにこれはエディターから生成しました。

f:id:suuta1123:20191208011240j:plain

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"


UCLASS()
class LOADINGSCREEN_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()
public:

    UFUNCTION(BlueprintCallable, Category = "Loading")
    static void PlayAsyncLoadingScreen(bool bPlayUntilStopped, float PlayTime);

    UFUNCTION(BlueprintCallable, Category = "Loading")
    static void StopAsyncLoadingScreen();
};
④ MyBlueprintFunctionLibrary.cpp

実装です f:id:suuta1123:20191208011237j:plain

#include "MyBlueprintFunctionLibrary.h"
#include "ASyncLoading/AsyncLoading.h"


void UMyBlueprintFunctionLibrary::PlayAsyncLoadingScreen(bool bPlayUntilStopped, float PlayTime)
{
    IAsyncLoadingModule& LoadingScreenModule = IAsyncLoadingModule::Get();
    LoadingScreenModule.StartInGameLoadingScreen(bPlayUntilStopped, PlayTime);
}

void UMyBlueprintFunctionLibrary::StopAsyncLoadingScreen()
{
    IAsyncLoadingModule& LoadingScreenModule = IAsyncLoadingModule::Get();
    LoadingScreenModule.StopInGameLoadingScreen();
}

BPから使ってみる

BP_GameInstance

f:id:suuta1123:20191208143101j:plain

GameInstanceにロードイベントを作り

BP_MoveLevel

f:id:suuta1123:20191208143105j:plain

レベルロードのトリガーとなるActorを作り、
そのActorのオーバーラップイベントでロードイベントを呼び出す。

Name型のリテラルを返すだけのマクロ

f:id:suuta1123:20191208145004j:plain

ちなみにOpenLevel()に渡すLevelNameは、ただレベル名を渡すだけなく、絶対パスを渡してやると検索が早くなるそうだ。

スタンドアローンからプレイ

エディターからは確認できないので、スタンドアローンから起動するのを忘れずに。
f:id:suuta1123:20191208150846j:plain f:id:suuta1123:20191208151048j:plain f:id:suuta1123:20191208151225j:plain

まとめ

結果、非同期ローディングの実装よりもモジュールのビルド関連に手こずった感があった。
大きなプロジェクトの様に広大なレベルを扱っているわけでもなく、ロードが遅いわけでもないので、
変化があまり感じられなかった。規模が小さいうちは簡単にできる他の方法でごまかせるんじゃないかな。 また、UMGでなくSlateなのでビルドしないとロード画面のUIの変更が反映されず、スタンドアローンでないと動作確認できないのが辛かった。


UE4 レベルのロードディング画面

ローディング画面について

大抵のゲームはゲーム開始時やステージ移動の際にローディング画面をはさむ。重たいステージを読み込むためには時間がかかり、プレイヤーが操作可能になるまでに時間がある。 ローディング画面はロードの進行度合いを示す場合もあるけど、ロード中にゲームがフリーズしたと思わせないための役割をしている。

OpenLevel と LoadStreamLevel

UE4ではレベル間の移動手段が2つ。
BPにはOpenLevel()LoadStreamLevel()がある。2つの違いは処理(レベルの読み込み)が終わるまで待つか・待たないか。 つまりOpenLevel()では重いレベルを読みこむと他の処理が止まり、画面がフリーズしたかのように見える。
ならLoadStreamLevel()は良いのかと言ったらそうじゃない。次のレベルを裏で先読みをして、任意にロードする際の処理を軽くするためにある。

LoadStreamLevel()にはMake Visible After Load(ロード後に表示するかどうか)のフラグがあり、チェックを入れないとレベルが表示されない。 "その場でロードを完了させて表示させたい"という意味ではOpenLevel()と変わらない気がする。 テンプレートのレベルでは分からないけど、InfinityBladeのレベルアセット読みこみではロード待ちを体感できる。

カクつかないロード画面が欲しい

ロードがカクつくのはどうしようもないので、ごまかすためにロード画面のUMGを作ってみた。しかしそんなに甘くはなかった... ロードするとUMGが一瞬カクついた。どうやらUMGの処理も止まるようだ。(アニメーションのないロード画面ならごまかせるかもだけど...)
今度は、UE4サンプルプロジェクトのActionRPGの非同期ローディングを参考に、カクつかないロード画面を実装したいなと思う。