先回り通路生成方式で8番出口ライク再現#3

※本サイトはアフィリエイト広告を利用しています。

前回の続きで、通路生成方式で8番出口風なゲームシステムを作るために必要な課題と対策を解説します。

よっしー
よっしー

動画で未解決の課題もほぼ解決しています。

シリーズ

  1. 基本的な仕組みと道の生成削除
  2. 通路生成方式での無限回廊の完成
  3. ワープ装置方式との違いで増える問題と解決 ★今回
  4. ゲームシステムの完成(ゲームモード全体解説)
上記解説動画の中盤以降の内容の記事です。

前提おさらい

  • 開始時の通路も作るのでゲーム開始の瞬間は道がない
  • 進むか戻るか選択した時点で次のステージが作られる、かつ、選択した時点ではまだいまのステージは消せない=2つステージがある状態

看板の更新

動画では最後のほうで説明していますが、看板の表示方法を先に説明します。

メイン通路に入る直前にみる看板は、手前のステージに属するようにします。

そして看板のスポーン時(レベルインスタンスの一部として作られるタイミング)は非表示になるようにして、Front/Backに触れて正誤判定後に更新された数字で表示されるようにします。

すると、

  • 進むタイミングでは上図右上の看板が見える
  • 戻るタイミングでは上図左下の看板が見える
  • 進むタイミングで次のレベルの上図左下の看板は見えない(あるがまだ非表示)
  • 戻るタイミングで手前のレベルの上図右上の看板は見えない(Startの時点でレベルごと消去済)

という形で8番出口の看板表示の仕様を再現できます。

看板の処理は、

BeginPlayで0表示にしていたのをやめて、絶対使わない-999でDefaultの分岐に進むようにして、Defaultの分岐で非表示化しています。

正誤判定後に看板を更新する処理はそのままでよいと思います。

問題1:地面より先にPlayerやおじさんがスポーンして落下

プレイヤーやおじさんなどのNPC(ノンプレイヤーキャラクター)が地面より先にスポーンされて落下する問題です。

あらかじめステージを置くと、それが残って逆向きの通路と重なったりするのと、敵にやられたりしたきっかけでリセットすることも踏まえ、ゲーム開始時に最初の通路も作る仕様にしたのですが、

プレイヤーはゲーム開始と同時にスポーンされるため、床ができるより前にプレイヤーがいる状態になり、落っこちるという問題が起きました。

問題2:おじさんがさまよう

メイン通路が同時に2個存在する状況があるので、Get Actor of Classで探すとどちらか片方がみつかり、期待通り動けない事態が起きます。

問題3:演出の読み込みでカクつく

スポーンアクターでサブレベルを読み込む方式だと瞬間的にロードする形となり、重めな違和感演出を出そうとするとカクつく場合がありました。

解決

通路とは重ならない位置に控室となる常設の床を置き、ステージが出来上がってからワープしてゲームプレイを開始できるようにしました(問題1)。

プレイヤースタートのほか、一緒に無限ループしているおじさんもステージごとではなく1人を移動してもらう方式としているため控室にいれています(問題2)。
重くなりがちなナイアガラを使う演出はゲーム開始時に一旦スポーンさせておくことで、初ロード時の重い処理を開始時にあらかじめ済ませておく作戦としています(問題3)。

上の図で下のほうにある本当のプレイヤースタート地点をTag付きのターゲットポイントにしおき、BeginPlay時にその位置を変数に取得しておきます。

リセット時の、ステージを作る処理の最初で、控室に移動します。(ゲーム開始時はもともとそこにいますが別に問題ありません)

そのあとステージを作り、できてから(1秒待って)

保存しておいた位置に移動します

このとき、控室は暗闇にしてあり、そこから出るときに明るくフェードインする演出もつけています。これはポストプロセスボリュームの露出補正で実現できます。(ほかにも色々手段はあります)

よっしー
よっしー

ちなみに、ポストプロセスはキャラクターではなく、カメラが入ったら利くものなので3人称視点等の場合は意図しないタイミングで中に入らないように気を付けましょう。

次に、おじさんのほうは、
ステージができるタイミング(=前のステージでFrontかBackに触れた瞬間)ではなく、次のステージのStartにたどり着いた瞬間にワープさせて歩き始めるのが良いと思います。

ステージ開始のイベントで古い道を消して2フレーム待って古い経路アクターが消えて1つになってから、おじさんの歩行開始イベントを呼び出します。

よっしー
よっしー

もっと安全にやりたい場合は、新しいほうがスポーンされたBeginPlayからゲームモード経由で通知を送るとか、古いほうが消えたEndPlayから通知を送るのがよさそうですね。

※上図の一番最後のノードはあとで説明します。

呼び出されたおじさんのBPの歩行開始イベントでは、新しい経路アクターを探して、開始地点をもらい、そこにワープしてから歩くイベントを実行します。

これで、暗闇スタートから明転してメイン通路にいて、おじさんが歩いてくるあの状態が再現できて、次の通路まで進む/戻るとまた奥からおじさんが歩いてくる演出ができます。

よっしー
よっしー

本家8番出口だと違和感ありと思って引き返すときもおじさんはしばらくいるので、ワープ装置方式のときよりその仕様にも近づきますね。
(ちなみにワープ装置方式でもがんばればおじさんを連れて一緒にワープさせたり、Backのワープ位置でなんとかすることもできます)

おじさんの歩行イベント含めたBP全体は下記です。

問題4:おじさん見た目リセットタイミングずれ

出題用サブレベルのBeginPlayでおじさんのスケール変更などを行うと、まだ今いる古いほうのステージ上でおじさんが変化してしまうことになります。
また、EndPlayで通常状態に戻す処理もタイミングが悪く、Startのトリガに触れて古いステージが消えるタイミングでリセットされてしまうので、結果的に次のステージ側の演出がキャンセルされてしまう場合があります。

例) 巨人化の違和感直後に小人化の違和感が採用されると、小人化したあと巨人化が解除され普通になってステージが始まる

解決

基本的には、出題レベルのBeginPlayで変化させるのではなく、ステージ開始のタイミングで前のレベルの戻す処理を終わらせてから次のを実行するだけです。

今回は「インターフェース」という、その接続口をもっていれば呼び出す・持ってなければ何もしない仕組みを使って実装してみました。

それがさきほど説明を飛ばしたメインステージ開始時の最後の部分です。(右上に手紙アイコンがついている)

よっしー
よっしー

インターフェースの説明は割愛しますが、規格が決まっているので、キャストしなくても呼び出してみることができる(し、そのインターフェースもっていなければ無視される)という特徴があるので、これを活用してみました。

作り方は、まずコンテンツブラウザ右クリックからブループリント/ブループリントインターフェースを作り、

そのインターフェースを持つクラスがどんな機能を提供するかの定義(関数やイベントの入出力仕様ぎめ)をします。その機能自体の中身はここでは作らないのがポイントです。

SetOjisanという名前でおじさんクラスの引数を受け取って何かする関数を用意しました。

今度はこのインターフェースを持つサブレベル(レベルインスタンスアクター)を作ります。
※データ専用ではなくブループリントエディタを開いた状態

画面左のクラス設定の詳細から「実装インターフェース」にさきほど作ったインターフェースを設定します。すると画面左のインターフェースという欄にそのインターフェースクラスがもつ機能が並ぶので、それをダブルクリックすると、その機能のノードができます。

あとはBeginPlayでやっていたおじさんを変更する処理をそのイベントにつなぎ変えればOKです。上図はそこまでやった状態です。

EndPlayはステージ切り替えで古いステージが消されるタイミングで先に呼ばれる処理順に決まるので、そのままで構いません。

おじさんを変更する必要がないレベルはこのインターフェースを付けなければいいだけです。無視されます。

ナビメッシュサイズ問題

AI Move to などを使うためにはNavMeshが必要でしたね。通路が伸びていくのでそれに合わせる必要があります。

ただ、めちゃくちゃ広大にすると計算コストが高い&地面や壁がないと事前に歩行可能な場所を計算できないので、あらかじめ地面などの設置が必要になります。なのでこの案は不採用とします。

解決

実行時にナビメッシュを作る作戦です。あらかじめ作っておくよりは計算負荷があがりますが、広大につくるよりは負荷が低いというバランスをとった方法です。

Dynamic設定にして実際歩くおじさんの周辺だけを計算する仕組みとします。
それには、NavMeshInvokerという仕組みを使います。

おじさんクラスのコンポーネントに「追加」から「NavigationInvoker」というコンポーネントを追加します。

詳細のTile Generation Radiusがこのコンポーネントからどのくらいまでの範囲にナビゲーションメッシュを作るかなので、基本的に、これより遠い地点にAI MoveToなどで移動しようとすると失敗します。

また、これだけでは動かず、レベル上にNavMeshBoundsVolumeをおいて計算させると作られるRecastNavMesh-Default(か、ワールド設定)から「Runtime Generation」という実行時に生成するモード?を「Dynamic」(動的)に設定変更しておく必要があります。

これで常におじさん周辺にナビメッシュが動的に作られるので、おじさんは当然ですが、ステージ上でそう離れていないタイルマンなどのキャラクターもAI MoveToなどの移動が利用できます。

よっしー
よっしー

たくさんのNPCにInvokerをつけるよりも、必ずいるおじさんかプレイヤー自身につけてその周辺だけ動くってのが効率がいいかなと思います。

リセット演出

リセット時のプレーヤーの動きは、開始と同じく控室に入れてステージリセットして開始地点にワープでOKなので同じ処理を呼ぶだけでいいです。

で、リセットの場合に正誤判定やステージ切り替えはどうするかですが、

  • リセットされる可能性があるのは違和感が出題されている状況(水攻めやタイルマンなど攻撃してくるものにやられたとき)
  • リセットされたのは戻らないといけない選択を間違えて進んだとき

なので、違和感が出題されていて、前進を選んだとき、つまりFrontに接触したときと同じイベントを実行すればスコア判定や次のステージどうするかの選択はOKです。

あとは追加でリセットが必要というフラグを追加するのと、どうやって正誤判定イベントを呼ぶかですね。

これはダメージ処理を使う作戦にしてみます。

タイルマンや水(私のサンプルゲームの場合は焼きリンゴや玉ねぎちゃんがプレイヤーに接触したときに攻撃を送り、

攻撃されたプレイヤーのクラスからリセットフラグをセットしたあと、前進を選んだ入力(「Is Front=true」)で判定イベントを呼び出します。

よっしー
よっしー

(リセットフラグもイベントの入力に追加しても構いませんが、その場合、トリガ装置側の呼び出し元も合わせる必要ありますね)

つづく

ゲームモードクラス以外の大半は説明終わりました。

残るゲームモードクラスの処理は一気に説明するには少し大きいので、記事を分けて説明します。

コメント

タイトルとURLをコピーしました