【UE5 初心者】球を転がして迷路を攻略するTPSゲームを作る(4) -迷路をつくる-

# はじめに
以下の点に注意して頂ければ幸いです。
・初心者による初心者のための記事です。文章に誤りのある場合がありますので注意ください。
・UE(Unreal Engine)のバージョンは5.5.3-39772772+++UE5+Release-5.5、OSはWindows10です。
・UE5のインストールについては完了している想定とします。
・本記事ではスクリプト(C++)は使用せず、UE5で使用されるビジュアルスクリプティングであるブループリントのみを使用します。
# 概要
本記事では新規のレベルを作成して、スタティックメッシュアクタを配置して迷路を作成します。さらに、スタート/ゴールのフラグの実装も行います。
# もくじ
- [1.迷路を作成する](#1.迷路を作成する)
  - [1-a.コースを作成する](#1-a.コースを作成する)
  - [1-b.スタートラインとゴールラインを作成する](#1-b.スタートラインとゴールラインを作成する)
- [2.ゲームの状態を設定する](#2.ゲームの状態を設定する)
- [3.ゲームプレイエリアを設定する](#3.ゲームプレイエリアを設定する)


# 1.迷路を作成する
まず迷路ステージ用の新規レベルを作成します。ファイル>新規レベルを選び、「基本」を選んで作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/800d30da-9116-4d23-bf29-b57e5c5d51f5.png)

コンテンツの下にLevelフォルダーを作成して、このレベルを保存します。名称は「Stage1」としてください。レベルの保存は上の方のボタンを押すかCTRL+Sで行えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/6cf8219d-f5f1-4bbe-93c0-37e9ace5d03e.png)

新規レベル作成時点ではプレイヤーのスタート地点であるPlayerStartがマップ中央になっていますが、今回コースをレベル中央に配置しようと思うので一旦ずらします。アウトライナーからPlayerStartを選択してギズモを操作するか、詳細からトランスフォームの値を変えて移動させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/36adb7f5-a9bd-4eea-ac26-6f813be8ad22.png)

## 1-a.コースを作成する
次にレベル内に迷路コースを作成していきます。迷路の壁にはスタティックメッシュかジオメトリ ブラシを用いることができますが、基本的にスタティックメッシュの方を使うことが推奨されているため、スタティックメッシュで作成します。
<a href=https://dev.epicgames.com/documentation/ja-jp/unreal-engine/geometry-brush-actors-in-unreal-engine?application_version=5.4>公式ドキュメント Geometry Brush</a>

:::note info
これまで、ジオメトリ ブラシはレベル デザインの主要な構成要素として使用されていました。その役割ははるかに効率的な スタティックメッシュ に引き継がれました。ただし、レベルとオブジェクトのプロトタイプを手早く作成する制作の初期段階や、特に 3D モデリング ツールを使用できないユーザーにとっては、引き続きこのジオメトリ ブラシが役立ちます。
:::

まずはウィンドウ>アクタを配置をクリックして「アクタを配置」ウィンドウを追加してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/0d696869-179f-4428-8ef7-859d2fca7732.png)

左のタブから「形状」を選択し、キューブをレベルビューポートまでドラッグしてレベル内に配置します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/f24ca060-8043-4ba1-aa5f-97e233516163.png)

トランスフォームを以下のように変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/067431f2-1c40-49b2-b71e-ca623993c4f8.png)

以下の３つの壁も作成していきます。
- 位置「X:0,<b>Y:-1000</b>,Z:200」回転「X:0,Y:0,Z:0」拡大「X:20,Y:1.0,Z:4.0」
- 位置「<b>X:-1000,Y:200</b>,Z:200」回転「X:0,Y:0,<b>Z:90</b>」拡大「<b>X:16</b>,Y:1.0,Z:4.0」
- 位置「<b>X:1000,Y:-200</b>,Z:200」回転「X:0,Y:0,<b>Z:90</b>」拡大「<b>X:16</b>,Y:1.0,Z:4.0」

こんな感じになればokです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/8f81bc53-f309-41dc-ba1f-af1d4e5705b3.png)

この壁を整理するために、アウトライナー内のレベル名を右クリックしてフォルダを作成します。Mazeフォルダと、その中にOutline,Inlineフォルダを作成し、Outlineフォルダに先ほど作った４つの壁を移動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/2bc5f2f2-ca66-4dc5-b329-e0ae33ced789.png)

後は迷路の内部の壁をInlineフォルダに配置していきます。設置済みのアクタはコピーできるので、それも利用しつつ自由に作っていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/05e36f28-9b98-42ef-ae8e-2ad04c50cdb5.png)

## 1-b.スタートラインとゴールラインを作成する
次に、迷路のスタートラインとゴールラインを作成します。これらにはイベントを関連づけてスタート/ゴールの処理を行いたいので、スタティックメッシュではなくブループリントで作成します。
コンテンツドロワーを開き、コンテンツフォルダ内にLevelElementフォルダを作成してその中にBP_StartLineを作成します。親クラスはアクタにしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/46cf6906-4143-424f-a5df-4104c1e912db.png)

BP_StartLineを開いてコンポーネントを追加します。平面を追加してDefaultSceneRootに設定して、そのルート下にBox Collisionコンポーネントを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/4521ebdf-e8ec-4fd0-85c3-abfb8efc1247.png)

今回、このBox Collisionにプレイヤーが侵入した際にスタート/ゴールの判定を行わせます。
まず、Box Collisonのトランスフォームを変更してライン上を覆うように判定を広げます。
次にパフォーマンス向上のために、可動性をムーバブルだと過剰なのでスタティックに変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/16d84e79-65eb-445e-863e-8880b518daf0.png)

次に、ゴールライン自体の当たり判定をなくして、プレイヤーが通過できるように変更します。
平面のコリジョンの項目から、コリジョンプリセットをBlockAllDynamicとなっているのをNoCollisionに変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/93d26e92-8e6d-4cc3-b8b4-d86c4da9d26b.png)

Box Collisionは変更を行わずとも機能しますが、可動性がスタティックの場合に最適化されているぽいので、OverlapAllDynamicからOverlapAllに変更します。

各コリジョンの状態(BlockやOverlapなど)について詳しくは公式ドキュメントが条件・各状態の図解を用いて説明しているので、そちらをご覧ください。
<a href=https://dev.epicgames.com/documentation/ja-jp/unreal-engine/collision-in-unreal-engine---overview>公式ドキュメント コリジョンの概要</a>

コンポーネントの設定は以上なので、次はロジックを実装するためイベントグラフに移動します。元からあるイベントを全て削除して、マイブループリントからBox Collisonコンポーネントを選択して詳細タブのイベントの欄からOnComponentBeginOverlap(何かが重なった際に一度呼ばれるイベント)の+をクリックしてイベントを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/811d30a9-4f99-4c59-814e-0bdd8c766c3d.png)

このイベントを重なった対象がプレイヤーである場合のみ処理をさせたいので、イベントのOther Actorピンを伸ばし、CastToBP_Characterノードを作成してプレイヤーであるかの判定を行います。このときキャスト成功時にはスタート処理を行いたいのですが、そういった処理は未実装であるため、代わりにPrint Stringノードを使ってイベントが正常に行われた際に画面上へメッセージを表示させるようにします。Cast To BP_CharactorノードからPrint Stringノードを伸ばし、In String(表示される内容)を「Start!」に変更します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/f0b32eee-84fd-46eb-9cc4-cba3ac08f60c.png)

一度チェックのために、BP_StartLineをレベルに配置してこれにプレイヤーが触れると画面左上に「Start!」が表示されるか確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/d785142b-9951-4c7e-a537-62734d5a91f4.png)

現状だと触れたたびにStart!が表示されると思います。今回はスタートの判定は一度だけ行いたいため、開始状態のフラグを用いて開始状態では判定を行わないように設定します。BP_Startlineのイベントグラフを開いて「Started?」変数を追加し、Branchノードを用いてFalse(開始していない場合)の場合のみ処理をさせるようにして、プレイヤーが初めて侵入した際にSetノードでStarted?のフラグをTrueに変更させます。(本来フラグはここで管理すべきではないですが、一時的に管理させています)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/4820d184-831c-4aa0-910e-efe5080a3504.png)

これでStart!が一度表示されるとそれ以降スタートラインを踏んでもStart!が表示されなくなったと思います。

次にゴールラインを作成します。基本的にはスタートラインと同様であるためスタートラインをコピーして作成して、その後調整を行います。
コピーして名前をBP_GoalLineに変更して、イベントグラフを開きます。以下のようにBranchの実行ピンと、セットStarted?ノードの値を変更して、Print Stringの値を「Goal!」にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/f7ac987c-455c-4743-ab67-1d9503757289.png)

:::note info
今回ゴールラインをスタートラインからコピーして作成しましたが、重複部分を親クラスとして作成してクラスを継承させて作成する方法もあります。今回はスタートライン/ゴールラインでしか使わないのと、構造が単純なので、そのままコピーして実装しています。
:::

BP_GoalLineをレベルに配置してゲームで試してみましょう。まず、StartLineを踏んでそれからGoalLineをふみます。
現状だとStartLineとGoalLineは個別にフラグを管理しているため、StartLine内でフラグが変更されてもGoalLine内のフラグは変更されずGoal!が表示されることはありません。これを解決するにはフラグをどこかで一元的に管理して参照する必要があります。
# 2.ゲームの状態を設定する

現状だと開始フラグの管理はStartLineとGoalLineが個別に行なっています。
ここでGoalLineのフラグを削除して、StartLineから参照させるようにすればフラグが一つになり、実装することはできますが、問題点としてGoalLineがStartLineへ依存することになります。

これはStartLineとGoalLineは逆でも実装可能であり、つまり本来同等であるオブジェクト間に無理やり依存関係を持たせているため、その関係がわかりにくくなっています。
一般的にこういった場合には、上位のオブジェクトが管理してStartLineとGoalLineがそこから参照するようにする方が自然で、関係性もわかりやすくなります。

ゲーム内のフラグを管理するためにUE5では以下のクラスが用意されています。
- GameInstance:レベルを跨いで保存できるため、ゲーム全体のフラグや合計スコアを管理するのに利用
- GameMode：ゲームの勝利条件のルールや、スポーン位置などのルールなどルールに関する部分を管理するのに利用
- GameState：現在のゲームの進行状況・進行時間の合計やすべてのプレイヤーで共有する必要のある情報を管理する
- PlayerState：プレイヤー個人の進行状態・スコア・旗取りゲームではプレイヤーが旗をもっているか？などの情報を管理するのに利用

また各クラスには専用の関数が用意されています。
例えばGameMode/GameStateではマルチプレイ向けの関数が用意されています。
<a href=https://dev.epicgames.com/documentation/ja-jp/unreal-engine/game-mode-and-game-state-in-unreal-engine?application_version=5.5>公式ドキュメント GameModeとGameState</a>

今回は小規模であるためこれらのフラグをいずれかに管理させます。
予めプレイヤーキャラクターを設定する際にBP_Gamemodeを作成しているので、Gamemodeで一元的にフラグ管理させたいと思います。BP_Gamemodeを開いてStarted?変数を追加します。
これで後はStartLineやGoalLineがこのフラグGamemodeからGET/SETすれば実装はできるのですが、GETで参照するのは問題ないのですが、依存先の変数を直接SETするのはよくないのと、それぞれ開始・ゴール時にフラグの切り替え以外の処理も行うため、代わりに関数を渡します。
BP_GamemodeにOnStartMazeとOnGoalMazeという関数を作成して、SET StartedノードとPrint Stringノードをつなげます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/7a0524c4-d93e-4211-b2dd-9b38a1c4bd7b.png)

後は、StartLineとGoalLineを修正していきます。
EventBeginPlayノードを作成して、BP_GameMode変数を作成して参照させます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/61dc4e0c-1438-4f7e-8f1a-8568a46c788a.png)

StartLine/GoalLineからStarted?変数を削除し、BP_Gamemodeから変数と関数を呼び出します。(以下はStartLineの場合)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/24139001-ab86-4fbd-b8c6-daa7091dd4aa.png)

両方に適応したらゲームを開始してみてください。StartとGoalの処理が行えるようになったと思います。
最後に、レベルビューポートでStartLineとGoalLineの位置と大きさを調整してください。これらのオブジェクトはスタティックメッシュをルートコンポーネントに設定しているため、メッシュのサイズを変更すると判定も自動的にスケールされるので再度BPで設定する必要はありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/fe3cad22-14ce-49d9-a3e4-2891323557ca.png)



# 3.ゲームプレイエリアを設定する
現状だと迷路の外周を通ることができ、迷路を攻略せずとも外からぐるっと回ってゴールできてしまいます。
ので、外周に見えない壁を配置してプレイエリアを設定します。
アウトライナーにPlayAreaフォルダを追加して以下の４つのキューブを配置します。
- 位置「X:0,Y:-1050,Z:250」回転「X:0,Y:0,Z:0」拡大「X:30,Y:1.0,Z:5.0」
- 位置「X:0,Y:1050,Z:250」回転「X:0,Y:0,Z:0」拡大「X:30,Y:1.0,Z:5.0」
- 位置「X:1500,Y:-0,Z:250」回転「X:0,Y:0,Z:90」拡大「X:21,Y:1.0,Z:5.0」
- 位置「X:-1500,Y:-0,Z:250」回転「X:0,Y:0,Z:90」拡大「X:21,Y:1.0,Z:5.0」

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/92633962-a4f4-4115-bcdb-a87006da00cd.png)

これらの壁を全て選択し、レンダリングのVisibleのチェックを外して非表示にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/2ceb3176-4c60-4f72-8506-6be5f898f589.png)

PlayerStartの位置をPlayAreaの範囲内に移動してゲームを開始してください。見えない壁によって外周へいくことが出来なくなったと思います。
ただ、カメラが見えない壁にも反応して移動しているのが気になります。これをカメラの衝突対象から見えない壁を除外することで対応します。

まず見えない壁を全て選択して、tagで検索してアクタのtagに任意の名前のタグ(今回はPlayArea)を追加することで、識別できるようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/8a8b2a86-a182-4992-a2e7-3ab4e0e02668.png)

次に、BP_Characterを開いてFollowCamera関数を開きます。Break Hit ResultのHit Actorピンを伸ばして、Actor Has Tagノードを作成してTagにPlayAreaを設定します。さらに、Branchノードを作成し、繋ぎこんでFalse(ActorがPlayAreaタグを持たない場合)の時のみカメラの位置調整の処理をするように変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/44f5e707-7fa6-4549-8ab4-fac6f843a258.png)

(散らかって分かりづらければ、適宜マクロや関数にまとめてあるいはコメントをつけて整理してください。)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992369/32ecf88c-8a48-4f11-8a68-968c159e3608.png)

これでゲームを開始してみてください。見えない壁によってカメラが移動することなく、かつプレイヤーはプレイエリアの外部に出ることができない状態になったと思います。

# おわりに
これで迷路は完成です。次回は現在のステージの情報を表示したり、クリア時のUIを作成していきます。
最後まで読んでいただき、ありがとうございました。