[Unity] [UniRx] とっても便利なCompositeDisposableのお話

# はじめに

　こんにちは！ Life is Tech! メンターのダイキチです。
 
　2023年も残りわずかとなりましたが、いかがお過ごしでしょうか？
 　僕はお正月のおみくじで **大吉** を引くためにウォーミングアップを始めました。（？）

　さて、この記事は [Life is Tech ! Advent Calendar 2023](https://qiita.com/advent-calendar/2023/life-is-tech?fbclid=IwAR2UU-Fp4OJHJ3mGiWmmG27TbaIh5LsT5xWfS7maDxe3QSpH8x8N30B2ryE) の21日目の記事です。
　今回は`UniRx`の`Disposable.Create(Action disposeAction)`及び`CompositeDisposable`について、サンプルコードを書きながら紹介します。

# `Disposable.Create(Action disposeAction)`を使ってみよう！
　早速ですが、Unityでカメラの追従を実装するために、カメラの親子関係を管理する`CameraHandler`クラスを以下のように作ったとしましょう。
```cs
using UnityEngine;

public interface ICameraHandler
{
    /// <summary>
    /// 親子関係を構築
    /// </summary>
    /// <param name="parent"></param>
    void SetParent(GameObject parent);

    /// <summary>
    /// 親子関係を解除
    /// </summary>
    void Release();
}

public class CameraHandler : MonoBehaviour, ICameraHandler
{
    /// <summary>
    /// メインカメラ
    /// </summary>
    [SerializeField]
    private GameObject m_MainCamera;

    /// <summary>
    /// カメラを追従させる
    /// </summary>
    /// <param name="parent"></param>
    void ICameraHandler.SetParent(GameObject parent)
    {
        m_MainCamera.transform.SetParent(parent.transform);
    }

    /// <summary>
    /// カメラの追従を止める
    /// </summary>
    void ICameraHandler.Release()
    {
        m_MainCamera.transform.parent = null;
    }
}
```

```cs
/// <summary>
/// 利用者側のサンプルコード
/// </summary>
public class Player : MonoBehaviour
{
    private ICameraHandler m_CameraHandler;

    public void SetParent()
    {
        m_CameraHandler.SetParent(this.gameObject);
    }

    public void Release()
    {
        m_CameraHandler.Release();
    }
}
```

　`ICameraHandler` は親子関係の構築と解除のメソッドを提供しています。
　このままでも十分使えますが、ここで`Disposable.Create(Action disposeAction)`を使った実装に作り変えてみると、以下のようになります。

```cs
using UnityEngine;
using System;
using UniRx;

public interface ICameraHandler
{
    /// <summary>
    /// 親子関係を構築
    /// </summary>
    /// <param name="parent"></param>
    /// <returns>親子関係を解除</returns>
    IDisposable SetParent(GameObject parent);
}

public class CameraHandler : MonoBehaviour, ICameraHandler
{
    /// <summary>
    /// メインカメラ
    /// </summary>
    [SerializeField]
    private GameObject m_MainCamera;

    /// <summary>
    /// カメラを追従させる
    /// </summary>
    /// <param name="parent"></param>
    /// <returns>親子関係を解除</returns>
    IDisposable ICameraHandler.SetParent(GameObject parent)
    {
        m_MainCamera.transform.SetParent(parent.transform);
        return Disposable.Create(() => m_MainCamera.transform.parent = null);
    }
}
```

```cs
/// <summary>
/// 利用者側のサンプルコード
/// </summary>
public class Player : MonoBehaviour
{
    private ICameraHandler m_CameraHandler;
    
    private IDisposable m_CameraRelease;

    public void SetParent()
    {
        if (m_CameraRelease != null)
        {
#if DEBUG
            Debug.LogWarning("親子関係はすでに構築済みです。");
#endif
            return;
        }

        m_CameraRelease = m_CameraHandler.SetParent(this.gameObject);
    }

    public void Release()
    {
        m_CameraRelease?.Dispose(); // IDiposableを使って親子関係を解除
        m_CameraRelease = null;
    }
}
```

　主な変更点は、`ICameraHandler`から提供されるメソッドが`IDisposable SetParent(GameObject parent)`のみになったことです。
　戻り値の`IDisposable`には、カメラとの親子関係を解除する処理が入っていて、これは`Disposable.Create(Action disposeAction)`を使って作成しています。
 
　こうすることで利用者側は、戻り値の`IDisposable`をメンバ変数で覚えておくことで、`ICameraHandler`を介さずに親子関係の解除をすることができるようになりました。

 # `CompositeDisposable`を使ってみよう！
 　`Disposable.Create(Action disposeAction)`で作成した`IDisposable`を戻り値にすると、利用者側に終了処理を教えることができるということが伝わったかと思います。

　しかし、実際に開発をしていると複数の`IDisposable`を扱いたくなるときがあります。そんなときは`CompositeDisposable`を使いましょう。

 ```cs
/// <summary>
/// 利用者側のサンプルコード
/// </summary>
public class Player : MonoBehaviour
{
    private ICameraHandler m_CameraHandler;
    private IDummyHandler m_DummyHandler;
    
    private CompositeDisposable m_CompositeDisposable = new CompositeDisposable();

    public void SetParent()
    {
        var cameraDisposable = m_CameraHandler.SetParent(this.gameObject);
        m_CompositeDisposable.Add(cameraDisposable);// IDisposableを登録

        var dummyDisposable = m_DummyHandler.DummyMethod();
        m_CompositeDisposable.Add(dummyDisposable); // IDisposableを登録
    }

    public void Release()
    {
        m_CompositeDisposable.Clear(); // IDisposableをまとめてDispose
    }
}
```

　`void Add(IDisposable item)`で`IDisposable`を`CompositeDisposable`に登録できます。
 　また、`void Clear()`で登録した`IDisposable`をまとめてDisposeできます。このとき、登録されていた`IDisposable`は全て破棄されます。

 # おわりに
　今回は `UniRx`の`Disposable.Create(Action disposeAction)`及び`CompositeDisposable`について紹介しました。

　細かい説明は端折って解説してしまったので、より詳しいことを知りたい方はこの後に載っている参考文献をお読みください。

　さて、明日の [Life is Tech ! Advent Calendar 2023](https://qiita.com/advent-calendar/2023/life-is-tech?fbclid=IwAR2UU-Fp4OJHJ3mGiWmmG27TbaIh5LsT5xWfS7maDxe3QSpH8x8N30B2ryE) は [みったに先生](@KeichiMizutani)の記事になります。
　どうやらUI演出についての記事のようです。楽しみですね！！

 　以上、Life is Tech! メンターのダイキチでした。またね〜！

  # 参考文献
  https://qiita.com/su10/items/4e01023a7a1607e8323e