LoginSignup
31

More than 5 years have passed since last update.

[Unity] uGUIとUniRxで四方向へのスワイプを実装する

Last updated at Posted at 2017-04-03

こういうの作ります

swipegesture_d.gif

実行環境

  • Unity5.5.2f1
  • UniRx 5.5.0
  • DOTween v1.1.310

UniRx

やりたいこと

  • uGUI要素上におけるスワイプ/フリック処理の実装
    • e.g) 左右方向にスワイプしたら次の要素が表示される
  • FingerGestures とか Input.Touches 使えとか言わない

コード

SwipeGesture
using System;
using UniRx;
using UniRx.Triggers;
using UnityEngine;

public class SwipeGesture : MonoBehaviour
{
    public float ThresholdSenconds = 1.0f;
    public float ThresholdDistance = 100.0f;

    private Subject<Unit> onSwipeLeft = new Subject<Unit>();
    public IObservable<Unit> OnSwipeLeft {
        get { return onSwipeLeft; }
    }

    private Subject<Unit> onSwipeRight = new Subject<Unit>();
    public IObservable<Unit> OnSwipeRight {
        get { return onSwipeRight; }
    }

    private Subject<Unit> onSwipeDown = new Subject<Unit>();
    public IObservable<Unit> OnSwipeDown {
        get { return onSwipeDown; }
    }

    private Subject<Unit> onSwipeUp = new Subject<Unit>();
    public IObservable<Unit> OnSwipeUp {
        get { return onSwipeUp; }
    }

    private Vector2 beginPosition;
    private DateTime beginTime;

    void OnEnable()
    {
        var eventTrigger = this.gameObject.AddComponent<ObservableEventTrigger>();

        eventTrigger
            .OnBeginDragAsObservable()
            .TakeUntilDisable(this)
            .Where(eventData => eventData.pointerDrag.gameObject == this.gameObject)
            .Select(eventData => eventData.position)
            .Subscribe(position =>
            {
                this.beginPosition = position;
                this.beginTime = DateTime.Now;
            });

        var onEndDragObservable = eventTrigger
            .OnEndDragAsObservable()
            .TakeUntilDisable(this)
            .Where(eventData => (DateTime.Now - this.beginTime).TotalSeconds < ThresholdSenconds)
            .Select(eventData => eventData.position)
            .Share();

        // left
        onEndDragObservable
            .Where(position => beginPosition.x > position.x)
            .Where(position => Mathf.Abs(beginPosition.x - position.x) >= this.ThresholdDistance)
            .Subscribe(_ => onSwipeLeft.OnNext(Unit.Default));

        // right
        onEndDragObservable
            .Where(position => position.x > beginPosition.x)
            .Where(position => Mathf.Abs(position.x - beginPosition.x) >= this.ThresholdDistance)
            .Subscribe(_ => onSwipeRight.OnNext(Unit.Default));

        // down
        onEndDragObservable
            .Where(position => beginPosition.y > position.y)
            .Where(position => Mathf.Abs(beginPosition.y - position.y) >= this.ThresholdDistance)
            .Subscribe(_ => onSwipeDown.OnNext(Unit.Default));

        // up
        onEndDragObservable
            .Where(position => position.y > beginPosition.y)
            .Where(position => Mathf.Abs(position.y - beginPosition.y) >= this.ThresholdDistance)
            .Subscribe(_ => onSwipeUp.OnNext(Unit.Default));

    }
}

説明

  • ObservableEventTrigger を使用することで uGUI における任意の EventTrigger 発火時に Observable を生成することが出来ます
  • 今回は以下の2つを利用しています
    • OnBeginDragAsObservable : ドラッグ開始時に Observable を発火する
    • OnEndDragAsObservable : ドラッグ終了時に Observable を発火する
  • ドラッグ開始時にはスタート位置と開始時間を記録します
  • ドラッグ終了時に一定時間 (ThresholdSenconds) 以内かつ閾値 (ThresholdDistance) 以上の任意方向の移動があれば、該当する方向の Subject.OnNext を実行します

ページ送りを実装する

  • サンプルとして上記のスワイプ処理を利用してページ送りを実装する
  • モーションには DOTween を使用
TurnPage
using DG.Tweening;
using UniRx;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(SwipeGesture), typeof(HorizontalLayoutGroup))]
public class TurnPage : MonoBehaviour
{
    public float PageWidth = 1080;

    private RectTransform rectTransform;
    private SwipeGesture swipeGesture;
    private Tween moveAnimation;
    private int pageCount;
    private int currentPage = 1;

    void Awake()
    {
        DOTween.Init();
        DOTween.defaultAutoPlay = AutoPlay.None; // Tween生成時に自動再生させない
    }

    void OnEnable()
    {
        this.rectTransform = this.GetComponent<RectTransform>();
        this.swipeGesture = this.GetComponent<SwipeGesture>();
        this.pageCount = this.transform.childCount;

        // next
        this.swipeGesture
            .OnSwipeLeft
            .Where(_ => currentPage < pageCount) // 最大ページ以前である場合のみ進める
            .Where(_ => this.moveAnimation == null || !this.moveAnimation.IsPlaying()) // アニメーション実行中ではない
            .Subscribe(_ =>
            {
                this.currentPage++;
                this.moveAnimation = this.rectTransform
                    .DOAnchorPosX(rectTransform.anchoredPosition.x - this.PageWidth, 1.0f)
                    .SetEase(Ease.OutBounce)
                    .Play();
            });

        // back
        this.swipeGesture
            .OnSwipeRight
            .Where(_ => currentPage > 1) // 1ページ目以降である場合のみ戻れる
            .Where(_ => this.moveAnimation == null || !this.moveAnimation.IsPlaying()) // アニメーション実行中ではない
            .Subscribe(_ =>
            {
                this.currentPage--;
                this.moveAnimation = this.rectTransform
                    .DOAnchorPosX(rectTransform.anchoredPosition.x + this.PageWidth, 1.0f)
                    .SetEase(Ease.OutBounce)
                    .Play();
            });
    }
}

使い方

  • HorizontalLayoutGroup (横に要素を並べるリスト) を親にアタッチ
  • 親に ContentSizeFitter をアタッチ
  • 親の pivot を左端に合わせる (0, 0.5)
    • RectTransform の AnchorPreset 上で shift 押しながら左の要素選択すると早い
  • 子要素に任意の大きさの Page を設置
  • 親に TurnPage をアタッチ
    • RequireComponent によって自動的に SwipeGesture もアタッチ
  • SwipeGestureコンポーネントの PageWidth に子要素の大きさを指定

swipegesture.gif

DEMO

swipegesture_demo.gif

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31