【Unity】Recorderで連続してシーンを録画する

[前回](https://qiita.com/GameDevEgg/items/fb1538c40a1f861d2f55)、Recorderをスクリプトから制御してシーンを録画しました。
今回は、その応用にあたる「連続したレコーディング」の実装をやっていきます。
その前に、Recorderとは何か軽くおさらいしておきます。

Recorder とは、「Unity Recorder」というツールのことです。
Unity のエディタ内で、ゲームプレイ、アニメーション、その他のシーンコンテンツを
様々な形式でキャプチャおよびエクスポートすることが出来ます。

例えるなら、Unity版の「Xbox Game Bar」のようなものでしょうか。

# 前回までのコード

<details><summary>まず、前回までのコードを確認します。（少し変更してます）</summary>

```cs
using UnityEditor.Recorder;
using UnityEditor.Recorder.Input;
using UnityEngine;

public class Recorder : MonoBehaviour
{
    /// <summary>コントローラー</summary>
    private RecorderController _controller;

    /// <summary>コントローラー設定</summary>
    private RecorderControllerSettings _controllerSettings;

    /// <summary>レコーダー設定</summary>
    private MovieRecorderSettings _recorderSettings;

    //-------------------------------------------------------------------------------
    // RecorderControllerの設定
    //-------------------------------------------------------------------------------

    /// <summary>フレームレートの制御方法</summary>
    [SerializeField, Header("フレームレートの制御方法")] private FrameRatePlayback _playback;

    /// <summary>フレームレート</summary>
    [SerializeField, Header("フレームレート")] private float _frameRate = 30f;

    /// <summary>フレームレート制限</summary>
    [SerializeField, Header("フレームレート制限")] private bool _isCapped = true;

    //-------------------------------------------------------------------------------
    // 録画設定
    //-------------------------------------------------------------------------------

    /// <summary>出力の横幅</summary>
    [SerializeField, Header("出力の横幅")] private int _width = 1920;

    /// <summary>出力の縦幅</summary>
    [SerializeField, Header("出力の縦幅")] private int _height = 1080;

    //-------------------------------------------------------------------------------
    // 録音設定
    //-------------------------------------------------------------------------------

    [SerializeField, Header("録音するかどうか")] private bool _isAudioPreserved = true;

    //-------------------------------------------------------------------------------
    // フォーマット設定
    //-------------------------------------------------------------------------------

    /// <summary>出力形式</summary>
    [SerializeField, Header("出力形式")] private MovieRecorderSettings.VideoRecorderOutputFormat _format;

    /// <summary>ビデオ品質</summary>
    [SerializeField, Header("ビデオ品質")] private UnityEditor.VideoBitrateMode _quality;

    //-------------------------------------------------------------------------------
    // 出力設定
    //-------------------------------------------------------------------------------

    /// <summary>ファイル名</summary>
    [SerializeField, Header("ファイル名")] private string _fileName;

    private void Start()
    {
        SetUpRecorderController();
        SetUpRecorder();
    }

    /// <summary>コントローラーの設定を行う</summary>
    private void ConfigureRecorderControllerSettings()
    {
        _controllerSettings = ScriptableObject.CreateInstance<RecorderControllerSettings>();

        // レコーディングモードを設定
        _controllerSettings.SetRecordModeToManual();

        // フレームレートの制御方法
        _controllerSettings.FrameRatePlayback = _playback;

        // フレームレート
        _controllerSettings.FrameRate = _frameRate;

        // フレームレートを制限するか
        _controllerSettings.CapFrameRate = _isCapped;
    }

    /// <summary>レコーダーの設定を行う</summary>
    private void ConfigureMovieRecorderSettings()
    {
        _recorderSettings = ScriptableObject.CreateInstance<MovieRecorderSettings>();

        // フレームレートの制御方法
        _recorderSettings.FrameRatePlayback = _playback;

        // フレームレート
        _recorderSettings.FrameRate = _frameRate;

        // フレームレートを制限するか
        _recorderSettings.CapFrameRate = _isCapped;

        // 出力形式
        _recorderSettings.OutputFormat = _format;

        // ビデオ品質
        _recorderSettings.VideoBitRateMode = _quality;

        // ファイル名
        _recorderSettings.OutputFile = $"Assets/Recordings/{_fileName}";

        // 録画設定
        _recorderSettings.ImageInputSettings = 
            new GameViewInputSettings(){OutputWidth = _width, OutputHeight = _height};

        // 録音設定
        _recorderSettings.AudioInputSettings.PreserveAudio = _isAudioPreserved;

        // 有効化
        _recorderSettings.Enabled = true;
    }

    /// <summary>コントローラーのセットアップ</summary>
    private void SetUpRecorderController()
    {
        // コントローラーを設定
        ConfigureRecorderControllerSettings();
        
        // コントローラーを作成
        _controller = new RecorderController(_controllerSettings);
    }

    /// <summary>レコーダーのセットアップ</summary>
    private void SetUpRecorder()
    {
        // レコーダーを設定
        ConfigureMovieRecorderSettings();
        // レコーダーの設定をコントローラーの設定に追加
        _controllerSettings.AddRecorderSettings(_recorderSettings);
    }

    /// <summary>レコーディングを開始する</summary>
    public void StartRecording()
    {
        // 録画準備
        _controller.PrepareRecording();
        // 録画開始
        _controller.StartRecording();
    }

    /// <summary>レコーディングを終了する</summary>
    public void StopRecording()
    {
        // 録画終了
        _controller.StopRecording();
    }
}
```
</details>

このコードでは、RecorderController と Recorder のセットアップを行った上で、
レコーディングの開始と終了をメソッド化して外部から呼び出せるようにしています。

---

前回、記事を書いた時点から変更した内容は、
RecorderController のセットアップと Recorder のセットアップを切り離したことです。

理由としては、「RecorderController のインスタンス作成後も、
Recorder の設定を追加できる」ということが分かったからです。

---

変更前の時点では、
「RecorderController のインスタンス作成前に、Recorder の設定を
RecorderController の設定に追加しないといけない」と考えていました。

そのため、次のグラフのようなフローになるので、
RecorderController のセットアップと Recorder のセットアップ をまとめていました。

```mermaid
graph TD;
    RecorderControllerの設定-->Recorderの設定;
    Recorderの設定-->Recorderの設定をRecorderControllerの設定に追加;
    Recorderの設定をRecorderControllerの設定に追加-->RecorderControllerのインスタンスを作成;
```
---

しかし、「RecorderController のインスタンス作成後も、Recorder の設定を
RecorderController の設定に追加することができる」ことが分かったので、
次のグラフのように処理を分離させました。

```mermaid
graph TD;
    RecorderControllerの設定-->RecorderControllerのインスタンスを作成;
    Recorderの設定-->Recorderの設定をRecorderControllerの設定に追加;
```

---

この変更を反映したのが、下記のコードです。

```cs
// 変更前のコード

// コントローラーを設定
ConfigureRecorderControllerSettings();
// レコーダーを設定
ConfigureMovieRecorderSettings();
// レコーダーの設定をコントローラーの設定に追加
_controllerSettings.AddRecorderSettings(_recorderSettings);
// コントローラーを作成
_controller = new RecorderController(_controllerSettings);
```

```cs
// 変更後のコード

/// <summary>コントローラーのセットアップ</summary>
private void SetUpRecorderController()
{
    // コントローラーを設定
    ConfigureRecorderControllerSettings();
    
    // コントローラーを作成
    _controller = new RecorderController(_controllerSettings);
}

/// <summary>レコーダーのセットアップ</summary>
private void SetUpRecorder()
{
    // レコーダーを設定
    ConfigureMovieRecorderSettings();
    // レコーダーの設定をコントローラーの設定に追加
    _controllerSettings.AddRecorderSettings(_recorderSettings);
}
```

---

話が大幅に逸れてしまいました。

前回のコード（変更後）でも、録画の開始と終了を行えます。
しかし、このコードの開始・終了メソッドを何度呼び出しても、
録画ファイルが1つしか作成されません。

これは、録画が1度しか行われていないのでしょうか？

結論から言うと、録画は開始・終了メソッドを呼ぶ度に行われています。
ではなぜ録画ファイルが1つしか作成されないのか。
その理由は、**「録画ファイルの名前が全て同一なのでファイルが上書きされているから」**

では、この問題を解決していきましょう。

# 連続したレコーディングに対応させる

現在の問題点は、「録画する度にファイルが上書きされてしまうこと」なので、
「録画する度に異なるファイル名で出力する」ようにしましょう。
今回は、録画回数を記録する変数を用意し、ファイル名の末尾に変数を追加します。
あとは、録画する度にカウント変数をインクリメントさせます。

この際に注意することは、**出力ファイル名の指定を録画する時に行うこと**です。
なぜなら、現在のコードでは出力ファイル名の指定をレコーダーの設定で行っているため、
カウント変数を用意しても、設定時のカウント変数の値のまま固定されてしまうためです。

つまり、カウント変数の値が0のときにレコーダーの設定をすると、
カウント変数を何度インクリメントしてもファイル名の末尾は0のままです。
これは、レコーダーの設定が呼ばれるのはレコーダーのセットアップ時のみだからです。

ではどうするのかというと、
出力ファイル名を指定する処理を、レコーダーの設定ではなく、録画の開始時に行います。

この変更を反映したのが以下のコードです。

```cs
using UnityEditor.Recorder;
using UnityEditor.Recorder.Input;
using UnityEngine;

public class Recorder : MonoBehaviour
{
    /// <summary>コントローラー</summary>
    private RecorderController _controller;

    /// <summary>コントローラー設定</summary>
    private RecorderControllerSettings _controllerSettings;

    /// <summary>レコーダー設定</summary>
    private MovieRecorderSettings _recorderSettings;

    /// <summary>レコーディングした回数</summary>
    private int _recordingCount = 1;

    //-------------------------------------------------------------------------------
    // RecorderControllerの設定
    //-------------------------------------------------------------------------------

    /// <summary>フレームレートの制御方法</summary>
    [SerializeField, Header("フレームレートの制御方法")] private FrameRatePlayback _playback;

    /// <summary>フレームレート</summary>
    [SerializeField, Header("フレームレート")] private float _frameRate = 30f;

    /// <summary>フレームレート制限</summary>
    [SerializeField, Header("フレームレート制限")] private bool _isCapped = true;

    //-------------------------------------------------------------------------------
    // 録画設定
    //-------------------------------------------------------------------------------

    /// <summary>出力の横幅</summary>
    [SerializeField, Header("出力の横幅")] private int _width = 1920;

    /// <summary>出力の縦幅</summary>
    [SerializeField, Header("出力の縦幅")] private int _height = 1080;

    //-------------------------------------------------------------------------------
    // 録音設定
    //-------------------------------------------------------------------------------

    [SerializeField, Header("録音するかどうか")] private bool _isAudioPreserved = true;

    //-------------------------------------------------------------------------------
    // フォーマット設定
    //-------------------------------------------------------------------------------

    /// <summary>出力形式</summary>
    [SerializeField, Header("出力形式")] private MovieRecorderSettings.VideoRecorderOutputFormat _format;

    /// <summary>ビデオ品質</summary>
    [SerializeField, Header("ビデオ品質")] private UnityEditor.VideoBitrateMode _quality;

    //-------------------------------------------------------------------------------
    // 出力設定
    //-------------------------------------------------------------------------------

    /// <summary>ファイル名</summary>
    [SerializeField, Header("ファイル名")] private string _fileName;

    private void Awake()
    {
        SetUpRecorderController();
        SetUpRecorder();
    }

    /// <summary>コントローラーの設定を行う</summary>
    private void ConfigureRecorderControllerSettings()
    {
        _controllerSettings = ScriptableObject.CreateInstance<RecorderControllerSettings>();

        // レコーディングモードを設定
        _controllerSettings.SetRecordModeToManual();

        // フレームレートの制御方法
        _controllerSettings.FrameRatePlayback = _playback;

        // フレームレート
        _controllerSettings.FrameRate = _frameRate;

        // フレームレートを制限するか
        _controllerSettings.CapFrameRate = _isCapped;
    }

    /// <summary>レコーダーの設定を行う</summary>
    private void ConfigureMovieRecorderSettings()
    {
        _recorderSettings = ScriptableObject.CreateInstance<MovieRecorderSettings>();

        // フレームレートの制御方法
        _recorderSettings.FrameRatePlayback = _playback;

        // フレームレート
        _recorderSettings.FrameRate = _frameRate;

        // フレームレートを制限するか
        _recorderSettings.CapFrameRate = _isCapped;

        // 出力形式
        _recorderSettings.OutputFormat = _format;

        // ビデオ品質
        _recorderSettings.VideoBitRateMode = _quality;

        // 録画設定
        _recorderSettings.ImageInputSettings = 
            new GameViewInputSettings(){OutputWidth = _width, OutputHeight = _height};

        // 録音設定
        _recorderSettings.AudioInputSettings.PreserveAudio = _isAudioPreserved;

        // 有効化
        _recorderSettings.Enabled = true;
    }

    /// <summary>コントローラーのセットアップ</summary>
    private void SetUpRecorderController()
    {
        // コントローラーを設定
        ConfigureRecorderControllerSettings();
        
        // コントローラーを作成
        _controller = new RecorderController(_controllerSettings);
    }

    /// <summary>レコーダーのセットアップ</summary>
    private void SetUpRecorder()
    {
        // レコーダーを設定
        ConfigureMovieRecorderSettings();
        // レコーダーの設定をコントローラーの設定に追加
        _controllerSettings.AddRecorderSettings(_recorderSettings);
    }

    /// <summary>レコーディングを開始する</summary>
    public void StartRecording()
    {
        // 出力ファイル名
        _recorderSettings.OutputFile = $"Assets/Recordings/{_fileName} {_recordingCount}";
        // 録画準備
        _controller.PrepareRecording();
        // 録画開始
        _controller.StartRecording();
    }

    /// <summary>レコーディングを終了する</summary>
    public void StopRecording()
    {
        // 録画終了
        _controller.StopRecording();

        // レコーディングの回数を増やす
        _recordingCount++;
    }
}

```

この状態でシーンのレコーディングを行うと、
開始・終了を呼び出した回数だけ録画ファイルが作成されます。

これで、シーンを連続してレコーディングすることが出来ました！
最後までご覧いただきありがとうございました :laughing: