Edited at

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

More than 1 year has passed since last update.

こういうの作ります

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