In modern Unity3D, we use the IPointerDownHandler family of calls.
Regarding the IPointerDownHandler
family of calls,
public class FingerMove:MonoBehaviour, IPointerDownHandler...
{
public void OnPointerDown (PointerEventData data)
{
Of course they are fantastic
But how do you deal with multiple touches in a serious way?
You can "do it all by hand" tracking the touches yourself, but it seems incredible Unity would want you to do that for something so absolutely basic. (I mean - it's a game engine. Sure, I could also write all my own rendering and physics!)
Here's an example of basically "cowboy programming", just doing it by hand with no software engineering. What's the real solution?
//
// example of programming a pinch (as well as swipes) using modern Unity
//
// here we are forced to track "by hand" in your own code
// how many fingers are down and which
// fingers belong to you etc etc:
//
// pedagogic example code:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.EventSystems;
public class FingerMove:MonoBehaviour,
IPointerDownHandler, IDragHandler, IPointerUpHandler
{
// these three for the ordinary one-finger-only drag
private Vector2 prevPoint;
private Vector2 newPoint;
private Vector2 screenTravel;
// and this one is the ordinary one-finger-only drag
private int currentMainFinger = -1;
// and this for the (strictly second finger only) drag...
private int currentSecondFinger = -1;
private Vector2 posA;
private Vector2 posB;
private float previousDistance = -1f;
private float distance;
private float pinchDelta = 0f;
public void OnPointerDown (PointerEventData data)
{
if (currentMainFinger == -1)
{
// this is the NEW currentMainFinger
currentMainFinger = data.pointerId;
prevPoint = data.position;
// and for the drag (if it becomes used)...
posA = data.position;
return;
}
if (currentSecondFinger == -1)
{
// this is the NEW currentSecondFinger
currentSecondFinger = data.pointerId;
posB = data.position;
figureDelta();
previousDistance = distance;
return;
}
Debug.Log("third+ finger! (ignore)");
}
public void OnDrag (PointerEventData data)
{
// handle single-finger moves (swipes, drawing etc) only:
if ( currentMainFinger == data.pointerId )
{
newPoint = data.position;
screenTravel = newPoint - prevPoint;
prevPoint = newPoint;
if (currentSecondFinger == -1)
{
Debug.Log("NO 2f");
_processSwipe(); // handle it your way
}
else
{
}
// and for two-finger if it becomes used next frame
// or is already being used...
posA = data.position;
}
if (currentSecondFinger == -1) return;
// handle two-finger (eg, pinch, rotate etc)...
if ( currentMainFinger == data.pointerId ) posA = data.position;
if ( currentSecondFinger == data.pointerId ) posB = data.position;
figureDelta();
pinchDelta = distance - previousDistance;
previousDistance = distance;
_processPinch(); // handle it your way
}
private void figureDelta()
{
// when/if two touches, keep track of the distance between them
distance = Vector2.Distance(posA, posB);
}
public void OnPointerUp (PointerEventData data)
{
if ( currentMainFinger == data.pointerId )
{
currentMainFinger = -1;
}
if ( currentSecondFinger == data.pointerId )
{
currentSecondFinger = -1;
}
}
private float sensitivity = 0.3f;
// in this example, the swipes/pinch affects these three calls:
public Changer orbitLR;
public Changer orbitUD;
public Changer distanceZ;
// initial values of those...
private float LR = -20f;
private float UD = 20f;
private float distanceCam = 5f;
private void _processSwipe()
{
// in this example, just left-right or up-down swipes
LR = LR + sensitivity * screenTravel.x;
UD = UD - sensitivity * screenTravel.y;
LR = Mathf.Clamp(LR, -150f, 30f);
UD = Mathf.Clamp(UD, 5f, 50f);
orbitLR.RotationY = LR;
orbitUD.RotationX = UD;
}
private void _processPinch()
{
// in this example, pinch to zoom
distanceCam = distanceCam - pinchDelta * 0.0125f;
distanceCam = Mathf.Clamp(distanceCam, 3f, 8f);
distanceZ.DistanceZ = distanceCam;
}
}
(Note, please do not answer regarding the legacy "Touches" system which is unusable. This about normal modern Unity development.)
I don't like to answer my own questions, but after much investigation and expert input, the following is the only way to do it.
Let's summarize:
You might think "it sucks it doesn't work automagically without adding a daemon." BUT - in fact, with Unity's own you have to add a daemon, the "TouchInput" family. (Which they sometimes automatically add, sometimes they forget and you have to do it.)
So quite simply, the "chase" for automagic is silly, forget it. You have to add a daemon.
Quite simply, it's not good engineering to go down the path of subclassing StandAloneInputModule, since Unity messed-up. You simply use IPointerDownHandler/etc in your new daemons. More discussion on this below.
Below I give examples for "single touch" and for "pinch". These are production-ready. You can write your own for other situations such as four-touch etc. So, with the pinch-daemon (literally just drop it on the object in question), it's then insanely easy to handle pinches:
public void OnPinchZoom (float delta)
{
_processPinch(delta);
}
Difficult to see it being any easier.
So do this, until Unity remembers it's product is "used on phones" and they add calls for pinch, etc.
Make a cube, put your own script on it FingerMove
.
Make the script, say move the camera LR, UD. (Or whatever - just Debug.Log the changes.)
Paste in this handler script...
/*
ISingleFingerHandler - handles strict single-finger down-up-drag
Put this daemon ON TO the game object, with a consumer of the service.
(Note - there are many, many philosophical decisions to make when
implementing touch concepts; just some issues include what happens
when other fingers touch, can you "swap out" etc. Note that, for
example, Apple vs. Android have slightly different takes on this.
If you wanted to implement slightly different "philosophy" you'd
do that in this script.)
*/
public interface ISingleFingerHandler
{
void OnSingleFingerDown (Vector2 position);
void OnSingleFingerUp (Vector2 position);
void OnSingleFingerDrag (Vector2 delta);
}
/* note, Unity chooses to have "one interface for each action"
however here we are dealing with a consistent paradigm ("dragging")
which has three parts; I feel it's better to have one interface
forcing the consumer to have the three calls (no problem if empty) */
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
public class SingleFingerInputModule:MonoBehaviour,
IPointerDownHandler,IPointerUpHandler,IDragHandler
{
private ISingleFingerHandler needsUs = null;
// of course that would be a List,
// just one shown for simplicity in this example code
private int currentSingleFinger = -1;
private int kountFingersDown = 0;
void Awake()
{
needsUs = GetComponent(typeof(ISingleFingerHandler)) as ISingleFingerHandler;
// of course, you may prefer this to search the whole scene,
// just this gameobject shown here for simplicity
// alternately it's a very good approach to have consumers register
// for it. to do so just add a register function to the interface.
}
public void OnPointerDown(PointerEventData data)
{
kountFingersDown = kountFingersDown + 1;
if (currentSingleFinger == -1 && kountFingersDown == 1)
{
currentSingleFinger = data.pointerId;
if (needsUs != null) needsUs.OnSingleFingerDown(data.position);
}
}
public void OnPointerUp (PointerEventData data)
{
kountFingersDown = kountFingersDown - 1;
if ( currentSingleFinger == data.pointerId )
{
currentSingleFinger = -1;
if (needsUs != null) needsUs.OnSingleFingerUp(data.position);
}
}
public void OnDrag (PointerEventData data)
{
if ( currentSingleFinger == data.pointerId && kountFingersDown == 1 )
{
if (needsUs != null) needsUs.OnSingleFingerDrag(data.delta);
}
}
}
Put that daemon onto the game object, with your consumer FingerMove
, and forget about it. It is now
to handle dragging:
public class FingerMove:MonoBehaviour, ISingleFingerHandler
{
public void OnSingleFingerDown(Vector2 position) {}
public void OnSingleFingerUp (Vector2 position) {}
public void OnSingleFingerDrag (Vector2 delta)
{
_processSwipe(delta);
}
private void _processSwipe(Vector2 screenTravel)
{
.. move the camera or whatever ..
}
}
like I said,
Now let's think about the two finger case, a pinch to zoom/unzoom.
/*
IPinchHandler - strict two sequential finger pinch Handling
Put this daemon ON TO the game object, with a consumer of the service.
(Note, as always, the "philosophy" of a glass gesture is up to you.
There are many, many subtle questions; eg should extra fingers block,
can you 'swap primary' etc etc etc - program it as you wish.)
*/
public interface IPinchHandler
{
void OnPinchStart ();
void OnPinchEnd ();
void OnPinchZoom (float gapDelta);
}
/* note, Unity chooses to have "one interface for each action"
however here we are dealing with a consistent paradigm ("pinching")
which has three parts; I feel it's better to have one interface
forcing the consumer to have the three calls (no problem if empty) */
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
public class PinchInputModule:MonoBehaviour,
IPointerDownHandler,IPointerUpHandler,IDragHandler
{
private IPinchHandler needsUs = null;
// of course that would be a List,
// just one shown for simplicity in this example code
private int currentFirstFinger = -1;
private int currentSecondFinger = -1;
private int kountFingersDown = 0;
private bool pinching = false;
private Vector2 positionFirst = Vector2.zero;
private Vector2 positionSecond = Vector2.zero;
private float previousDistance = 0f;
private float delta = 0f;
void Awake()
{
needsUs = GetComponent(typeof(IPinchHandler)) as IPinchHandler;
// of course, this could search the whole scene,
// just this gameobject shown here for simplicity
}
public void OnPointerDown(PointerEventData data)
{
kountFingersDown = kountFingersDown + 1;
if (currentFirstFinger == -1 && kountFingersDown == 1)
{
// first finger must be a pure first finger and that's that
currentFirstFinger = data.pointerId;
positionFirst = data.position;
return;
}
if (currentFirstFinger != -1 && currentSecondFinger == -1 && kountFingersDown == 2)
{
// second finger must be a pure second finger and that's that
currentSecondFinger = data.pointerId;
positionSecond = data.position;
FigureDelta();
pinching = true;
if (needsUs != null) needsUs.OnPinchStart();
return;
}
}
public void OnPointerUp (PointerEventData data)
{
kountFingersDown = kountFingersDown - 1;
if ( currentFirstFinger == data.pointerId )
{
currentFirstFinger = -1;
if (pinching)
{
pinching = false;
if (needsUs != null) needsUs.OnPinchEnd();
}
}
if ( currentSecondFinger == data.pointerId )
{
currentSecondFinger = -1;
if (pinching)
{
pinching = false;
if (needsUs != null) needsUs.OnPinchEnd();
}
}
}
public void OnDrag (PointerEventData data)
{
if ( currentFirstFinger == data.pointerId )
{
positionFirst = data.position;
FigureDelta();
}
if ( currentSecondFinger == data.pointerId )
{
positionSecond = data.position;
FigureDelta();
}
if (pinching)
{
if ( data.pointerId == currentFirstFinger || data.pointerId == currentSecondFinger )
{
if (kountFingersDown==2)
{
if (needsUs != null) needsUs.OnPinchZoom(delta);
}
return;
}
}
}
private void FigureDelta()
{
float newDistance = Vector2.Distance(positionFirst, positionSecond);
delta = newDistance - previousDistance;
previousDistance = newDistance;
}
}
Put that daemon ON TO the game object, where you have a consumer of the service. Note that there is absolutely no problem with "mixing and matching". In this example, let's have BOTH a drag and pinch gesture. It is now
to handle pinch:
public class FingerMove:MonoBehaviour, ISingleFingerHandler, IPinchHandler
{
public void OnSingleFingerDown(Vector2 position) {}
public void OnSingleFingerUp (Vector2 position) {}
public void OnSingleFingerDrag (Vector2 delta)
{
_processSwipe(delta);
}
public void OnPinchStart () {}
public void OnPinchEnd () {}
public void OnPinchZoom (float delta)
{
_processPinch(delta);
}
private void _processSwipe(Vector2 screenTravel)
{
.. handle drag (perhaps move LR/UD)
}
private void _processPinch(float delta)
{
.. handle zooming (perhaps move camera in-and-out)
}
}
Like I say,
To see how elegant this is consider issues such as this: when pinching, do you want that to "pause" dragging, or let both happen? The amazing thing is, you simply program that inside SingleFingerInputModule.cs. In the specific example, I wanted it to "hold" dragging, while/if the user is zooming, so SingleFingerInputModule.cs above is programmed that way. You can easily modify it to have keep-going dragging, change to the centroid, cancel dragging, or whatever you want. The amazing thing is that FingerMove.cs is not affected at all! Incredibly useful abstraction!
Note that for Gökhan's excellent four-corner example above, I would write it like this:
public class FingerStretch:MonoBehaviour, IFourCornerHandler
{
public void OnFourCornerChange (Vector2 a, b, c, d)
{
... amazingly elegant solution
... Gökhan does all the work in FourCornerInputModule.cs
... here I just subscribe to it. amazingly simple
}
Which is just a mind-bogglingly simple approach.
Gökhan would encapsulate all the logic for the fingers inside FourCornerInputModule.cs which would have an interface IFourCornerHandler. Note that FourCornerInputModule would sensibly make all the philosophical decisions (example, must you have all four fingers down, what if you have one extra, etc etc).
Here are the issues arising:
Look at your Unity project at the so-called "stand alone input module" which is a game object with a EventSystem
and a StandAloneInputModule
You can in fact "write from scratch" something like SingleFingerInputModule.cs or PinchInputModule.cs, so that it will "work like" Unity's StandAloneInputModule.
While difficult this can be done, note the links in the comments in this answer.
But there is a specific, knock-down problem: ridiculously, you can't use OO principles: in my code above for SingleFingerInputModule.cs
, we very sensibly - of course - use the existing amazing IPointerDownHandler etc. power which Unity has done already and we (essentially) "subclass that", adding a little more logic. Which is exactly what you should, really must, do. In contrast: if you do decide to "make something that works like StandAloneInputModule", it's a fiasco - you have to start again, likely copying and pasting Unity's source code (for IPointerDownHandler etc) and sort of modifying it a bit your way, which is of course an exact example of how you should never do software engineering.
Note that if you go the "make something that works like StandAloneInputModule" route, in fact, you still have to do that!!!!! Which is somewhat bizarre; there is zero advantage.
If you go the "make something that works like StandAloneInputModule" route, Unity have an "Execute...." call which ... does just that. It's little more than a macro for just "calling the subscribers" (which we all do every day in every script other than the most trivial); no advantage.
In fact: I personally believe it's actually far better to have a subscribe call, as Everts suggests here, just have it as one of the interface calls. I just think that's far better engineering than trying to "be like" Unity's whacky magic-call system (which really doesn't work at all anyway - you have to "remember to attach" a StandAloneInputModule anyway).
I have come to believe that
It's unarguably a bad idea to start re-writing code to "make something that works like StandAloneInputModule"
If you try to "make something that works like StandAloneInputModule" ... you have to "remember to add it" anyway, for goodness sake.
If you try to "make something that works like StandAloneInputModule" there's the almost non-existent advantage of the "Execute..." call which Unity gives you, which is one line of code versus your one (shorter, clearer, faster) line of code to "call the subscriber". Again it's just far more obvious and clear to simply have a subscribe call, you could say any non-Unity programmer would simply do that.
So for me, the best approach in Unity today is just write modules/interfaces such as SingleFingerInputModule.cs, PinchInputModule.cs, FourCornerInputModule.cs, drop it on the game object where you want to have a consumer of those - and you're done. "It's that simple."
public class Zoom:MonoBehaviour, ISingleFingerHandler, IPinchHandler
{
public void OnPinchZoom (float delta)
{
...
You have no predefined way to do this in Unity. All you can do is, again, going with a custom solution, in an object oriented approach. Best thing to do would be splitting event detecting and event handling.
The main question to ask is, how to represent the fingers, touch, gesture etc. in an OOP manner. I choose to do it like so:
f1, f2, f3
, created finger combinatons are: f3, f1f3, f2f3, f1f2f3
. This gives the ultimate flexibility when working with multiple fingers. You can do gestures like this. For example, if you want to do the anchor gesture, you only need gestures of f2f3
but f1
must also exists. You can simply ignore f1
in that case.Also what you generally need from a multi-touch event:
Long code ahead:
using UnityEngine;
using UnityEngine.EventSystems;
using System.Linq;
using System.Collections.Generic;
public class MultitouchHandler : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler {
public List<Finger> Fingers = new List<Finger>();
public List<FingerCombination> FingerCombinations = new List<FingerCombination>();
public FingerCombination GetFingerCombination(params int[] fingerIndices) {
var fc = FingerCombinations.Find(x => x.IDs.Count == fingerIndices.Length && fingerIndices.All(y => x.IDs.Contains(Fingers[y].ID)));
if (fc != null) return fc;
fc = new FingerCombination() {
Fingers = fingerIndices.Select(x => Fingers[x]).ToList()
};
fc.IDs = fc.Fingers.Select(x => x.ID).ToList();
fc.Data = Fingers.Select(x => x.Data).ToList();
fc.PreviousData = Fingers.Select(x => x.Data).ToList();
FingerCombinations.Add(fc);
return fc;
}
public delegate void MultitouchEventHandler(int touchCount, MultitouchHandler sender);
public event MultitouchEventHandler OnFingerAdded;
public event MultitouchEventHandler OnFingerRemoved;
public void OnDrag(PointerEventData eventData) {
var finger = Fingers.Find(x => x.ID == eventData.pointerId);
var fcs = FingerCombinations.Where(x => x.IDs.Contains(eventData.pointerId));
finger.PreviousData = finger.Data;
finger.Data = eventData;
foreach (var fc in fcs) {
fc.PreviousData = fc.Data;
fc.Data = fc.Fingers.Select(x => x.Data).ToList();
fc.PreviousGesture = fc.Gesture;
fc.Gesture = new Gesture() {
Center = fc.Center,
Size = fc.Size,
Angle = fc.Angle,
SizeDelta = 1
};
if (fc.PreviousGesture != null) {
fc.Gesture.CenterDelta = fc.Center - fc.PreviousGesture.Center;
fc.Gesture.SizeDelta = fc.Size / fc.PreviousGesture.Size;
fc.Gesture.AngleDelta = fc.Angle - fc.PreviousGesture.Angle;
}
fc.Changed();
}
}
public void OnPointerDown(PointerEventData eventData) {
var finger = new Finger() { ID = eventData.pointerId, Data = eventData };
Fingers.Add(finger);
if (OnFingerAdded != null)
OnFingerAdded(Fingers.Count, this);
}
public void OnPointerUp(PointerEventData eventData) {
Fingers.RemoveAll(x => x.ID == eventData.pointerId);
if (OnFingerRemoved != null)
OnFingerRemoved(Fingers.Count, this);
var fcs = FingerCombinations.Where(x => x.IDs.Contains(eventData.pointerId));
foreach (var fc in fcs) {
fc.Finished();
}
FingerCombinations.RemoveAll(x => x.IDs.Contains(eventData.pointerId));
}
public class Finger {
public int ID;
public PointerEventData Data;
public PointerEventData PreviousData;
}
public class FingerCombination {
public List<int> IDs = new List<int>();
public List<Finger> Fingers;
public List<PointerEventData> PreviousData;
public List<PointerEventData> Data;
public delegate void GestureEventHandler(Gesture gesture, FingerCombination sender);
public event GestureEventHandler OnChange;
public delegate void GestureEndHandler(FingerCombination sender);
public event GestureEndHandler OnFinish;
public Gesture Gesture;
public Gesture PreviousGesture;
public Vector2 Center
{
get { return Data.Aggregate(Vector2.zero, (x, y) => x + y.position) / Data.Count; }
}
public float Size
{
get
{
if (Data.Count == 1) return 0;
var magnitudeSum = 0f;
for (int i = 1; i < Data.Count; i++) {
var dif = (Data[i].position - Data[0].position);
magnitudeSum += dif.magnitude;
}
return magnitudeSum / (Data.Count - 1);
}
}
public float Angle
{
get
{
if (Data.Count == 1) return 0;
var angleSum = 0f;
for (int i = 1; i < Data.Count; i++) {
var dif = (Data[i].position - Data[0].position);
angleSum += Mathf.Atan2(dif.y, dif.x) * Mathf.Rad2Deg;
}
return angleSum / (Data.Count - 1);
}
}
internal void Changed() {
if (OnChange != null)
OnChange.Invoke(Gesture, this);
}
internal void Finished() {
if (OnFinish != null)
OnFinish.Invoke(this);
}
}
public class Gesture {
public Vector2 Center;
public float Size;
public float Angle;
public Vector2 CenterDelta;
public float SizeDelta;
public float AngleDelta;
}
}
Here is an example showing how it is used with 4 fingers.
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
public class MultiTouchTest : MonoBehaviour {
public Vector2 rectSize = Vector2.one * 2;
public Vector2 skewedRectSize = Vector2.one;
public Vector2 rectPos = Vector2.zero;
public List<Vector3> Fingers = new List<Vector3>();
void Start() {
var h = GetComponent<MultitouchHandler>();
h.OnFingerAdded += OnGestureStart;
}
private void OnGestureStart(int touchCount, MultitouchHandler sender) {
if (touchCount != 4) return;
var fc = sender.GetFingerCombination(0, 1, 2, 3);
fc.OnChange += OnGesture;
}
private void OnGesture(MultitouchHandler.Gesture gesture, MultitouchHandler.FingerCombination sender) {
rectSize *= gesture.SizeDelta;
Fingers = sender.Fingers.Select(x => Camera.main.ScreenToWorldPoint(x.Data.position)).ToList();
var tan = Mathf.Tan(gesture.Angle * Mathf.Deg2Rad);
skewedRectSize = new Vector2(rectSize.x / tan, rectSize.y * tan);
rectPos += gesture.CenterDelta / 50;
}
public void OnDrawGizmos() {
Gizmos.color = Color.red;
Gizmos.DrawCube(rectPos, skewedRectSize);
Gizmos.color = Color.blue;
foreach (var finger in Fingers) Gizmos.DrawSphere(finger + Vector3.forward, 0.5f);
}
}
And the result looks like:
This is only a simple example though. A good answer would be too long for the format of SO.
Implementing this is not really complicated.
Use a List
and store the pointerId
each time there is a OnPointerDown
event then increment touchCount
variable. Don't store pointerId
on OnPointerDown if it already exist in the List
.
When OnPointerUp
is called or when there is a release, check if the pointerId
exist. If it does, decrement the touchCount
variable. If it does not exist in the List
then don't decrement anything.
1.Very Simple Implementation:
public class FingerMove : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
public int touchCount;
public List<int> touchID = new List<int>(6); //6 touches limit
public void OnPointerDown(PointerEventData data)
{
Debug.Log("Pressed");
//Check If PointerId exist, if it doesn't add to list
if (touchID.Contains(data.pointerId))
{
return; //Exit if PointerId exist
}
//PointerId does not exist, add it to the list then increment touchCount
touchID.Add(data.pointerId);
touchCount++;
}
public void OnPointerUp(PointerEventData data)
{
Debug.Log("Released");
//Check If PointerId exist, if it exist remove it from list then decrement touchCount
if (touchID.Contains(data.pointerId))
{
touchID.Remove(data.pointerId);
touchCount--;
return;
}
}
void Update()
{
Debug.Log("Touch Count: " + touchCount);
}
}
2.The first example is very simple but it can be improved with our own interface.
This method uses two scripts:
IPointerCounterHandler.cs
interface:
public interface IPointerCounterHandler : IEventSystemHandler
{
void OnPointerCounterChanged(int touchCount);
void OnPointerCounterChanged(PointerCounterEventData touchCountData);
}
PointerCounterEventData.cs
script.
public class PointerCounterEventData : BaseEventData
{
//The callback with int parameter
public static readonly ExecuteEvents.EventFunction<IPointerCounterHandler> counterChangedV1Delegate
= delegate (IPointerCounterHandler handler, BaseEventData data)
{
//var casted = ExecuteEvents.ValidateEventData<PointerCounterEventData>(data);
handler.OnPointerCounterChanged(touchCount);
};
//The callback with PointerCounterEventData parameter
public static readonly ExecuteEvents.EventFunction<IPointerCounterHandler> counterChangedV2Delegate
= delegate (IPointerCounterHandler handler, BaseEventData data)
{
var casted = ExecuteEvents.ValidateEventData<PointerCounterEventData>(data);
handler.OnPointerCounterChanged(casted);
};
public static int touchCount = 0;
public PointerCounterInfo touchCountData = new PointerCounterInfo();
public static List<int> touchID = new List<int>(6); //6 touches limit
//Constructor with the int parameter
public PointerCounterEventData(
EventSystem eventSystem,
int tempTouchId,
PointerState pointerStat
)
: base(eventSystem)
{
//Process the Input event
processTouches(pointerStat, tempTouchId, null, CallBackType.TouchCountOnly);
}
//Constructor with the PointerEventData parameter
public PointerCounterEventData(
EventSystem eventSystem,
PointerEventData eventData,
PointerState pointerStat,
GameObject target
)
: base(eventSystem)
{
//Process the Input event
processTouches(pointerStat, eventData.pointerId, eventData, CallBackType.CounterData);
//Create new PointerCounterInfo for the OnPointerCounterChanged(PointerCounterEventData eventData) function
PointerCounterInfo pcInfo = createPointerInfo(eventData,
target, pointerStat);
//Update touchCountData
touchCountData = pcInfo;
}
void processTouches(PointerState pointerStat, int tempTouchId, PointerEventData touchCountData, CallBackType cbType)
{
if (pointerStat == PointerState.DOWN)
{
//Check If PointerId exist, if it doesn't add to list
if (touchID.Contains(tempTouchId))
{
//eventData.eventData
return; //Exit if PointerId exist
}
//PointerId does not exist, add it to the list then increment touchCount
touchID.Add(tempTouchId);
touchCount++;
}
if (pointerStat == PointerState.UP)
{
//Check If PointerId exist, if it exist remove it from list then decrement touchCount
if (touchID.Contains(tempTouchId))
{
touchID.Remove(tempTouchId);
touchCount--;
return;
}
}
}
public static void notifyPointerDown(EventSystem eventSystem, PointerEventData eventData,
GameObject target)
{
PointerState pointerStat = PointerState.DOWN;
notifyfuncs(eventSystem, eventData, target, pointerStat);
}
public static void notifyPointerUp(EventSystem eventSystem, PointerEventData eventData,
GameObject target)
{
PointerState pointerStat = PointerState.UP;
notifyfuncs(eventSystem, eventData, target, pointerStat);
}
private static void notifyfuncs(EventSystem eventSystem, PointerEventData eventData,
GameObject target, PointerState pointerStat)
{
//////////////////////Call the int parameter//////////////////////
PointerCounterEventData eventParam1 = new PointerCounterEventData(
eventSystem,
eventData.pointerId,
pointerStat);
ExecuteEvents.Execute<IPointerCounterHandler>(
target,
eventParam1,
PointerCounterEventData.counterChangedV1Delegate);
//////////////////////Call the PointerCounterEventData parameter//////////////////////
PointerCounterEventData eventParam2 = new PointerCounterEventData(
eventSystem,
eventData,
pointerStat,
target);
ExecuteEvents.Execute<IPointerCounterHandler>(
target,
eventParam2,
PointerCounterEventData.counterChangedV2Delegate);
}
//Creates PointerCounterInfo for the OnPointerCounterChanged(PointerCounterEventData eventData) function
private static PointerCounterInfo createPointerInfo(PointerEventData eventData,
GameObject target, PointerState pointerStat)
{
PointerCounterInfo pointerCounterInfo = new PointerCounterInfo();
pointerCounterInfo.pointerId = eventData.pointerId;
pointerCounterInfo.touchCount = touchCount;
pointerCounterInfo.eventData = eventData;
pointerCounterInfo.pointerState = pointerStat;
pointerCounterInfo.target = target;
return pointerCounterInfo;
}
public enum CallBackType
{
TouchCountOnly, CounterData
}
}
public enum PointerState { NONE, DOWN, UP }
public class PointerCounterInfo
{
public int pointerId = 0;
public int touchCount = 0;
public PointerEventData eventData;
public PointerState pointerState;
public GameObject target;
}
Usage:
Implement IPointerCounterHandler
in your script then override the
void OnPointerCounterChanged(int touchCount);
and
void OnPointerCounterChanged(PointerCounterEventData touchCountData);
functions.
Finally, call PointerCounterEventData.notifyPointerDown
in the OnPointerDown
function and also call PointerCounterEventData.notifyPointerUp
in the OnPointerUp
function.
Test:
public class Test : MonoBehaviour, IPointerCounterHandler, IPointerDownHandler, IPointerUpHandler
{
public void OnPointerCounterChanged(int touchCount)
{
Debug.Log("Simple Finger Counter: " + touchCount);
}
public void OnPointerCounterChanged(PointerCounterEventData touchCountData)
{
PointerCounterInfo moreEventData = touchCountData.touchCountData;
Debug.Log("Finger TouchCount: " + moreEventData.touchCount);
Debug.Log("Finger PointerId: " + moreEventData.pointerId);
Debug.Log("Finger Pointer State: " + moreEventData.pointerState);
Debug.Log("Finger Target: " + moreEventData.target.name);
//Can also access PointerEventData
PointerEventData eventData = touchCountData.touchCountData.eventData;
Debug.Log("Click Time!: " + eventData.clickTime);
}
public void OnPointerDown(PointerEventData eventData)
{
PointerCounterEventData.notifyPointerDown(EventSystem.current, eventData, this.gameObject);
}
public void OnPointerUp(PointerEventData eventData)
{
PointerCounterEventData.notifyPointerUp(EventSystem.current, eventData, this.gameObject);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With