こういうの作ります
実行環境
- Unity5.5.2f1
- UniRx 5.5.0
- DOTween v1.1.310
UniRx
- 言わずと知れた Unity で Reactive Extensions (Rx) が使えるようになるアセット
- 詳細は他の方が詳しくまとめているのでそちらを参照
やりたいこと
- 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 をアタッチ
- 子要素の数/大きさに合わせて親要素の大きさを変更する
- 詳しくは [Unity] ContentSizeFitterを使ってコンテンツサイズによって可変なUI要素を作る に記載
- 親の pivot を左端に合わせる (0, 0.5)
- RectTransform の AnchorPreset 上で shift 押しながら左の要素選択すると早い
- 子要素に任意の大きさの Page を設置
- 親に TurnPage をアタッチ
- RequireComponent によって自動的に SwipeGesture もアタッチ
- SwipeGestureコンポーネントの PageWidth に子要素の大きさを指定