[第16回UE4ぷちコン]振り返り:リプレイ

ここは[第16回UE4ぷちコン]振り返りの子頁です。

概要

  • 破壊見たさ、テーマ「みち」要素回収したさで作成

  • 移動や破壊等の発生時刻を記録して再生

  • ゲームが単純なのもあり比較的作りやすかった

動機

一通り動くものを作っていく過程で、どんどん懸念リストが膨らむ。

  • 壁着地時にライトパネルが壊れるようにしたのはいいけど、プレイ中全然見えない!

    • TPSにしようかな!でも見えちゃうとポーズとか作る必要あるし、作業増えるなぁ…

    • 敵の撃破演出みたいに、いちいちショートリプレイにする?ただの移動だしなぁ…

  • 「みち」を決めて敵をやっつけるのはいいけど、移動後は消えちゃう

    • 薄い色で残しとくか!でも遊ぶ時は邪魔な…

    • 通った「みち」にも着地できるとか、何かしら膨らませられそうな気はするけど発散しそう…

懸念リストを俯瞰して「なんかいい感じにならねぇかな~」と考えた時、リプレイを作れば第三者視点から見られるし、通過した「みち」も示せるしで一挙両得である事に気づく。後者はややこじつけであるが、配られたカードから「これは役です」とゴリ押して勝負するしかない(やさぐれスヌーピー)

ということでリプレイを作ってみることにした。

作り方

仕様は以下の通り。

  • ゲームプレイ中、イベント発生時刻を記録する

    • 実際にプレイヤーが移動する際、いつ、どこからどこまで、何秒で飛んだか記録

    • ビームが発生する際、いつ、誰が、どの方向に発砲したか記録

    • 何かが破壊された際、いつ、誰が、どういう入射角で破壊されたか記録

    • 部屋を移動する時、いつ、どの部屋に移動したか記録

  • リプレイ開始後、経過時刻に従って記録を再生する

以下のように実装していった。

Object型の派生Blueprint「BP_ReplayRecord」を作成。中身がないPlay関数とSec変数を持つ。

BP_ReplayRecordをContent Browser内で右クリックして、Child Blueprintを記録の種類分作る。いわゆるCommandパターンだ。

例えばBP_ReplayRecord_Destは破壊イベントの記録保持と再生を行う。Play関数をoverrideして、記録した破壊対象に「リプレイ用の破壊をやれ」と指示する。

破壊イベントパラメータ構造体はこんな感じ。変数Targetは"BPI_Land_Target"というBlueprint Interface型。これは「プレイヤーが着地した時に何かする」Interfaceで、壁、天井、扉、敵Actorが実装を持つ。

各種Actorがリプレイの記録を依頼するためのBlueprint Inerfaceを用意する。

今回はGameModeにリプレイ記録依頼を投げるようにした。図は壁が破壊イベントを記録する様子。壁はBPI_Land_Targetの実装を持つので、暗黙的にCastしてData Targetピンに入力できている(多分UE5からの機能)。他にはプレイヤーが着地した位置と自分の正面方向を記録している。

それを受け取ったGameModeは、自分が管理しているゲーム内時刻とともにリプレイ管理BP(Actor Component)に引き次ぐ。これで、「いつ何が起きたか」の情報が完成する。

リプレイ管理BPは、各イベント用に用意したBP_ReplayRecord派生オブジェクト受け取った記録パラメータとともにConstructして記録する。

記録そのものはBP_ReplayRecord型配列。時系列順に格納したいので、Reverse for Loopでイベント時刻をチェックしてInsertする。といっても基本的には来た順に最後尾に入っていく。プレイヤーが死ぬと部屋の最初からやりなおしになるので、この時点ではまだ一時記録用の配列に格納している。

プレイヤーが部屋内の敵を全滅させたタイミングで、一時期録を確定記録に移す。Clear Temp Records関数はプレイヤーが死んだ時にも呼ばれるやつで、中身は配列クリアしているだけ。

リプレイ再生処理はこんな感じ。確定記録を頭から追っていく。リプレイ内の経過時刻を記録しつつ、次のイベントまでDelayで待機するループになっている。イベントをBP_ReplayRecord派生型で記述したおかげで、ここではとにかくPlay関数を呼ぶだけで済んでいる。再生が終わったらEvent Dispatcherで終了を叫ぶ。GameModeがそれを聞いて、リザルト処理に移る。

その他

  • リプレイはTAS的に「クリアできたときの動き」だけ追従する仕様

    • ミス含めたリプレイにもできたが、格好良さと時短重視

    • 最初に部屋に入った時の時刻を覚えておき、プレイヤーが死んだらリプレイの一時記録を破棄して、記録用時刻も巻き戻す

  • リトライ処理の使い回しで一石二鳥

    • ゲーム中にプレイヤーがミスすると、敵含め部屋内の破壊状況をリセットしてやり直す機能を作っていた

    • リプレイ開始時に全部屋にリトライ処理をかければ環境の準備は整うことになる

      • 部屋間をつなぐゲートは例外的にリプレイ時のみリセットされるようにしている

  • 実はリプレイはテンポ重視で倍速再生

    • Global Time Dilation=2.0でやっている

    • これだとChaos破壊がゲーム用のパラメータではうまく動作しないので、全てリプレイ用に調整している

      • と言っても、衝撃の効果時間を調整するだけで済んだ。詳しくはChaos Physicsの記事を参照

      • 敵の破壊は超スロー状態(0.01倍速)で見栄え調整しているので、等速再生でも専用パラメータは結局必要になるというのもある

  • 実は正確じゃない

    • 正確な再現が重要でない部分は簡易に済ませている

    • ゲーム中のプレイヤーは徐々に加速するが、リプレイでは1回移動分の開始・終了点で等速直線運動している

    • プレイヤーの攻撃ポーズは反映されてない(全部ドリル頭突き)

    • 敵もビーム発生時に振り向かせてはいるが、プレイ中実際にその向きになっていたわけではない

      • 銃口の向きと発射角に余裕があるため

反省点

  • チャレンジしてみて良かった

    • めちゃくちゃに暴れながら突き進む様に謎の爽快感がある

    • プレイが報われる感じがするのも良い

  • ほぼ1日で作れた

    • 破壊と「みち」を見せられればよい、と割り切れたのが大きい

    • 敵の動きとかもう全然正確じゃないっていうか、バグってるまである。審査員のみんなには内緒だ

  • ミス含めたリプレイでも良かった気がする

    • 格好良さ重視とか言ったけど、別にコミカルさ重視でもアリなのでは

    • その場合、部屋や敵の破壊を巻き戻しをどうするかも含めて検討になりそう

      • 死亡しても何も巻き戻さないという手もあるが、ゾンビ戦法がゲームプレイで採択されてしまう恐れもあり悩ましい

        • 現状の仕様だと、半分パズルゲームだし…

    • まぁ、格好良く成功時だけ見せてテンポよくリプレイを締めるのも正解の一つではあった…はず

  • カメラはもうちょっとなにかできたんじゃないか

    • 何かが大暴れしているということしかわからん

    • ただ、下手にカメラズームするとプレイヤーの攻撃ポーズ再現のしごとが増えるので、これはこれでいいだろう

  • だいぶ端折って説明書いたけど、伝わるだろうか

    • ココもうちょっと詳しく、とかあればTwitterでこっそり教えて下さい(この頁以外でも)