かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

UniRxを使ったシンプルなコードを書いて入門してみた

id:neueccさんの最新作と思われるUniRxをようやく触りました。

Reactive ExtensionsのUnity版。かっこいい。ということでマウスのドラッグを扱う簡単な例を書いて入門してみました。こんな感じ。

using UnityEngine;
using System.Collections;
using UniRx;

public class UniRxCubeBehavior : ObservableMonoBehaviour
{
    public override void Awake()
    {
        // ドラッグ対象のオブジェクト上でマウスが押されたときに値を発行するIObservable
        var mouseDownOnUniRxCube = this.UpdateAsObservable()
            .Where(_ => Input.GetMouseButtonDown(0))
            .Select(_ => Camera.main.ScreenPointToRay(Input.mousePosition))
            .Select(x =>
                {
                    RaycastHit rh;
                    var hit = Physics.Raycast(x, out rh);
                    return Tuple.Create(hit, rh);
                })
            .Where(x => x.Item1 && x.Item2.collider.gameObject == this.gameObject)
        // マウスが離されたときに値を発行するIObservable
        var mouseUp = this.UpdateAsObservable()
            .Where(_ => Input.GetMouseButtonUp(0));

        this.UpdateAsObservable()
            // マウスが押されてるとき
            .Where(_ => Input.GetMouseButton(0))
            // 対象オブジェクトの上でマウスが押されるまでスキップして
            .SkipUntil(mouseDownOnUniRxCube)
            // マウスが離されるまで値を拾う
            .TakeUntil(mouseUp)
            // それを繰り返す
            .Repeat()
            // 現在のマウスの位置から移動位置を算出して
            .Select(_ => Input.mousePosition)
            .Select(x => 
            {
                RaycastHit rh;
                Physics.Raycast(Camera.main.ScreenPointToRay(x), out rh, 1 << LayerMask.NameToLayer("Background"));
                return rh.point;
            })
            // z座標は移動させないように変換して
            .Select(x => new Vector3(x.x, x.y, this.transform.position.z))
            // 位置を設定する
            .Subscribe(x => this.transform.position = x);

        base.Awake();
    }
}

気合で1ステートメントに収めることもできるけど、個人的には適度にわけるのがお好み。これをCubeあたりに割り当てて、Backgroundというレイヤを割り当てた透明な背景を置いておくと、マウスでドラッグが動くようになります。

因みにRxを使わない場合はこんな感じになりました。

using UnityEngine;
using System.Collections;

public class NormalCubeBehavior : MonoBehaviour
{
    private bool drag;

    void Update()
    {
        // ドラッグ中じゃなくてマウスが押されたら
        if (!drag && Input.GetMouseButtonDown(0))
        {
            // 対象オブジェクトの上でクリックされたかチェックして
            RaycastHit rh;
            if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out rh))
            {
                if (rh.collider.gameObject == this.gameObject)
                {
                    // 対象オブジェクトの場合は、ドラッグ中の状態にする
                    drag = true;
                }
            }
        }

        // ドラッグ中にマウスが離されたらドラッグ中を解除して処理を抜ける
        if (drag && Input.GetMouseButtonUp(0))
        {
            drag = false;
            return;
        }

        // ドラッグ中は
        if (drag)
        {
            // 現在のマウスの位置を算出して
            RaycastHit rh;
            if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out rh, 1 << LayerMask.NameToLayer("Background")))
            {
                // マウスの位置を新しい場所に設定する
                var newPos = new Vector3(rh.point.x, rh.point.y, this.transform.position.z);
                this.transform.position = newPos;
            }
        }
    }
}

まだシンプルな例だから、そんなに変わりないかな。でも、ネストが嫌な感じを醸し出してる。