Record and Play を用いて、Unityのゲームオブジェクトの動きをデバイス内で保存、再生する

# はじめに
いわゆるvTuberのようにアバターをVR内で動かすとき、動きを保存して後で再生したいことは多いです。そういうシーン内モーションキャプチャーのためのUnityアセットは既にいくつか公開されていますが、Unityエディター内でしかキャプチャーしたデータを保存できない、という問題がありました。

それに対して今回紹介するRecord and Playは、ビルドしたアプリ内でゲームオブジェクトの動きを記録してデータファイルを生成し、保存することが可能です。私はVive Focus Plus内でモーションキャプチャーをテストしましたが、無事デバイス内のフォルダに記録ファイルを保存できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/4a6dd76b-a8cb-5932-41e0-a6d2b5cef8b6.png)

今回は、その方法を紹介します。なお、以下の内容はごく基本的なものだけなので、より詳しいことは[公式ページ](https://recolude.gitlab.io/RecordAndPlay3D/).を参考にしてください。

# 記録を取る

記録を取るには、まずRecorderというScriptable Objectを生成します。スクリプトからも作れますが、私はエディター内のメニュー(Create->RecordAndPlay->Recorder)で作ってます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/c74cae41-058f-3cbf-70c6-47c99eefda98.png)

次に、作ったRecorderに対してトラッキングしたいゲームオブジェクトを指定します。下の例のように、オブジェクトをListで指定し、SubjectBehaviour.Buildを使って一つ一つをrecorderに登録します。VRモーションキャプチャーなら、HMDやコントローラに相当するものを指定すればよいでしょう。

```RecorderManager.cs
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`メソッドで保存します。例えば、こういう感じです。

```RecorderManager.cs
//上に追加
    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`にしました。


```RecorderManager.cs
//上に追加
   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をインスタンス化して、それへの参照を返却値として渡すようにしました。

```RecorderManager.cs
//上に追加
    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というゲームオブジェクトの下に生成（あるいは移動）され、記録した通りに動きます。

```RecorderManager.cs
//上に追加
    public void PlayRecording()
    {
        myPlaybackBehavior.Play();
    }
```