【UniRx】マウスの長押しを判定する


**UniRxについての記事のまとめは[こちら](http://qiita.com/toRisouP/items/48b9fa25df64d3c6a392)**

---

UniRxを使ってマウスの長押し判定を行うストリームを書いてみました。

* 長押し判定（一定時間以上マウスがクリックされ続けた時）
* 長押しキャンセル（一定時間以内にマウスクリックが中断された時）

をそれぞれ検出できるストリームを、**オペレータチェーンのみ**で書くパターンと**コルーチンと組み合わせる**パターンで２種類書いてみました。


#オペレータチェーンのみで書く場合

```csharp:オペレータチェーンのみ
void Start()
{
    var mouseDownStream = this.UpdateAsObservable().Where(_ => Input.GetMouseButtonDown(0));
    var mouseUpStream = this.UpdateAsObservable().Where(_ => Input.GetMouseButtonUp(0));

    //長押しの判定
    mouseDownStream
        //マウスクリックされたら3秒後にOnNextを流す
        .SelectMany(_ => Observable.Timer(TimeSpan.FromSeconds(3)))
        //途中でMouseUpされたらストリームをリセット
        .TakeUntil(mouseUpStream)
        .RepeatUntilDestroy(this.gameObject)
        .Subscribe(_ => Debug.Log("長押し"));

    //長押しのキャンセル判定
    mouseDownStream.Timestamp()
        .Zip(mouseUpStream.Timestamp(), (d, u) => (u.Timestamp - d.Timestamp).TotalMilliseconds / 1000.0f)
        .Where(time => time < 3.0f)
        .Subscribe(t => Debug.Log(t + "秒でキャンセル"));
}
```

ちょっとゴリ押しですけど一応動作します。

#コルーチンと組み合わせる場合

```csharp:コルーチンと合わせる
public class LongClick : MonoBehaviour
{

    #region 長押しストリーム
    Subject<Unit> longClickSubject = new Subject<Unit>();

    IObservable<Unit> LongClickAsObservable
    {
        get
        {
            return longClickSubject;
        }
    }
    #endregion 

    #region 長押しキャンセルストリーム
    Subject<float> longClickCancelSubject = new Subject<float>();

    IObservable<float> LongClickCancelAsObservable
    {
        get
        {
            return longClickCancelSubject;
        }
    }

    #endregion

    Coroutine longClickCoroutine;

    void Start()
    {
        longClickCoroutine = StartCoroutine(LongClickCoroutine(3.0f, longClickSubject, longClickCancelSubject));

        LongClickAsObservable.Subscribe(_ => Debug.Log("長押し"));
        LongClickCancelAsObservable.Subscribe(t => Debug.Log(t + "秒でキャンセル"));

    }

    /// <summary>
    /// 長押し判定コルーチン
    /// </summary>
    IEnumerator LongClickCoroutine(float threshold, IObserver<Unit> longClickObserver, IObserver<float> longClickCancelObserver)
    {
        var isLongClicked = false; //長押しのOnNext発行済か
        var count = 0.0f;

        while (true)
        {
            if (Input.GetMouseButton(0))
            {
                count += Time.deltaTime;
                if (!isLongClicked && count > threshold)
                {
                    isLongClicked = true;
                    longClickObserver.OnNext(Unit.Default);
                }
            }
            else if (Input.GetMouseButtonUp(0))
            {
                isLongClicked = false;
                if (count > 0)
                {
                    longClickCancelObserver.OnNext(count);
                    count = 0;
                }
            }
            yield return null;

        }
    }


    void OnDestroy()
    {
        if (longClickCoroutine != null)
            StopCoroutine(longClickCoroutine);
    }
}
```

判定処理を無限ループするコルーチンの中に書き、結果をストリームで外部に通知する形にしてみました。
オペレータチェーンの連結でわけのわからないストリームになるくらいなら、諦めて手続き的に書いた方が早い場合もあります。雑多な処理は全部コルーチンに押し込まれていて外に出てこないのでまぁ許容範囲かなぁと。

またこういった汎用的に使いそうな処理はObservableTriggerとして定義しておくと良いかもしれないですね。