UniRxでDOTweenをObservableにする

# 概要
DOTweenをObservableにしました。
すべてのアニメーションの完了を待ってから何かしたいといった場合に、
Observable化されていると、何かと便利です。

### DOTween
無料版と有料版があり、それぞれアセットストアからDLできます。
[DOTween (HOTween v2)](https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676)
[DOTweenPro](https://assetstore.unity.com/packages/tools/visual-scripting/dotween-pro-32416)

### 使用バージョン
DOTweenPro v1.0.075

# コード

```csharp:DOTweenExtensions.cs
using UniRx;
using System;
using DG.Tweening;

static public partial class DOTweenExtensions
{
    static public IObservable<Tween> OnCompleteAsObservable(this Tween tweener)
    {
        return Observable.Create<Tween>(o =>
        {
            tweener.OnComplete(() =>
            {
                o.OnNext(tweener);
                o.OnCompleted();
            });
            return Disposable.Create(() =>
            {
                tweener.Kill();
            });
        });
    }

    static public IObservable<Sequence> PlayAsObservable(this Sequence sequence)
    {
        return Observable.Create<Sequence>(o =>
        {
            sequence.OnComplete(() =>
            {
                o.OnNext(sequence);
                o.OnCompleted();
            });
            sequence.Play();
            return Disposable.Create(() =>
            {
                sequence.Kill();
            });
        });
    }

#if true //DOTweenPro Extensions
    static public IObservable<DOTweenAnimation> DOPlayAsObservable(
        this DOTweenAnimation animation,
        bool rewind = false)
    {
        return Observable.Create<DOTweenAnimation>(o =>
        {
            if (rewind) animation.DORewind();

            animation.tween.OnComplete(() =>
            {
                o.OnNext(animation);
                o.OnCompleted();
            });
            animation.DOPlay();
            return Disposable.Empty;
        });
    }

    static public IObservable<DOTweenAnimation> DOPlayByIdAsObservable(
        this DOTweenAnimation animation,
        string id,
        bool rewind = false)
    {
        return Observable.Create<DOTweenAnimation>(o =>
        {
            if (rewind) animation.DORewind();

            animation.tween.OnComplete(() =>
            {
                o.OnNext(animation);
                o.OnCompleted();
            });
            animation.DOPlayById(id);
            return Disposable.Empty;
        });
    }

    static public IObservable<DOTweenAnimation> DOPlayAllByIdAsObservable(
        this DOTweenAnimation animation,
        string id,
        bool rewind = false)
    {
        return Observable.Create<DOTweenAnimation>(o =>
        {
            if (rewind) animation.DORewind();

            animation.tween.OnComplete(() =>
            {
                o.OnNext(animation);
                o.OnCompleted();
            });
            animation.DOPlayAllById(id);
            return Disposable.Empty;
        });
    }

    static public IObservable<DOTweenAnimation> DORestartAsObservable(
        this DOTweenAnimation animation,
        bool fromHere = false)
    {
        return Observable.Create<DOTweenAnimation>(o =>
        {
            animation.tween.OnComplete(() =>
            {
                o.OnNext(animation);
                o.OnCompleted();
            });
            animation.DORestart(fromHere);
            return Disposable.Empty;
        });
    }

    static public IObservable<DOTweenAnimation> DORestartByIdAsObservable(
        this DOTweenAnimation animation,
        string id)
    {
        return Observable.Create<DOTweenAnimation>(o =>
        {
            animation.tween.OnComplete(() =>
            {
                o.OnNext(animation);
                o.OnCompleted();
            });
            animation.DORestartById(id);
            return Disposable.Empty;
        });
    }

    static public IObservable<DOTweenAnimation> DORestartAllByIdAsObservable(
        this DOTweenAnimation animation,
        string id)
    {
        return Observable.Create<DOTweenAnimation>(o =>
        {
            animation.tween.OnComplete(() =>
            {
                o.OnNext(animation);
                o.OnCompleted();
            });
            animation.DORestartAllById(id);
            return Disposable.Empty;
        });
    }
#endif //DOTweenPro Extensions
}
```

DOTweenProでのみ使用可能な`DOTweenAnimation`を使用しているコードは
if～endifで囲っています。無料版を使用している場合は`DOTweenAnimation`が使用不可なので、
falseにしてください。

# 使用例

### DOLocalMove
```csharp
        this.transform.DOLocalMove(new Vector3(-200f, 0f, 0f), 1f)
            .OnCompleteAsObservable()
            .Subscribe(_ => 
            {
                Debug.Log("Completed.");
            })
            .AddTo(this);
```
### シーケンス
```csharp

        // シーケンス
        var sequence = DOTween.Sequence();
        sequence.Append(anim.tween);
        sequence.PlayAsObservable().Subscribe(_ =>
        {
            Debug.Log("Complete Sequence.");
        })
        .AddTo(this);
```

### Observable.WhenAllですべてのアニメの待機完了待ち

```csharp

    [SerializeField] DOTweenAnimation[] animations;

    void Start()
    {
        Observable.WhenAll(animations.Select(x => x.DOPlayAsObservable(rewind)))
        .Subscribe(x =>
        {
            Debug.Log("Completed.");
        });
        .AddTo(this);
    }
```

### AsyncMessageBrokerでアニメの待機完了待ち
他にも`MessageBroker`の非同期版である`AsyncMessageBroker`で、
送り手がメッセージを`Publish`して誰がReceiveしているかわからないけど完了するまで
送り手を待機させるといった、疎結合な非同期処理も可能です。

```csharp
    public class AnimEvent {}

    // 受け手
    public class SampleReceiver : MonoBehaviour
    {
        [SerializeField] DOTweenAnimation anim;

        void Awake()
        {
            AsyncMessageBroker.Default.Subscribe<AnimEvent>(_ =>
                bool rewind = true;
                anim.DOPlayByIdAsObservable("in", rewind)
            ).AddTo(this);
        }
    }

    // 送り手
    public class SamplePublisher : MonoBehaviour
    {
        void Start()
        {
            AsyncMessageBroker.Default.PublishAsync<AnimEvent>(new AnimEvent()).Subscribe(_ =>
            {
                Debug.Log("Publish Completed.");
            });
        }
    }
```