UniRx + MessagePipe を利用したフリック入力のデモンストレーション(2)


デモ

解説

前回の記事の続きです。

記事で実装しているフリック判定には
ドラッグ対象を動かし始めた座標 、 オブジェクトの速度が一定の速度を超えた瞬間の座標 の2つを使っています

つまり、オブジェクトがどの角度で動いているかを認識していません。

ということは、素早くカクカクなりグルグルなり直線を引く以外の動作でも、フリック判定が行われてしまいます。

そこで、動かした角度を判定する処理を加えます。

  1. DragEventを受け取るストリームに角度を判定する処理を追加します

    
    private Vector3 moveStartPosition;
    private ReactiveProperty<bool> isMoving = new ReactiveProperty<bool>(false); // 動作中の場合、true
    private ReactiveProperty<Vector3> draggingVerocity = new ReactiveProperty<Vector3>(Vector3.zero); // ドラッグ中に得た速度の合計
    [SerializeField] private float acceptableAngle = 1;// フリック角度のズレ許容範囲
    
    // ReactiveProperty(DragEvent) から平均速度を算出する
    drag.Where(x => x != null)
        .Buffer(15).Where(d => d.Count >= 2)
        .Select(d => {
            var a = d.First();
            var b = d.Last();
            var dx = b.position - a.position;
            var dt = b.time - a.time;
            var verocity = dx / dt;
            if (!isMoving.Value) {
                moveStartPosition = a.position;// 動き出しのポジションをセット
            }
            return dx / dt;
        })
        .Subscribe(v => {
            draggingVerocity.Value = (draggingVerocity.Value + v) / 2;
            // 平均速度が閾値を超えている場合、isMoving
            isMoving.Value = v.magnitude > thresholdMoving
              && Math.Abs(Vector3.SignedAngle(draggingVerocity.Value, v, new Vector3(0, 0, 1))) < acceptableAngle;
              // ↑ 1行加えました。解説は後述します
        }).AddTo(this);
    

Vector3.SignedAngle

Vector3.SignedAngle(from, to, axis)

from と to の間の角度が返ってくる関数になっています。

axis は回転軸です。Z軸平面のため、Vector3(0,0,1)を利用します。

戻りが-180度~180度のfloat 型なので Math.Abs で絶対値を取っています

from に累計速度(現在速度)ベクトル、to に単位時間あたりの速度ベクトルを用いることで同じ角度で動かされていることを判定できますね

公式ドキュメント
https://docs.unity3d.com/ja/current/ScriptReference/Vector3.SignedAngle.html

補足

  • 平均速度を算出していない

以下のソースにて平均速度を算出するとコメントしてありますが、

// ReactiveProperty(DragEvent) から平均速度を算出する
drag.Where(x => x != null)
    .Buffer(15).Where(d => d.Count >= 2)
    .Select(d => {
        var a = d.First();
        var b = d.Last();
        var dx = b.position - a.position;
        var dt = b.time - a.time;
        var verocity = dx / dt;
        return verocity;
    })

15回ドラッグの位置を取得して、First(),Last() の値しか使っていません。

つまり1つ目と15回目の速度しか計算できていないです。
厳密に計算するのであれば、 .Buffer(2) にするべきですね。

低フレームレート環境などでは15回に1度では動きに問題があるでしょう、調節が必要です

  • フリックが連続する

剣を振って衝撃波を出す、バットを振ってボールを打つなどに応用する場合、

マウスをブンブンすると衝撃波は出っぱなしでボールは打ちっぱなしになるので、処理を止める必要があります。

そういった場合でもUniRxのオペレータを上手く組み合わせることで処理をまとめることができるので、やりごたえが出てくると思います

記事について

ソースに関しては、抜粋のためコピペでは動作しない可能性があります。
コメント、補足、LGTM大歓迎です。(やる気が出ます)

フリック判定としては、満足する仕様になったので次回があればフリック判定をアセット化するくらいでしょうか