Unityでゲームプレイの保存をするゴースト機能を作成する

今回はゲームプレイのゴーストを表示する機能を作成していきます。

以前作成した

UnityのAnimatorの保存と再生の機能を使うとゲームプレイ中のキャラクターのアニメーションを保存したり再生したりすることが出来ます。今回はその機能を使ってみようと思います。

の機能を自分なりに作成したものです。

今回の機能を作成すると、例えばレーシングゲームで自分の車のゴーストを表示させたり、アクションゲームで「このように攻略してください」といった例をゴーストとして表示する時に応用して使えると思います。

ただし完全に正確なゴーストを作成出来るわけでもなく多少ズレるかもしれません。

今回の機能ではキャラクターの動きを保存し、そのデータをその場で再生出来る機能だけでなくファイルにゴーストデータを保存して後からデータを読み出してゴーストの再生を出来るようにしていきます。

今回の機能を作成すると

↑のようなものが出来上がります。

動的に動く床のゲームオブジェクトとはスタートが同期していないのでゴーストキャラクターの位置と動く床の位置は一致しません。

スポンサーリンク

ゴースト機能の概要

まずはゴースト機能をどのように作るかを考えていきます。

キャラクターの動きを後から再生すればいいので、Update毎にキャラクターの位置や角度、アニメーションパラメータの値等をデータとして保持しておき、あらかじめ用意しておいたゴーストキャラに通常の操作キャラクターと同じAnimatorControllerを取り付け、ゴーストキャラクターの位置や角度、アニメーションを保持したデータで再生すれば出来そうです。

ただ毎回Updateメソッドが呼ばれる度にデータを追加していくとデータ量が多くなりすぎるので、データを取る時間間隔の設定とデータの数の制限を設ける事にします。

ゴースト機能の作成

それではゴースト機能を作成していきましょう。

操作キャラクターの準備

まずは通常の操作キャラクターを用意します。

キャラクターにはCharacterControllerを取り付け、コライダのサイズを調整します。

キャラクターにはAnimatorControllerを作成し取り付けます。

AnimatorControllerにはIdle、Walk、Jump状態を作成します。

ゴースト機能の操作キャラクターのAnimatorController

アニメーションパラメータにFloatのSpeed、TriggerのJumpを作成します。

Idle→WalkはSpeedがGreaterで0.1
Walk→IdleはSpeedがLessで0.1
Idle→Jump、Walk→IdleはJumpがトリガーされた時
Jump→IdleはHas Exit Timeにチェックを入れておきます。

キャラクターを動かすスクリプトは

を取り付けます。

最小限の移動とジャンプが出来るスクリプトです。

これで操作キャラクターは完成です。

ゴーストキャラクターの作成

ゴーストキャラクターはキャラクターのモデルにCharacterControllerと操作キャラクターに設定したAnimatorControllerをゴーストキャラクターにも取り付けます。

ゴーストキャラクターのモデルにはEthanを使用していて、EthanBodyとEthanGrassesにはデフォルトでEthaWhiteマテリアルが設定されています。

そこでゴーストキャラクター用のGhostMaterialを作成し、それをゴーストキャラクターのEthanBodyとEthanGrassesにドラッグ&ドロップします。

ゴーストキャラクター用のマテリアルを作成

GhostMaterialは↑のように半透明なマテリアルにします。

ゴーストキャラクターは

実際のゴーストキャラクター

↑のように半透明になります。

ゴーストキャラクターはGhostという名前にし、最初は非表示にする為インスペクタの名前の横のチェックを外しておきます。

ゴースト機能スクリプトの作成

ヒエラルキー上に空のゲームオブジェクトを作成し、名前をRecorderとします。

Recorderゲームオブジェクトには新しくRecorderスクリプトを作成し取り付けます。

フィールド宣言部とStartメソッド

まずはフィールド宣言とStartメソッドを見ていきます。

インスペクタで操作キャラクタースクリプトghostCharaを設定出来るようにし、そのキャラクターのデータを保持出来るようにします。

isRecordは今ゴーストを記録しているかどうか
maxDataNumはデータを保持する最大数で、この数字を超えたらゴーストの記録を停止します。
recordDurationはキャラクターのデータを取る時間間隔です。
animKeyはInputManagerのキーの名前を指定します。
elapsedTimeは前回記録してからの経過時間を入れます。
ghostDataはゴーストデータをクラスとして持たせて、そのインスタンスを入れておくフィールドです。
isPlayBackは今ゴーストを再生しているかどうか
ghostはゴーストとして動かすゴーストキャラクターのゲームオブジェクトをインスペクタで設定します。
waitTimeはゴーストの再生が終わった後は最初から再生し直しますが、その間の待ち時間を設定します。
saveDataFolderは保存先のフォルダのパスです。
saveFileNameは保存ファイル名です。

StartメソッドではGhostCharaスクリプトからAnimatorコンポーネントを取得しanimatorに保持しておきます。

似たようなフィールド名になっていますが、ghostCharaはGhostCharaスクリプト、ghostはゴーストキャラクターのゲームオブジェクトになります。

ゴーストデータクラス

Recorderクラスの中にゴーストデータを保持するクラスを作成します。

後でクラスごとシリアライズするので[Serializable]アトリビュートをクラスに付けておきます。

それぞれリストでデータを保持出来るようにします。

データを記録するUpdateメソッド

次はデータを記録する処理を書いているUpdateメソッドを見ていきます。

isRecordでデータを記録していたら経過時間を足して記録間隔を超えていたらghostCharaスクリプトのTransformから位置と角度を取得しGhostDataクラスのリストに追加します。

ジャンプだけはアニメーションパラメータをトリガーで作成しているので、ジャンプキーを押したか押してないかのbool値を追加します。

記録したデータ数がmaxDataNumを超えたらStopRecordメソッドを呼び出しデータの保存をやめることにします。

UIのボタンを押した時に呼び出す処理

次にUIのボタンを押した時に呼び出す処理を記述します。

StartRecordメソッドは「ゴーストの保存」ボタンを押した時に呼び出す処理で、コルーチンの全停止とStopGhostメソッドを呼び出しゴーストの再生を止めます。

GhostDataクラスのインスタンスを作成しghostDataに参照を入れます。

StopRecordは「ゴーストの保存の停止」ボタンを押した時に呼び出します。

StartGhostは「ゴーストの再生」ボタンを押した時に呼び出す処理で、保存したゴーストを再生する処理です。

ゴーストが保存されていればisPlayBackをtrue、ゴーストキャラクターであるghostをアクティブにしてPlayBackメソッドをコルーチンで呼び出します。

StopGhostメソッドは「ゴーストの停止」ボタンを押した時に呼び出しゴーストの再生をやめゴーストキャラクターを非アクティブにします。

ゴーストの再生処理

ゴーストの再生処理はPlayBackメソッドで行います。

PlayBackメソッドではisPlayBackがtrueである間ghostDataから順番にデータを取り出しゴーストキャラクターの位置や角度、アニメーションの再生をします。

ゴーストデータはrecordDurationの間隔で保存したので、その間隔経過してから位置や角度、アニメーションを設定します。

保存データ数を超えた場合はまずSpeedを0、Jumpのトリガーをキャンセルした後、指定の時間待たせます。

その後ゴーストキャラの位置や角度を最初のデータの位置や角度にし、また最初から再生するようにします。

ファイルにゴーストデータを保存、読み出しする処理

最後はゴーストデータをファイルに保存したり読み出したりする処理を作成します。

Saveメソッドは「ゴーストデータをファイルに保存」ボタンを押した時に呼び出す処理で、ghostDataをJSON形式に変換したものを指定したファイルに一括で書き出しています。

LoadメソッドはファイルからJSON形式で保存したデータをreadAllTextに一括で読み出しそれをGhostDataクラスの形式に戻してghostDataに値を入れています。

Save、Loadメソッドの処理に関しては、

C#でファイルの読み込みと書き込みのサンプルを作成していきます。確認にはUnity付属のMonoDevelopを使用します。UnityでCSVファイルの読み込みと書き込みもしてみました。

を参照してください。

OnApplicationQuitはMonoBehaviourクラスで定義されているメソッドでアプリケーションの終了時に呼び出されます。

アプリケーション終了時にSaveメソッドを呼び出しファイルにデータを書き出しています。

ボタンのみでファイルに保存したい場合はOnApplicationQuitメソッドは要らないです。

これでスクリプトが完成しました。

UIの作成

スクリプトが完成したので、UIのボタンを作成しメソッドを呼び出すようにします。

UIのButtonを6つ作成しそれぞれ対応する名前を付けます。

「ゴーストの保存」ボタンはRecordButton
「ゴーストの保存の停止」ボタンはStopRecordButton
「ゴーストの再生」ボタンはPlayBackButton
「ゴーストの停止」ボタンはStopGhostButton
「ゴーストデータをファイルに保存」ボタンはOutputGhostDataButton
「ゴーストデータを読み込み」ボタンReadGhostDataButton

という名前にしました。

ゴースト機能操作ボタンのヒエラルキー

実際の並びは

ゴースト機能操作ボタンの配列

↑のようにしています。

ボタンのOnClickに呼び出す処理を設定する

次はボタンのOnClickに呼び出す処理を設定します。

RecordButtonはRecorderのStartRecordメソッドを指定します。

ボタンのOnClickにゴースト機能スクリプトのメソッドを指定

他のボタンもRecorderスクリプトの該当するメソッドを指定します。

StopRecordButtonはStopRecordメソッド
PlayBackButtonはStartGhostメソッド
StopGhostButtonはStopGhostメソッド
OutputGhostDataButtonはSaveメソッド
ReadGhostDataButtonはLoadメソッド

をそれぞれ指定します。

これでUIのボタンを押した時に呼び出すメソッドの設定が終わりました。

ゴースト機能の完成

RecorderゲームオブジェクトのRecorderスクリプトの設定は、

Recorderスクリプトの設定

↑のようになります。

GhostCharaには操作キャラクターをドラッグ&ドロップし、Ghostにはゴーストキャラクターのゲームオブジェクトをドラッグ&ドロップします。

これでゴースト機能が完成しました。

ジャンプ途中で「ゴーストの保存の停止」ボタンや「ゴーストの再生」ボタンを押すとゴーストの再生の最後でジャンプ終了時に高い所で止まってしまいます。

これはジャンプ途中の位置までしか記録せず保存を停止している為です。

ジャンプアニメーションは前のデータでトリガーされているので最後まで再生されます。

記事の最初に記したように動く床とゴーストキャラクターの位置が一致しません。

またデータに記録間隔を長くすると、実際のジャンプと位置が一致しなくなります。

さらにはジャンプボタンを押したかでbool値をデータに入れているので、アニメーション自体がされなくなる事もあります。

その為、記録間隔自体の処理をなくすか、ジャンプアニメーションに関してはジャンプボタンを押した時の経過時間を保持しておき、その時間が過ぎたらJumpアニメーションのトリガーをするという方法にした方がいいかもしれません。

機能を確認すると記事の最初に紹介した動画のようになります。(^^)/

ジャンプデータを時間で計測するバージョン

完成したゴースト機能ではジャンプのデータをキーを押したかどうかで記録していますが、記録する間隔(recordDuration)が長いと押していてもデータが取れないことがあります。

そこでジャンプキーを押したかどうかはrecordDurationによらず常に確認するようにし、bool値のデータでなく押すまでの時間でデータを取るようにしてみます。

あらかじめ言っておきますが、時間で計測する為、本来の動きとのズレが発生します。

ですが、recordDurationを長めに取っても時間でデータを記録するのでジャンプデータの取りこぼしがありません。

Recordスクリプトの改造

ジャンプキーを押したかどうかを時間で保持する為にRecordスクリプトを改造していきます。

まずはフィールドを追加します。

位置や角度と同じタイミングでジャンプのデータを取るわけではないので、ゴーストの再生が終わったらisLoopResetをtrueにし、それに伴ってジャンプのゴーストデータも最初に戻します。

次はGhostDataクラスのジャンプのデータを書き換えます。

データはfloatで持つようにします。

次にUpdateメソッドでジャンプのデータを取る部分の変更です。

元のジャンプのデータを取る部分を削除し、if文の外に以下の処理を追加します。

ジャンプキーを押した時にデータの記録を取り始めた時間からの経過時間、または前回ジャンプキーを押した時からの経過時間をリストに保存します。

startTimeには現在のゲーム開始からの時間を入れておきます。

次にStartRecordメソッドにstartTimeにゲーム開始からのリアルタイム秒を入れておきます。

次にStartGhostメソッドでPlayBackAnimメソッドのコルーチン処理を追加します。

今まではPlayBackでジャンプのデータの再生もしてましたが、ジャンプはPlayBackAnimの方に移します。

PlayBackメソッドの処理を変更します。

isLoopResetがtrueの時はゴーストの再生が終わり、ジャンプとの同期を取る為に待つためyield return nullでその後の処理をさせません。

次は新しく作ったPlayBackAnimメソッドです。

ジャンプのデータは押した時間を記録していくので、位置や角度のデータと数が違います。

そこでisLoopResetがtrueの時にジャンプデータを最初に戻します。

ジャンプデータは前に押した時からの時間を保存しているので、その時間待った後にJumpアニメーションパラメータのトリガーをしています。

それ以外はyield return nullで何もしません。

これで修正は完了です。

確認すると、

↑のようにrecordDurationを0.2と長くしてもジャンプアニメーションがちゃんとされているのがわかります。

終わりに

位置や角度と同じようにジャンプデータを取るか、押すまでの経過時間でジャンプデータを取るかで多少変わります。

経過時間で取った方が記録間隔にかかわらずジャンプアニメーションを再生させる事が出来ますが、位置や角度と完全な同期をしていない為、多少ズレてしまうかもしれません。

兎にも角にも簡単なゲームプレイの保存をしてゴーストの再生をする機能は出来たのではないかと思います。

(゜゜)~

スポンサーリンク

記事をシェアして頂ける方はこちら

フォローして頂くとやる気が出ます