アンリアる! C++入門編 ~対話形式で学ぶUnreal Engine~
BOOTHでUnreal Engine C++入門書を販売していますUnreal Engine上でC++を使って開発するために必要となる基礎知識を一冊の本にまとめた本です。 対話形式によるわかりやすい説明を目指しました。無料の試し読みも可能ですので、ぜひ読んでみてください!
[UE4] ローディング画面の作り方

Unreal Engineで異なるレベルを読み込むときは「Open Level」を利用するのが一般的ですが、「Open Level」は同期処理であり、レベル読み込み中にゲームがフリーズしているように見えてしまいます。
このため、専用のアニメーションを表示しながら非同期にレベルを読み込むことが一般的ですが、Unreal Engineで非同期にレベルを読み込むにはどのようにすればよいのでしょうか?

本記事では、Unreal Engineにおいてローディングアニメーションを表示しながら、非同期にレベルを読み込む方法を説明します。

OpenLevelを使ったレベル読み込み

最初に、ローディングアニメーションを表示せずに別のレベルを読み込む方法を説明します。
通常、Unreal Engineでレベルを同期で読み込む場合は「Open Level」を利用します。

Open Level

「Open Level」を利用すると、指定されたレベルが読み込まれるまで描画処理を含めたゲームの処理が止まるため、あたかもゲームがフリーズしたように見えてしまいます。
「Open Level」を実行する直前に画面を徐々にフェードアウトする処理にすれば、プレイヤーへの見え方としては多少はよくなりますが、暗転している時間が長いと正しく動作しているのかフリーズしているのかわからない状況が続くため、プレイヤーとしては不安になります。
このため別のレベルを読み込んでいる間は、何かしらのローディングアニメーションやゲームのTipsを表示しながら、非同期にレベルを読み込むのが基本になります。

ローディング画面付きレベル読み込み

Unreal Engineでローディング画面を表示しながら、新しいレベルを非同期に読み込むためには少し工夫が必要になります。
レベルの非同期読み込みの具体的な方法を理解してもらうために、ローディング画面付きのレベル読み込みを行うデモを作りながら説明していきます。

デモの仕様

今回作成するローディング画面付きレベル読み込みのデモの流れを次に示します。

  1. ゲーム開始時(Playボタンを押したあと)は、1つ目のLevel(Level Aとする)が最初に読み込まれている
  2. Bボタンを押すとローディング画面が表示され、2つ目のLevel(Level Bとする)が読み込まれる
    • Level Bの読み込みが完了すると画面上に「Level B Loaded」と表示される
    • Level Bの読み込みが完了するとローディング画面とLevel Aが消える
  3. Aボタンを押すとローディング画面が表示され、Level Aが読み込まれる
    • Level Aの読み込みが完了すると画面上に「Level A Loaded」と表示される
    • Level Aの読み込みが完了するとローディング画面とLevel Aが消える

ここからは、上記を実現する方法について説明していきます。

レベルの構成

レベルの非同期処理を実現するためには、非同期で読み込むレベルとこれらのレベルを管理するレベルを作り、必要になときに管理するレベルがレベルを非同期で読み込みます。
Unreal Engineでは管理するレベルをPersistent Levelとし、非同期で読み込むレベルをSub Levelとします。
Persistent Levelはゲーム中に1つだけ存在するレベルで複数のSub Levelを保持できることから、Sub Levelを管理するレベルとすることに適しています。

※「Open Level」はこのPersistent Levelを読み込むためのもので、Persistent Levelは同期的にしか読み込めないことを示しています。

LevelとSub Level

デモを実現するため、今回は次のようなレベル構成を考えます。
レベル名に示されている名前でレベルを作成してください。
同期読み込みしかできないPersistent Levelを切り替えることなく、ローディング画面の表示/非表示を動的に切り替えるため、ローディング画面もSub Levelの1つとしてPersistent Levelで管理することに注意してください。

レベル名
Persistent Level L_LevelGroup
Sub Level(Level A) L_LevelA
Sub Level(Level B) L_LevelB
Sub Level(ローディング画面) L_LoadingAnimation

レベルを作成したら、上記のレベル構成を実現します。
最初に、Level「L_LevelGroup」を開いた状態でLevelタブを開きます。
3つのSub Levelを追加し、すべてのSub LevelのVisibilityをOFFにすることで、Persistent Levelを読み込んだときにこれらのSub Levelが読み込まれていない状態にします。

レベル構成

レベル読み込み処理

ローディング画面を表示しながらSub Level(L_LevelAやL_LevelB)を非同期に読み込む処理の流れを次に示します。

  1. L_LoadingAnimationを「Load Stream Level (by Name)」を使って非同期に読み込む
  2. 「Load Stream Level (by Name)」の「Make Visible After Load」にチェックを入れて、L_LoadingAnimationの読み込み完了後にローディング画面が表示されるようにする
  3. 読み込み対象のレベル(L_LevelAもしくはL_LevelB)を「Load Stream Level (by Name)」を使って非同期に読み込む
  4. 「Load Stream Level (by Name)」の「Make Visible After Load」にチェックを入れて、読み込み対象のレベルが読み込まれたあとに表示されるようにする
  5. 「Unload Stream Level (by Name)」を使って古いレベル(L_LevelAを読み込んだ場合はL_LevelB)とL_LoadingAnimationを削除する

レベル読み込み処理

L_LevelAやL_LevelBを読み込む処理でアニメーション付きのローディング画面を表示させることで、ゲームが止まったように見えてしまうことを防ぐことができます。
同じような方法を用いることで、ローディング画面の代わりにゲームのTipsなどを表示することもできます。
ただし、新しいレベルを読み込んでいる間にも古いレベルは存在し続けるため、必要に応じてプレイヤーの入力を無効化するなど古いレベルでの処理を適切に行わないとバグの原因になるので注意しましょう。

レベル読み込み処理の実装

以上の処理をBlueprintで実装します。
実装の流れを次に示します。

  1. ローディング画面のUI(W_LoadingAnimation)を作成する
  2. L_LevelGroupとL_LoadingAnimationの間で通信するためのBlueprint Interface(BP_LoadingInterface)を作成する
  3. L_LevelGroupのBlueprintにローディング画面読み込み処理を追加する
  4. L_LoadingAnimationのBlueprintにL_LevelAまたはL_LevelBの読み込み処理を追加する
  5. L_LevelAとL_LevelBのBlueprintに読み込み完了後のメッセージを出力する処理を追加する
  6. 古いレベルとローディング画面を削除する

全体像を理解しやすくするために、デモで作成するファイル構成も示します。

ファイル構成

ここからは各手順について詳しく解説していきます。

1. ローディング画面のUIを作成する

レベルを読み込んでいる間に表示するローディング画面のUIを作成します。
今回のデモでは、User Widgetから派生させたW_LoadingAnimationというアセットを作成します。
W_LoadingAnimationには「Now Loading …」という文字を中央に配置し、1秒間隔で点滅するアニメーションを追加します。
Blueprintの画面を開き、Event Constructの先にPlay Animationノードを追加します。
作成したアニメーションを指定し、入力の「Num Loops to Play」を0に指定してアニメーションが無限にループするようにします。

W_LoadingAnimation (UI)

W_LoadingAnimation (Blueprint)

2. Blueprint Interfaceを作成する

L_LevelGroupは入力されたボタンに応じて読み込むレベルを切り替えます。
このため共通処理をL_LoadingAnimationに実装し、L_LevelGroupとL_LoadingAnimationをBlueprint Interfaceでつなぎ、引数としてレベル名を渡すことで実現します。

※共通処理を関数としてL_LevelGroupに実装する方法もありますが、共通処理の中身は非同期の処理を含むため実現できません。このため、暫定的な対応方法としてL_LoadingAnimationに共通処理を実装し、L_LevelGroupからBlueprint Interfaceを介して呼び出す形にしています。

Blueprint Interfaceのアセット名は「BP_LoadingInterface」とします。
関数LoadingAnimationLoadedを追加し、2つの入力を追加します。

入力名 意味
NextLevel Name 読み込むレベル
CurrentLevel Name 現在のレベル

BP_LoadingInterface

3. Blueprintにローディング画面読み込み処理を追加する

レベルを読み込むときに表示する、ローディング画面を読み込む処理を追加します。
今回のデモでは、ローディング画面をSub LevelのL_LoadingAnimationとして作成したため、これを読み込む処理を追加します。

Sub Levelの読み込みは「Load Stream Level (by Name)」によって行います。
引数のLevel Nameには、読み込むレベルの名前(ここではL_LoadingAnimation)を指定します。
ノードの右上に時計マークが付いていることからもわかるように、「Load Stream Level (by Name)」は非同期に実行されます。

「Load Stream Level (by Name)」の実行が完了すると、L_LoadingAnimationの読み込みが完了したことになります。
読み込みが完了したら、Blueprint Interfaceで作成したメッセージ「Loading Animation Loaded」をL_LoadingAnimationに対して発行します。
入力NextLevelにはキーボードのキーAが押された場合にはL_LevelAを、キーBが押された場合にはL_LevelBを指定します。
入力CurrentLevelには現在のレベル名が保存されているCurrentLevelを指定します。

L_LevelGroup (キーA、キーBのBlueprint)

ゲーム開始時(BeginPlayイベント発行時)はL_LevelAを読み込むため、Begin PlayにはキーボードのキーAが押された場合と同様の処理をつなぎます。

L_LevelGroup (BeginPlayのBlueprint)

次に、L_LoadingAnimationでイベント「Loading Animation Loaded」に対して行う処理を実装します。
ローディング画面のUIであるW_LoadingAnimationを「Create Widget」と「Add to Viewport」を使って表示します。

L_LoadingAnimation (Blueprint 1)

4. レベルの読み込み処理を追加する

ローディング画面がされたあとは、レベル(L_LevelAまたはL_LevelB)の読み込み処理を行います。
これはローディング画面の読み込みと同様に「Load Stream Level (by Name)」によって実現します。
引数のLevel Nameには、読み込むレベルを指定します。
ここでは「Loading Animation Loaded」に入力されたNext Levelを利用します。

レベルの読み込み処理は、レベルに含まれるデータ量によっては非常に時間がかかりますが、ほぼ初期状態のレベルを読み込む場合は一瞬で読み込み処理が完了してしまい、非同期読み込みの効果がわかりにくくなります。
そこで今回のデモでは「Load Stream Level (by Name)」の処理が完了したあとに「Delay」ノードを追加し、3秒間だけ待機したあとに読み込みが完了するようにします。

L_LoadingAnimation (Blueprint 2)

5. 読み込み完了後にメッセージを出力する処理を追加する

読み込み処理完了後、読み込んだレベルのBlueprintにて読み込みが完了したことを示すメッセージを出力させます。
今回のデモではこれを「Remote Event」を使って実装します。
イベント名はそれぞれ次のようにします。

L_LoadingAnimation (Blueprint 3)

イベント名 意味
L_LevelALoaded L_LevelAの読み込みが完了した
L_LevelBLoaded L_LevelBの読み込みが完了した

次にL_LevelAのBlueprintを開いてCustom Eventを作成し、イベント名を「L_LevelALoaded」とします。
「L_LevelALoaded」のあとに「Print String」ノードを追加し、イベントを受け取ったときに「L_LevelA Loaded」と出力するようにします。

L_LevelA

同様にL_LevelBについてもBlueprintでCustom Eventを作成します。

L_LevelB

6. 古いレベルとローディング画面を削除する

L_LoadingAnimationのBlueprintに戻ります。
「Remote Event」でイベントを送信したあとは、古いレベルとローディング画面を削除します。
最初に古いレベルを削除するため、「Loading Animation Loaded」に入力されたCurrent Levelを「Unload Stream Level (by Name)」に指定します。

レベルが削除されたらローディング画面を削除します。
ローディング画面のUIであるW_LoadingAnimationをL_LoadingAnimationから削除するため、「Reomve from Parent」を使います。
最後に「Unload Stream Level (by Name)」にL_LoadingAnimationを指定し、ローディング画面のレベルを削除します。

L_LoadingAnimation (Blueprint 4)

これでデモの作成は完了です。

動作確認

ここまでで作成したローディング画面付きのレベル読み込みを試してみましょう。

L_LevelGroupを開いた状態でプレイボタンを押すと、ローディング画面「Now Loding …」がしばらく表示されたあとに「Level A Loaded」と表示されてローディング画面が消えます。
この状態でキーボードのキーBを押すと再びローディング画面が表示された後に「Level B Loaded」と表示されます。
同様にしてキーボードのキーAを押すとローディング画面が表示された後に「Level A Loaded」と表示されます。
レベルの読み込み中は、ローディングのアニメーションが表示されていることがわかります。

ローディング画面 デモ

※ローディング処理中のキー入力を無効化していないため、ローディング画面表示中にキーを押すと正常に動作しない可能性があります。本来であればローディング処理中は入力を無効化するなどの処理が必要になります。

まとめ

ローディング画面を表示しながらレベルを読み込む方法について説明しました。
レベルの非同期読み込みは手順がやや煩雑ですが、読み込み中にフリーズしたように見えてしまう同期読み込みと比較すると、UIの観点で明らかに向上しているため導入する価値があります。
また本記事で説明したレベルの非同期読み込み処理は、オープンワールドを採用したゲームなどで行われている、プレイヤー周辺のみのデータをローディング画面なしに非同期に読み込むときにも利用されています。
ぜひこの記事を参考に、Unreal Engineにおけるレベルの非同期読み込みに挑戦してみてください。