はじめに
いわゆるvTuberのようにアバターをVR内で動かすとき、動きを保存して後で再生したいことは多いです。そういうシーン内モーションキャプチャーのためのUnityアセットは既にいくつか公開されていますが、Unityエディター内でしかキャプチャーしたデータを保存できない、という問題がありました。
それに対して今回紹介するRecord and Playは、ビルドしたアプリ内でゲームオブジェクトの動きを記録してデータファイルを生成し、保存することが可能です。私はVive Focus Plus内でモーションキャプチャーをテストしましたが、無事デバイス内のフォルダに記録ファイルを保存できました。
今回は、その方法を紹介します。なお、以下の内容はごく基本的なものだけなので、より詳しいことは公式ページ.を参考にしてください。
記録を取る
記録を取るには、まずRecorderというScriptable Objectを生成します。スクリプトからも作れますが、私はエディター内のメニュー(Create->RecordAndPlay->Recorder)で作ってます。
次に、作ったRecorderに対してトラッキングしたいゲームオブジェクトを指定します。下の例のように、オブジェクトをListで指定し、SubjectBehaviour.Buildを使って一つ一つをrecorderに登録します。VRモーションキャプチャーなら、HMDやコントローラに相当するものを指定すればよいでしょう。
using System.Collections.Generic;
using UnityEngine;
using EliCDavis.RecordAndPlay.Record;
using EliCDavis.RecordAndPlay;
using EliCDavis.RecordAndPlay.IO;
using EliCDavis.RecordAndPlay.Playback;
public class RecorderManager : MonoBehaviour, IActorBuilder
{
[SerializeField] private Recorder recorder;
[SerializeField] List<GameObject> trackedObjects;
void Start(){
foreach (var obj in trackedObjects)
{
SubjectBehavior.Build(obj, recorder);
}
}
}
記録を取るときは、recorder.Start();
で開始し、終了する際はRecording myRecording = recorder.Finish();
で記録結果をRecording型の変数に保存します。ファイルに保存するときは、ストリームを作ってPackager.Package
メソッドで保存します。例えば、こういう感じです。
//上に追加
public void StartRecording()
{
recorder.Start();
}
public void StopRecording()
{
Recording myRecording = recorder.Finish();
var fileName = string.Format("{0}/demo.rap", Application.persistentDataPath);
using (FileStream fs = File.Create(fileName))
{
myRecording.RecordingName = "Demo";
Packager.Package(fs, myRecording);
}
}
記録を読みだして再生する
ファイルから記録を読み出すときは、Unpackager.Unpackage
メソッドを使います。下では、recording
という変数に読み込んだデータを保存します。あとPlaybackBehaviour.Buildを使って、プレイバックのための変数myPlaybackBehavior
を準備します。このさい、Unpackageしたものを保存しているrecording、actor builderを持っているクラス、Custom Event handlerを持っているクラス、ループの有無を指定します。
actor builderというのは、保存したゲームオブジェクトの動きを再現するActorを作るためのクラスで、IActorBuilderを継承し、Buildというメソッドを実装している必要があります。Buildメソッドは、Actorをインスタンス化するときに使うので、後ほどこのクラス内で定義するのでthis
にしました。Custom Event handlerについては、今回は使わないのでnull
にしました。
//上に追加
void LoadRecording(){
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
var recording = Unpackager.Unpackage(fs)[0];
myPlaybackBehavior = PlaybackBehavior.Build(recording,this,null,false);
}
それでは、次にBuildメソッドを指定します。これは、記録されたゲームオブジェクトに対応するActorを指定するために呼び出されます。その際、記録されたゲームオブジェクトの名前などがメソッドに渡されるので、以下の例ではそれを最初に指定したトラッキング対象のゲームオブジェクトの名前と比較して、一致していればnew Actor()
でActorをインスタンス化して、それへの参照を返却値として渡すようにしました。
//上に追加
public Actor Build(int subjectId, string subjectName, Dictionary<string, string> metadata)
{
foreach(var ob in trackedObjects)
{
if (subjectName == ob.name)
{
var actor = new Actor(ob);
return actor;
}
}
throw new System.Exception("subject for actor not found");
}
ここまで準備できたら、あとはmyPlaybackBehavior.Play()
再生するだけです。Actorを作る際に指定したゲームオブジェクトがPLAYBACK OBJECTというゲームオブジェクトの下に生成(あるいは移動)され、記録した通りに動きます。
//上に追加
public void PlayRecording()
{
myPlaybackBehavior.Play();
}