Skip to content

Instantly share code, notes, and snippets.

@n-yoda
Created December 21, 2015 04:46
Show Gist options
  • Save n-yoda/b3343c090e1f51dc8a02 to your computer and use it in GitHub Desktop.
Save n-yoda/b3343c090e1f51dc8a02 to your computer and use it in GitHub Desktop.
Unityでダブルタップやトリプルタップを直接取れないプラットフォームでダブルタップを扱う何か。
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Touchのラッパー的なクラス。
/// </summary>
public struct WrappedTouch
{
public Touch original { get; private set; }
public int tapCount { get; private set; }
public Vector2 deltaPosition { get; private set; }
public float deltaTime { get; private set; }
public int fingerId { get; private set; }
public TouchPhase phase { get; private set; }
public Vector2 position { get; private set; }
public WrappedTouch(Touch touch, int tapCount = 0)
{
this.tapCount = tapCount;
original = touch;
deltaPosition = original.deltaPosition;
deltaTime = original.deltaTime;
fingerId = original.fingerId;
phase = original.phase;
position = original.position;
}
public WrappedTouch(WrappedTouch wt, int tapCount = 0)
{
this.tapCount = tapCount;
original = wt.original;
deltaPosition = wt.deltaPosition;
deltaTime = wt.deltaTime;
fingerId = wt.fingerId;
phase = wt.phase;
position = wt.position;
}
public WrappedTouch(Vector2 dp, float dt, int id, TouchPhase ph, Vector2 p)
{
deltaPosition = dp;
deltaTime = dt;
fingerId = id;
phase = ph;
position = p;
}
}
/// <summary>
/// 複数回タップを考慮したInput。マウスもTouchに含める。
/// ExecutionOrderをマイナスにすること。
/// </summary>
public class WrappedInput : MonoBehaviour
{
const int mouseFingerId = int.MinValue;
Vector2 lastMouse;
float lastTime;
[SerializeField] float multiTapIntervalMax = 0.3f;
[SerializeField] float multiTapInchMax = 0.2f;
[SerializeField] int multiTapCountMax = 3;
[SerializeField] bool simulateTouchWithMouse = true;
WrappedTouch[] m_touches = new WrappedTouch[0];
List<WrappedTouch> tempTouches = new List<WrappedTouch>();
List<QueuedTouch> touchQueue = new List<QueuedTouch>();
List<QueuedTouch> freeTouches = new List<QueuedTouch>();
#if DEBUG_WRAPPED_INPUT
[SerializeField] List<string> queue;
[SerializeField] List<string> free;
#endif
class QueuedTouch
{
public int count;
public float time;
public WrappedTouch touch;
public bool free;
public bool remove;
public QueuedTouch(WrappedTouch touch)
{
this.time = Time.realtimeSinceStartup;
this.touch = touch;
this.count = 1;
}
public void ApplyNext(WrappedTouch touch)
{
this.time = Time.realtimeSinceStartup;
this.touch = touch;
this.count ++;
}
}
/// <summary>
/// シングルトン
/// </summary>
static WrappedInput instance;
public static WrappedInput Instance
{
get
{
if (!instance)
{
instance = FindObjectOfType<WrappedInput>();
if (!instance)
{
var go = new GameObject("WrappedInput");
instance = go.AddComponent<WrappedInput>();
}
}
return instance;
}
}
void Awake()
{
instance = this;
Input.simulateMouseWithTouches = !simulateTouchWithMouse;
}
public static WrappedTouch[] touches
{
get
{
return Instance.m_touches;
}
}
void Update()
{
var multiTapDistMax = multiTapInchMax * Screen.dpi;
tempTouches.Clear();
var touches = Input.touches;
// マウスをタップに加える
WrappedTouch mouse = new WrappedTouch();
int loop = touches.Length;
if (!Input.simulateMouseWithTouches)
{
loop++;
var dt = Time.realtimeSinceStartup - lastTime;
var dp = (Vector2)Input.mousePosition - lastMouse;
if (Input.GetMouseButtonDown(0))
mouse = new WrappedTouch(dp, dt, mouseFingerId, TouchPhase.Began, Input.mousePosition);
else if (Input.GetMouseButtonUp(0))
mouse = new WrappedTouch(dp, dt, mouseFingerId, TouchPhase.Ended, Input.mousePosition);
else if (Input.GetMouseButton(0))
mouse = new WrappedTouch(dp, dt, mouseFingerId, TouchPhase.Moved, Input.mousePosition);
else
loop--;
lastMouse = Input.mousePosition;
lastTime = Time.realtimeSinceStartup;
}
for (int i = 0; i < loop; i ++)
{
var currentTouch = i == touches.Length ? mouse : new WrappedTouch(touches[i]);
// 既に数える必要が無いタップ
var iFree = freeTouches.FindIndex(x => x.touch.fingerId == currentTouch.fingerId);
if (iFree >= 0) {
if (currentTouch.phase == TouchPhase.Canceled
|| currentTouch.phase == TouchPhase.Ended) {
freeTouches[iFree].remove = true;
}
tempTouches.Add(new WrappedTouch(currentTouch, freeTouches[iFree].count));
continue;
}
// キューに同じIDのタッチがある場合(これはおまけ)
var qSame = touchQueue.FindIndex(x => x.touch.phase == TouchPhase.Began && x.touch.fingerId == currentTouch.fingerId);
if (qSame >= 0)
{
// キューにあるけど、範囲外
if (Vector2.Distance(touchQueue[qSame].touch.position, currentTouch.position) > multiTapDistMax
|| Time.realtimeSinceStartup - touchQueue[qSame].time > multiTapIntervalMax)
{
// 現在のカウントで流す
for (int j = 0; j < touchQueue.Count; j ++)
{
if (touchQueue[j].touch.fingerId == currentTouch.fingerId)
{
tempTouches.Add(new WrappedTouch(touchQueue[j].touch, touchQueue[qSame].count));
touchQueue[j].remove = j != qSame;
}
}
touchQueue[qSame].free = true;
touchQueue.Add(new QueuedTouch(currentTouch));
}
// キューにあってタップカウントが増える
else if (currentTouch.phase == TouchPhase.Began)
{
// カウント0で流す
for (int j = 0; j < touchQueue.Count; j ++)
{
if (touchQueue[j].touch.fingerId == currentTouch.fingerId)
{
tempTouches.Add(new WrappedTouch(touchQueue[j].touch, 0));
touchQueue[j].remove = j != qSame;
}
}
touchQueue[qSame].ApplyNext(currentTouch);
}
// キューにあるのでキューに詰める
else
{
touchQueue.Add(new QueuedTouch(currentTouch));
}
continue;
}
// キューに近い位置のタップがある場合
var qNear = touchQueue.FindIndex(
x => x.touch.phase == TouchPhase.Began
&& Vector2.Distance(x.touch.position, currentTouch.position) < multiTapDistMax
&& Time.realtimeSinceStartup - x.time <= multiTapIntervalMax);
if (qNear >= 0)
{
// カウント0で流す
for (int j = 0; j < touchQueue.Count; j ++)
{
if (touchQueue[j].touch.fingerId == touchQueue[qNear].touch.fingerId)
{
tempTouches.Add(new WrappedTouch(touchQueue[j].touch, 0));
touchQueue[j].remove = j != qNear;
}
}
touchQueue[qNear].ApplyNext(currentTouch);
}
else if (currentTouch.phase == TouchPhase.Began)
{
touchQueue.Add(new QueuedTouch(currentTouch));
}
// 開始が記録されてないタッチが検出...されることはないはず
else
{
Debug.Log("Unexpected touch: " + currentTouch);
}
}
// 時間切れタップ判定
for (int i = 0; i < touchQueue.Count; i ++)
{
if (!touchQueue[i].free && !touchQueue[i].remove && touchQueue[i].touch.phase == TouchPhase.Began
&& Time.realtimeSinceStartup - touchQueue[i].time > multiTapIntervalMax)
{
var ended = touchQueue.FindIndex(x => x.touch.fingerId == touchQueue[i].touch.fingerId
&& (x.touch.phase == TouchPhase.Ended || x.touch.phase == TouchPhase.Canceled)) >= 0;
for (int j = 0; j < touchQueue.Count; j ++)
{
if (touchQueue[j].touch.fingerId == touchQueue[i].touch.fingerId)
{
tempTouches.Add(new WrappedTouch(touchQueue[j].touch, touchQueue[i].count));
touchQueue[j].free = i == j && !ended;
touchQueue[j].remove = i != j || ended;
}
}
}
}
// removeフラッグで消去、freeフラッグでfreeTouchesに移動
freeTouches.RemoveAll(x => x.remove);
for (int i = 0; i < touchQueue.Count; i ++)
{
if (touchQueue[i].free) {
freeTouches.Add(touchQueue[i]);
}
}
touchQueue.RemoveAll(x => x.remove || x.free);
for (int i = 0; i < freeTouches.Count; i ++)
{
freeTouches[i].remove = false;
}
// コピー
System.Array.Resize(ref m_touches, tempTouches.Count);
tempTouches.CopyTo(m_touches);
#if DEBUG_WRAPPED_INPUT
free = freeTouches.Select(x => x.touch.fingerId + ":" + x.touch.tapCount).ToList();
queue = touchQueue.Select(x => x.touch.fingerId + ":" + x.touch.tapCount).ToList();
#endif
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment