シリーズ最終回となる本記事では、動画では割愛していたゲームモードの処理全体を細部まで説明します。
これでLumenを活かしたきれいな画面で、かつ、ワープによるチラつきなしで、もっとリアルな8番出口風脱出ゲームが自作できるようになりますね。
シリーズ
- 基本的な仕組みと道の生成削除
- 通路生成方式での無限回廊の完成
- ワープ装置方式との違いで増える問題と解決
- ゲームシステムの完成(ゲームモード全体解説)★今回
ゲームを初めて作りたい方には、より簡単なワープ装置方式をおススメしています。
アンリアルエンジン自体の使い方から動画で詳しく説明しているので、そちらも是非ご覧ください。
全体の処理構成
ゲームモードのイベントグラフは、8個のカスタムイベントと2個の関数で計10個の処理フローに分かれており少し複雑ですが、
基本的には下記3つのきっかけなので、そこから見ていけばOKです。
- ゲーム開始時
- BeginPlay:出題リスト・ステージの作成初期化と情報保存
- Startトリガ接触
- Start Stage: ステージ演出開始
- Front/Backトリガ接触 または プレイヤーがダメージを受けたとき
- Judge Choice:出題と回答の正誤判定して次の出題
ひとまず全体をお見せします。
BeginPlay:出題リスト・ステージの作成初期化と情報保存
ゲーム開始時に呼ばれ、ゲーム状態の初期化と、常に保持したい(何度も検索したくない)情報の取得保持を担当します。
Init List:違和感リスト初期化
まず、Level Arrayという違和感を管理するレベルインスタンスたちを、アクタの「クラス参照」という紫色の型の配列にあらかじめセットしてあります。
「変数の型」→「アクタ」→「クラス参照」として、配列にし、一旦コンパイル後、デフォルト値の+ボタンから出題するブループリント化したレベルインスタンスクラスを追加しておきます。
その配列の長さと同じ長さの配列変数Diff Listに0,1,2…のインデックスを作成します。
このDiff Listがまだ正解していない問題リスト(違和感)の番号となります。
ワープ装置作戦のときはこのイベント内でDiff+番号でファイル名のリストを作っていましたが、レベルインスタンスをスポーンさせるときに指定するクラス名を動的に作れるのかわからなかったので、名前ではなくて番号を管理するようにしました。
同じ処理を完全クリアしたあと再度遊べるようにするためのリセット時からも呼ばれるので、最初にDiff List変数のClearも必要です。
Init Stage: 初期ステージの作成
ステージの初期化処理です。
開始時のレベルには控室しかない状態になっています。
ここでやることはステージを作ってからプレイヤーをそこに移動する処理です。
- プレイヤーを控室に移動
- 古いステージを削除
- 最初のステージを作成
- プレイヤーをステージにワープ
ゲーム開始時は、1はもともとそこにいるし、2の古いステージはない状態なので、3からでよいですが、リセット時の処理と共通化しています。
無駄をなくしたい場合は前半後半で2つのカスタムイベントに分割してもよいと思います。
1. プレイヤーを控室に移動
Init flag 初期化が必要というフラグをおろします。この処理全体のどこでも構いません。
次に、Set Actor Transformでプレイヤーを控室内に置いたPlayer Startの位置にワープさせます。
位置だけでなく、プレイヤーの向きもリセットしたいため、Locationではなく位置・回転・スケールをもつTransformを使います。
そのあと、Set Control Rotationでプレイヤーカメラの回転をリセットします。
(キャラクターの向きとカメラの向きが一致している場合は不要です。)
2. 古いステージを削除
ひとつ前の通路を消す処理は(いま時点必要ありませんが、)なんらかの理由でステージ開始せずにリセットするとき用です。
今までいたステージを消すのは通常のリセット用です。
どちらもRemove Stage関数で処理していますが、「Remove Old Stage」はもう1か所Startに接触したステージ開始時にも呼ばれるので、さらにカスタムイベント化しています。
ぶっちゃけ便利になっていないので、RemoveOldStageイベントはいらないです(;^ω^)
Remove Stage関数
Road入力で渡した古い/新しい通路のレベルインスタンスアクタをFor Each LoopですべてDestroy Actor(消去)するのと、
そのステージとセットで出していた出題用の違和感レベルを消しています。
Is Validで存在しているか確認は、ゲーム開始時など空っぽで来る場合を考慮しています。
ここまででリセット時もゲーム開始時と同じく控室以外ないレベル状態になります。
3. 最初のステージを作成
開始時の最初のメイン通路とその前後のサブ通路をスポーンさせて管理用変数に格納します。
SpawnActor from ClassノードでClassにメイン通路・サブ通路を設定するのと、「Spawn Transform」入力ピンを右クリックから分解してLocationとRotationを指定します。
※この辺は以前の記事を見てください。
そのあとDiff Classという次の出題用レベルインスタンスのクラスが中身入っていればスポーンさせてCurrent Level変数に保存しています。
Diff Classはゲーム開始時はまだセットしていないので動きません。リセット時はInit Stageよりも先に動く次の出題を決めた段階で中身が入っています。
ゲーム開始時は常に違和感なしルートが表示されて進むだけなのに対し、リセットされた場合はいきなり出題される場合もある仕様を再現しています。
4. プレイヤーをステージにワープ
ステージが完成するのを1秒待って、Set Actor Locationで開始地点にプレイヤーをワープさせたらゲームが開始します。
1秒待っている間にBeginPlayに戻ってこの次に記載している残りの処理が先に動きます。
1秒で足りるかは微妙なので、もっと長くしておくか、もっと厳密には、開始できる状態になったことを通知を待つみたいなほうがいいかもしれません。大事なもののBeginPlayからゲームモードに準備できた信号を送る的な。
その他:開始地点・おじさんの検索と変数への保存
BeginPlayの2つのカスタムイベント後の残件です。
Get All Actors Of Class with Tagノードで「PlayStart」のタグ付きのTargetPoint(最初のメイン通路のStart位置)を検索します。
条件にあう全てのアクタを探す処理なので配列で取得されますが、レベル上に1つしか置いてない状態にして0番目(先頭)の要素を取り出し、Get Actor Locationで位置を変数に保存します。
これはゲーム開始時やリセット時(Init Stageの最後で)プレイヤーをステージにワープさせるために使います。
InitStage内でDelayが挟まるのでこっちが先に動きますが、処理順がややこしいので、一番最初にやってもいいと思います。
おじさんは、Get Actor Of Classでおじさんクラスのアクタを探しておじさんクラスの変数に保存します。
これはステージ開始時におじさんを歩かせたり変身させたりする指示を出す用です。
Start Stage: ステージ演出開始
Remove Old Stageを呼んで古いステージを消したあと、
2フレーム待って古いレベルの情報が消えてから、
おじさんを歩かせるのと、おじさんの見た目変化のインターフェース関数があれば実行します。
おじさんの歩行や「インターフェース」の件、詳しくは前回の記事で説明しています。
Judge Choice:出題と回答の正誤判定して次の出題
クイズとしての回答したあとの処理で、このゲームの中心的な処理です。
- 今回の正誤判定とクリア演出
- 次回の出題決め・ステージ作り
の大きく2パートがあり、後半の処理はChange Stageというカスタムイベント化しています。
長すぎない程度に分けているだけで、分岐はしてないのでくっつけても問題ありません。
前半パート:今回の正誤判定とクリア演出
Front/Backいずれかのトリガに接触したときと、プレイヤーがダメージを受けたときにプレイヤーのBPから呼び出されます。
Boxを変数に保存
後半の処理で使うので、一旦変数に保存しているだけです。
成否判定とカウンタ更新
前回出題時に決めたCorrect Is Front変数(進むのが正解か否か)とプレイヤーの選択Is Front入力を比較し、
一致していれば正解でProgress Count進捗カウンタをプラス1し次の処理に進みます。
不一致なら0にリセットしJudgeChoice自体の処理は終了でChangeStageイベントに移ります。
違和感リスト更新
出題番号管理のDiff List配列変数からLevelListNumber今回出題していた番号を取り除きます。
その次のブランチは取り除かれた結果リストが空になった、つまり、違和感がすべて発見されたら完全クリアへの分岐です。そうでなければ脱出判定へ進みます。
完全クリアルート
完全クリア時は画面左上へのデバッグ表示でCompleteと表示するだけの簡素なものとしています。
その後、進捗カウンタを0リセットするのとInit Listで空になった出題リストを全復活させています。
脱出判定・脱出成功ルート
Progress Count進捗カウンタが9になったら脱出成功時の処理に移ります。それ以外ならそのままChangeStageイベントに。
脱出成功時は、画面左上へのデバッグ表示でStage Clearと表示するだけの簡素なものとしています。
その後、進捗カウンタを0に戻して違和感全回収を目指す周回ができるようにします。
派手な演出を加えたい場合はカスタムイベント化など分離したほうが管理しやすいかと思います。
本家8番出口の登る階段みたいなものが出したい場合はChange Stageに行かずに専用のレベルインスタンスを付け足せばできそうですね。
登った先のエンドロールはウィジェットでいいので、レベル切り替えなくても同じ表現はできそうです。
脱出失敗含め全ルート合流
Change Stageで次のステージを準備する処理をコールします。
後半パート:次回の出題決め・ステージ作り
Change Stageカスタムイベントでは、下記の処理をします。
- 次の出題番号を乱択
- 出題番号・出題クラス情報更新
- 正解情報更新
- 進捗案内看板の数字変更
- 次のステージ付け足し または リセット(ステージ初期化)
次の出題番号決め
Diff Listの長さ、つまり残りの違和感数の2倍-1までの数字からランダムにくじ引きして、違和感数未満の数値がでたらDiffList内のその番号に入っている違和感番号を次の出題番号とし、それより大きければ10000というありえない数字で通常ルートを選択したことにしています。
こうすることで、違和感アリなしの2択50%50%と、違和感アリの場合にどの違和感を出すかを1回の乱数で決めています。
乱数で引いた数字そのものではなくて、DiffList内のそのインデックスの中に入っているLevel Arrayのインデックスという2重のインデックスなので、ややこしいですが気を付けてください。
たとえばDiffListが[1,2,4,7]となっていて乱数が2の場合、Level Arrayの4番が採択されたことになります。
出題番号・出題クラス情報更新
「Level Index」がさきほどひいた乱数で「Diff List 残りの違和感リスト」の長さ未満だったら番号を取り出し「Level List Number 現在の出題レベル番号」を更新します。
「Correct Is Front 進むのが正解の出題フラグ」は違和感があるときは戻るのが正解なのでチェックを付けず、ないときはチェックを付けて前進するのが正解とします。
「Diff Class 出題するレベルクラス」をさきほど更新したLevel List Numberで「Level Array 違和感全部入りのレベルインスタンスクラス配列」から取り出し更新します。
このとき、違和感がない場合はLevel List Number =10000となっていてGetできませんがエラーにはならずに空が返ります。
看板変更
看板クラスのアクターをGet All Actors Of Classで検索し、すべてに対して更新された進捗カウンタの数字を入力して看板クラスのChange Numberイベントを実行します。
これも一応カスタムイベントにしたけど大した処理してないやつです。
ステージ更新
プレイヤーが攻撃を受けた場合はInit Flagという初期が必要というフラグが立った状態でここまで来ます。その場合は上で説明済のInit Stageでリセットのルートに進みます。
そうでない通常は、MakeRoad関数でFrontかBackのぶつかったトリガ装置のコリジョンの情報をもとに次消す予約と新しい通路を付け足して出題をします。
これも一応カスタムイベントにしたけど大した処理してないやつです。
おわりに
お疲れさまでした。
あとは演出とメニューですね。この辺りはネット上にたくさん情報があるので、本ブログではまだ紹介しておりませんが、それほど困らないかなと思っています。
この仕組みを前提とした相談等にはのれると思いますのでお気軽に質問ください。
最初に作った「すぽでぃふ」開発日記にはメニューの話なども出てきますが、詳しい解説はしておりません。
コメント