EventSystem

Hello All,

I am playing around to see if the new event system can be used to handle multi touch inputs.

The eventsystem class has a function called

RaycastAll (PointerEventData,List).

Is PointerEventData the only type that this function can take ?

How would one pass for e.g. PinchEventData ( derived from BaseEventData ) to this function. This data class will hold lists of touches, world positions etc.

Or is there any other way of going about it.

Thank you.
Nihal.

Maybe

loop through the touches; create a PointerEventData for each of them; call eventSystem.raycastall each one; and then perform other computations ?

But then, doesn’t that make Base Event Data redundant.

So i have this custom event data and interface

public class PinchEventData : PointerEventData
{
    //test data
    public int touchId;
    public Vector2 touchPosition;

    public PinchEventData (EventSystem ES): base (ES)
    {
    }
}

The handler Interface

public interface IPinchHandler : IEventSystemHandler
{
    void OnPinch (PinchEventData data);
}

And I need to execute this

ExecuteEvents.Execute (firstHit.go, GetMyPinchData(), ExecuteEvents.pinchHandler);

in the Process function of the CustomInputModule derived from PointerInputModule.

How / where do I register the pinch interface / helper function with the event system.

Let me know if more information / code is needed.

Thank you,
Nihal.

I just came across this

Tim C’s simple example towards the end.

This is a touch module that I have managed to hack together. It is restricted to two touches as of now. But as you can see it can easily extend to handle more inputs.

Let me know if you have come across a better way of doing this.

Here is the code.

using UnityEngine;
using UnityEngine.EventSystems;

using System.Collections.Generic;

namespace UnityEngine.EventSystems
{

    public class PinchEventData : BaseEventData
    {
        public List<PointerEventData> data;
   
        public PinchEventData (EventSystem ES): base (ES)
        {
            data = new List<PointerEventData> ();
        }
    }

    public interface IPinchHandler : IEventSystemHandler
    {
        void OnPinch (PinchEventData data);
    }


    public static class PinchModuleEvents
    {
        private static void Execute (IPinchHandler handler, BaseEventData eventData)
        {
            handler.OnPinch (ExecuteEvents.ValidateEventData<PinchEventData> (eventData));
        }
   
        public static ExecuteEvents.EventFunction<IPinchHandler> pinchHandler {
            get { return Execute; }
        }
    }


    public class PinchInputModule : PointerInputModule
    {
        private PinchEventData _pinchData = null;
   
        public override void Process ()
        {
            if (Input.touchCount == 2) {
                bool pressed, released;
           
                PointerEventData touchData0 = GetTouchPointerEventData (Input.GetTouch (0), out pressed, out released);
                PointerEventData touchData1 = GetTouchPointerEventData (Input.GetTouch (1), out pressed, out released);
           
                _pinchData = new PinchEventData (eventSystem);
                _pinchData.data.Add (touchData0);
                _pinchData.data.Add (touchData1);
               
                eventSystem.RaycastAll (touchData0, m_RaycastResultCache);
                RaycastResult firstHit = FindFirstRaycast (m_RaycastResultCache);
           
                eventSystem.RaycastAll (touchData1, m_RaycastResultCache);
           
                if (FindFirstRaycast (m_RaycastResultCache).go.Equals (firstHit.go)) {
                    ExecuteEvents.Execute (firstHit.go, _pinchData, PinchModuleEvents.pinchHandler);
                }
           
                _pinchData.data.Clear ();
            }
        }
       
        public override string ToString ()
        {
            return string.Format ("[PinchInputModule]");
        }
    }
}

On the handling side, you derive from the IPinchHandler and define the void OnPinch(PinchEventData data) function

Thank you,
Nihal

4 Likes

Thanks Nihal for this example. It has got me started in the right direction!

Cheers.

Nice. But as you said there could be a “bug” if you are pressing a button or otherwise interacting and having multiple touchinputs before and during a pinch. First and second touch ID won’t always be the fingers which are pinching.
Maybe having a second layer with a lookuptable with every current fingerid and when they were pressed/released can help. Then it is possible to see which two fingerids are meant to react to a pinch.

1 Like

Hey thylaxene.

Glad I could be of some use.

Here are more examples stramit’s gists · GitHub

Thank you for the input Kogar.

does this work for testing through the Unity Remote 4? I’m getting null errors on un-initialised object around line 60. Or am I missing how this is meant to be used? Eg add input module script to EventSystem gameobject and then create a script that is derived and handles the OnPinch which is then add to an UI element.

Hey.

Unity remote just stopped working for me some time back. It does help in catching those errors.

I am assuming, if the eventSytem.RaycastAll function does not get a hit, the RaycastResult.go variable might be null. Both the firstHit.go would be required to be checked against null, before comparison

Also would it make sense to populate the PinchEventData.data List just before the ExecuteEvents.execute is called

The e.g. you mentioned is how this has to be used.

Let me know,

Nihal

Hello.

Here is some updated code, that checks for the phase of the touch before executing the event.

I am able to zoom in / out and drag an ui image around with this.

Please improve it and share it back, if you have the time.

using UnityEngine;
using UnityEngine.EventSystems;

using System.Collections.Generic;

namespace UnityEngine.EventSystems
{

    public class PinchEventData : BaseEventData
    {
        public List<PointerEventData> data;
       
        public PinchEventData (EventSystem ES): base (ES)
        {
            data = new List<PointerEventData> ();
        }
    }


    public interface IPinchHandler : IEventSystemHandler
    {
        void OnPinch (PinchEventData data);
    }
   
   
    public static class PinchModuleEvents
    {
        private static void Execute (IPinchHandler handler, BaseEventData eventData)
        {
            handler.OnPinch (ExecuteEvents.ValidateEventData<PinchEventData> (eventData));
        }
       
        public static ExecuteEvents.EventFunction<IPinchHandler> pinchHandler {
            get { return Execute; }
        }
    }
   
   
    public class PinchInputModule : PointerInputModule
    {   
        private PinchEventData _pinchData = null;
       
        public override void Process ()
        {
            if (Input.touchCount == 1) {
                bool pressed, released;
                PointerEventData touchData = GetTouchPointerEventData (Input.GetTouch (0), out pressed, out released);
               
                eventSystem.RaycastAll (touchData, m_RaycastResultCache);
                RaycastResult firstHit = FindFirstRaycast (m_RaycastResultCache);

                if (Input.GetTouch (0).phase == TouchPhase.Began) {
                    ExecuteEvents.Execute (firstHit.go, touchData, ExecuteEvents.beginDragHandler);
                }
               
                if (Input.GetTouch (0).phase == TouchPhase.Moved) {
                    ExecuteEvents.Execute (firstHit.go, touchData, ExecuteEvents.dragHandler);
                }
               
                if (Input.GetTouch (0).phase == TouchPhase.Ended) {
                    ExecuteEvents.Execute (firstHit.go, touchData, ExecuteEvents.endDragHandler);
                }
               
            } else if (Input.touchCount == 2) {
                bool pressed0, released0;
                bool pressed1, released1;
               
                PointerEventData touchData0 = GetTouchPointerEventData (Input.GetTouch (0), out pressed0, out released0);
                PointerEventData touchData1 = GetTouchPointerEventData (Input.GetTouch (1), out pressed0, out released1);
                eventSystem.RaycastAll (touchData0, m_RaycastResultCache);
                RaycastResult firstHit = FindFirstRaycast (m_RaycastResultCache);
               
                eventSystem.RaycastAll (touchData1, m_RaycastResultCache);
               
                if (Input.GetTouch (0).phase == TouchPhase.Moved || Input.GetTouch (1).phase == TouchPhase.Moved) {
                    if (firstHit.go != null && FindFirstRaycast (m_RaycastResultCache).go != null) {
                        if (FindFirstRaycast (m_RaycastResultCache).go.Equals (firstHit.go)) {
               
                            _pinchData = new PinchEventData (eventSystem);
                                   
                            _pinchData.data.Add (touchData0);
                            _pinchData.data.Add (touchData1);
                       
                            ExecuteEvents.Execute (firstHit.go, _pinchData, PinchModuleEvents.pinchHandler);
                        }
                    }
                }
               
                _pinchData.data.Clear ();
            }
        }
           
        public override string ToString ()
        {
            return string.Format ("[PinchInputModule]");
        }
    }
}
1 Like

Since i am not familiar with custom Interfaces and simultaneously using the new touchinputs i made an alternative to detect when a pinch should get used. Here it is irrelevant in which order the touchinputs gets triggered.
I have added a second canvas to my scene and added an image component and my script. I set the alpha value of the image component to transparent and increased the priority in the GraphicRaycaster to only detect raycast behind my first canvas UI.
The script is still incomplete but it can successfully detect which fingerIDs are meant to be used as single click or multiclicks combo. Now i have to add the actual pan and zoom code to move a camera.
I commented my debugcode but left it inside. Then you can directly see in which position in buttonOrCombo() the fingerIDs can get assigned for further usage.
Maybe the code will help to improve your generalized Interface and i won’t need to add an additional canvas to my scene :slight_smile:

using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
using System.Collections.Generic;
public class MultitouchPanZoom : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler {
   [Tooltip("Max time difference in milliseconds between button presses to be a combo")]
   public float maxTimeDifference = 100; //I still have to see how high i can set the input lag without degrading the user experience but sucessfully register bomboclicks
   private float timeDifferenceInMilliseconds;
   private Dictionary<int, bool> pressedKeys = new Dictionary<int, bool>(); //maybe needs a buffer of pressedKeys to be able to handle really fast button presses in a row
   private int panFingerID = -1; //is a fingerID using pan functionality
   private int[] pinchFingerIDs = new int[2]{-1,-1}; //are fingerIDs used for pinch functionality
   //Set Graphic Raycaster to a deeper level. Increase Priority to push canvas behind other canvas objects
   IEnumerator releaseButton(int fingerID)
   {
   yield return new WaitForSeconds(timeDifferenceInMilliseconds);
   if(pressedKeys.ContainsKey(fingerID) )
     pressedKeys[fingerID] = false;
   if(panFingerID==fingerID) //release panID
     panFingerID=-1;
   if(pinchFingerIDs[0]==fingerID || pinchFingerIDs[1]==fingerID) //release pinchIDs
   {
     pinchFingerIDs[0]=-1;
     pinchFingerIDs[1]=-1;
   }
   yield return true;
   }
   public void OnPointerDown(PointerEventData eventData)
   {
   timeDifferenceInMilliseconds = maxTimeDifference/1000f; //if it is not changing in game put it in awake
   if(!pressedKeys.ContainsKey(eventData.pointerId))
     pressedKeys.Add(eventData.pointerId, true);
   else
     pressedKeys[eventData.pointerId] = true;
   //GameState.Instance.debugPanel.addLine("Finger "+ eventData.pointerId + " down");
   Invoke("buttonOrCombo", timeDifferenceInMilliseconds);
   }
  
   public void OnPointerUp(PointerEventData eventData)
   {
  StartCoroutine("releaseButton", eventData.pointerId);
  //GameState.Instance.debugPanel.addLine("Finger "+ eventData.pointerId + " up" );
   }
  
   public void OnDrag(PointerEventData eventData)
   {
   }
   public void buttonOrCombo()
   {
   List<int> combo = new List<int>();
   string comboString="";
  
   #region check for pressed buttons //function invoked with timeDifference
   foreach(KeyValuePair<int, bool> pKeys in pressedKeys)
   {
     if(pressedKeys[pKeys.Key]) //add when pressed down
       combo.Add(pKeys.Key);//prepare to release
   }
   #endregion
   #region display for one or multiple simultanous key presses
   if(combo.Count>0)  //if there are still active buttons after minTimeDifference
   {
     #region just to display the keys
     for(int i=0; i<combo.Count; i++)
     {
     pressedKeys[combo[i]]=false;
     comboString += combo[i];
     if(i != combo.Count-1)
       comboString+= " + ";
     }
     #endregion
     if(combo.Count==1)
     {
       if(panFingerID==-1)
       {
       panFingerID = combo[0];
       //GameState.Instance.debugPanel.addLine("PanID: " + comboString);
       }
       //else
     //GameState.Instance.debugPanel.addLine("normal Keypress: " + comboString);
     }
     else if(combo.Count>1)
     {
       if(combo.Count==2)
       {
         if(pinchFingerIDs[0]==-1 || pinchFingerIDs[1]==-1)
         {
           pinchFingerIDs[0] = combo[0];
           pinchFingerIDs[1] = combo[1];
           //GameState.Instance.debugPanel.addLine("PinchIDs: " + comboString);
         }
         //else
         //GameState.Instance.debugPanel.addLine("Second PinchCombo: " + comboString);
       }
       //else
       //GameState.Instance.debugPanel.addLine("Keycombo: " + comboString);
     }
   }
   #endregion
}
}

Currently the code is not usable for really fast consecutively button presses. Maybe needs a buffer of pressedkeys which needs to be checked in the same manner as now instead of only the last pressedkey state.

Hello.

I have just updated the module to include pinch and rotation. It is renamed to MultiTouchInputModule

Two interfaces IRotateHandler and IPinchHandler declare

 public void OnRotate(RotateEventData data)

and

 public void OnPinch(PinchEventData data)

In addition to the usual PointerEventData these classes have a pinchDelta and rotateDelta which are to be used like scrollDelta.

Hope this helps.

using UnityEngine;
using UnityEngine.EventSystems;

using System.Collections.Generic;

namespace UnityEngine.EventSystems
{

    public class PinchEventData : BaseEventData
    {
        public List<PointerEventData> data;
        public float pinchDelta;
       
        public PinchEventData (EventSystem ES, float d = 0) : base (ES)
        {
            data = new List<PointerEventData> ();
            pinchDelta = d;
        }
    }
   
    public class RotateEventData : BaseEventData
    {
        public List<PointerEventData> data;
        public float rotateDelta;
       
        public RotateEventData (EventSystem ES, float d = 0) : base (ES)
        {
            data = new List<PointerEventData> ();
            rotateDelta = d;
        }
    }
   
    public interface IPinchHandler : IEventSystemHandler
    {
        void OnPinch (PinchEventData data);
    }
   
    public interface IRotateHandler : IEventSystemHandler
    {
        void OnRotate (RotateEventData data);
    }
   
   
    public static class MultiTouchModuleEvents
    {
        private static void Execute (IPinchHandler handler, BaseEventData eventData)
        {
            handler.OnPinch (ExecuteEvents.ValidateEventData<PinchEventData> (eventData));
        }
       
        private static void Execute (IRotateHandler handler, BaseEventData eventData)
        {
            handler.OnRotate (ExecuteEvents.ValidateEventData<RotateEventData> (eventData));
        }
       
        public static ExecuteEvents.EventFunction<IPinchHandler> pinchHandler {
            get { return Execute; }
        }
       
        public static ExecuteEvents.EventFunction<IRotateHandler> rotateHandler {
            get { return Execute; }
        }
    }
   
   
    public class MultiTouchInputModule : PointerInputModule
    {   
        private PinchEventData _pinchData;
        private RotateEventData _rotateData;
       
        private enum _MultiTouchMode
        {
            Idle, // no touch input.
            Began, // 2 input touches received.
            Pinching, // touches have passed the min pinch threshold
            Rotating // touches have passed the min rotating threshold
        }
        ;
       
        private _MultiTouchMode _touchMode = _MultiTouchMode.Idle;
       
        private bool _isRotating = false;
        private bool _isPinching = false;
       
        private Vector2 _prevVector = Vector2.zero;
       
        public float _minRotationThreshold = 10;
        public float _minPinchThreshold = 10;
       
        public override void Process ()
        {
            if (Input.touchCount == 1) {
                bool pressed, released;
                PointerEventData touchData = GetTouchPointerEventData (Input.GetTouch (0), out pressed, out released);
               
                eventSystem.RaycastAll (touchData, m_RaycastResultCache);
                RaycastResult firstHit = FindFirstRaycast (m_RaycastResultCache);

                if (Input.GetTouch (0).phase == TouchPhase.Began) {
                    ExecuteEvents.Execute (firstHit.go, touchData, ExecuteEvents.beginDragHandler);
                }
               
                if (Input.GetTouch (0).phase == TouchPhase.Moved) {
                    ExecuteEvents.Execute (firstHit.go, touchData, ExecuteEvents.dragHandler);
                }
               
                if (Input.GetTouch (0).phase == TouchPhase.Ended) {
                    ExecuteEvents.Execute (firstHit.go, touchData, ExecuteEvents.endDragHandler);
                }
               
            } else if (Input.touchCount == 2) {
                bool pressed0, released0;
                bool pressed1, released1;
               
                PointerEventData touchData0 = GetTouchPointerEventData (Input.GetTouch (0), out pressed0, out released0);
                PointerEventData touchData1 = GetTouchPointerEventData (Input.GetTouch (1), out pressed0, out released1);
               
                eventSystem.RaycastAll (touchData0, m_RaycastResultCache);
                RaycastResult firstHit0 = FindFirstRaycast (m_RaycastResultCache);
               
                eventSystem.RaycastAll (touchData1, m_RaycastResultCache);
                RaycastResult firstHit1 = FindFirstRaycast (m_RaycastResultCache);
               
                if (Input.GetTouch (0).phase == TouchPhase.Moved || Input.GetTouch (1).phase == TouchPhase.Moved) {
                    if (firstHit0.go != null && firstHit1.go != null) {
                        if (firstHit0.go.Equals (firstHit1.go)) {
                       
                            bool executeHandler = false;
                           
                            // 'var' works just as well.
                            BaseEventData motionData = DetectMultiTouchMotion (out executeHandler);
                           
                            if (executeHandler) {
                                if (_touchMode == _MultiTouchMode.Pinching) {
                                    _pinchData = motionData as PinchEventData;
                                    _pinchData.data.Add (touchData0);
                                    _pinchData.data.Add (touchData1);
                                   
                                    ExecuteEvents.Execute (firstHit0.go, _pinchData, MultiTouchModuleEvents.pinchHandler);
                                   
                                } else if (_touchMode == _MultiTouchMode.Rotating) {
                                    _rotateData = motionData as RotateEventData;
                                    _rotateData.data.Add (touchData0);
                                    _rotateData.data.Add (touchData1);
                                   
                                    ExecuteEvents.Execute (firstHit0.go, _rotateData, MultiTouchModuleEvents.rotateHandler);
                                }
                            }
                        }
                    }
                }
               
                if (Input.GetTouch (0).phase == TouchPhase.Ended || Input.GetTouch (1).phase == TouchPhase.Ended ||
                    Input.GetTouch (0).phase == TouchPhase.Canceled || Input.GetTouch (1).phase == TouchPhase.Canceled) {
                   
                    _touchMode = _MultiTouchMode.Idle;
                    _prevVector = Vector2.zero;
                }
            }
        }
       
        BaseEventData DetectMultiTouchMotion (out bool executeHandler)
        {
            executeHandler = false;
           
            if (_touchMode == _MultiTouchMode.Idle) {
                _prevVector = Input.GetTouch (1).position - Input.GetTouch (0).position;
                _touchMode = _MultiTouchMode.Began;
               
                return null;
               
            } else if (_touchMode == _MultiTouchMode.Began) {
                Vector2 currentVector = Input.GetTouch (1).position - Input.GetTouch (0).position;
               
                //check for rotation threshold
               
                float angleOffset = Vector2.Angle (_prevVector, currentVector);
               
                if (angleOffset > _minRotationThreshold) {
                    _touchMode = _MultiTouchMode.Rotating;
                    _prevVector = currentVector;
                }
               
                // check for pinch threshold
               
                if (Mathf.Abs (currentVector.magnitude - _prevVector.magnitude) > _minPinchThreshold) {
                    _touchMode = _MultiTouchMode.Pinching;
                    _prevVector = currentVector;
                }                   
               
                return null;
               
            } else if (_touchMode == _MultiTouchMode.Rotating) {
                Vector2 currentVector = Input.GetTouch (1).position - Input.GetTouch (0).position;

                float angleOffset = Vector2.Angle (_prevVector, currentVector);
               
                // to get the direction of rotation
                Vector3 dirVec = Vector3.Cross (_prevVector, currentVector);
               
                _prevVector = currentVector;
               
                executeHandler = true;
               
                return new RotateEventData (eventSystem, dirVec.z < 0 ? -angleOffset : angleOffset);
               
            } else if (_touchMode == _MultiTouchMode.Pinching) {
                Vector2 currentVector = Input.GetTouch (1).position - Input.GetTouch (0).position;
               
                float pinchDelta = currentVector.magnitude - _prevVector.magnitude;
               
                _prevVector = currentVector;
               
                executeHandler = true;
               
                return new PinchEventData (eventSystem, pinchDelta);
            }
           
            return null;
        }

        public override string ToString ()
        {
            return string.Format ("[MultiTouchInputModule]");
        }
    }
}

Thank you,
Nihal

5 Likes

Hello again.

  • Updated to avoid creating “new” instances of rotatedata and pinchdata at every tick of the motion. So there is one instance of RotateEventData and PinchEventData instantiated inside Start of the InputModule, which are passed to the event handlers, with the “delta” updated in the DetectMultiTouchMotion.

  • Instead of a list to hold the PointerEventData in the rotate and pinch data classes, arrays with size 2 have been used for ease of usage. Lists seemed to be overkill when you are sure there only 2 items.

  • It is possible to change the Pinch to Rotate and vice versa without lifting your fingers by setting the “stationaryTimeThreshold” value.

Let me know if you have something funky to add or substract, or if this just doesnt work.

Thanks.
Nihal

using UnityEngine;
using UnityEngine.EventSystems;

namespace UnityEngine.EventSystems
{
    public class MultiTouchInputModule : PointerInputModule
    {   
        private PinchEventData _pinchData;
        private RotateEventData _rotateData;
       
        private enum _MultiTouchMode
        {
            Idle, // no touch input.
            Began, // 2 input touches received.
            Pinching, // touches have passed the min pinch threshold
            Rotating // touches have passed the min rotating threshold
        }
        ;
       
        private _MultiTouchMode _touchMode = _MultiTouchMode.Idle;
       
        private Vector2 _prevVector = Vector2.zero;
       
        private float _stationaryTime;
       
        public float minRotationThreshold = 10;
        public float minPinchThreshold = 10;
        public float stationaryTimeThreshold = 1;
       
        protected override void Start ()
        {
            _pinchData = new PinchEventData (eventSystem);
            _rotateData = new RotateEventData (eventSystem);
        }
       
        public override void Process ()
        {
            if (Input.touchCount == 1) {
                bool pressed, released;
                PointerEventData touchData = GetTouchPointerEventData (Input.GetTouch (0), out pressed, out released);
               
                eventSystem.RaycastAll (touchData, m_RaycastResultCache);
                RaycastResult firstHit = FindFirstRaycast (m_RaycastResultCache);
               
                if (Input.GetTouch (0).phase == TouchPhase.Began) {
                    ExecuteEvents.Execute (firstHit.go, touchData, ExecuteEvents.beginDragHandler);
                }
               
                if (Input.GetTouch (0).phase == TouchPhase.Moved) {
                    ExecuteEvents.Execute (firstHit.go, touchData, ExecuteEvents.dragHandler);
                }
               
                if (Input.GetTouch (0).phase == TouchPhase.Ended) {
                    ExecuteEvents.Execute (firstHit.go, touchData, ExecuteEvents.endDragHandler);
                }
               
            } else if (Input.touchCount == 2) {
                bool pressed0, released0;
                bool pressed1, released1;
               
                PointerEventData touchData0 = GetTouchPointerEventData (Input.GetTouch (0), out pressed0, out released0);
                PointerEventData touchData1 = GetTouchPointerEventData (Input.GetTouch (1), out pressed1, out released1);
               
                eventSystem.RaycastAll (touchData0, m_RaycastResultCache);
                RaycastResult firstHit0 = FindFirstRaycast (m_RaycastResultCache);
               
                eventSystem.RaycastAll (touchData1, m_RaycastResultCache);
                RaycastResult firstHit1 = FindFirstRaycast (m_RaycastResultCache);
               
                if (Input.GetTouch (0).phase == TouchPhase.Began || Input.GetTouch (1).phase == TouchPhase.Began) {
                    _prevVector = Input.GetTouch (1).position - Input.GetTouch (0).position;
                    _touchMode = _MultiTouchMode.Began;
                }
               
                if (Input.GetTouch (0).phase == TouchPhase.Moved || Input.GetTouch (1).phase == TouchPhase.Moved) {
                    if (firstHit0.go != null && firstHit1.go != null) {
                        if (firstHit0.go.Equals (firstHit1.go)) {
                            bool executeHandler = DetectMultiTouchMotion ();
                           
                            if (executeHandler) {
                                if (_touchMode == _MultiTouchMode.Pinching) {
                                    _pinchData.data [0] = touchData0;
                                    _pinchData.data [1] = touchData1;
                                   
                                    ExecuteEvents.Execute (firstHit0.go, _pinchData, MultiTouchModuleEvents.pinchHandler);
                                } else if (_touchMode == _MultiTouchMode.Rotating) {
                                    _rotateData.data [0] = touchData0;
                                    _rotateData.data [1] = touchData1;
                                   
                                    ExecuteEvents.Execute (firstHit0.go, _rotateData, MultiTouchModuleEvents.rotateHandler);
                                }
                            }
                        }
                    }
                }
               
                //check for ended or cancelled touched fingers and set the mode back to "idle".
               
                if (Input.GetTouch (0).phase == TouchPhase.Ended || Input.GetTouch (1).phase == TouchPhase.Ended ||
                    Input.GetTouch (0).phase == TouchPhase.Canceled || Input.GetTouch (1).phase == TouchPhase.Canceled) {
                   
                    _touchMode = _MultiTouchMode.Idle;
                    _prevVector = Vector2.zero;
                }
               
               
                //check for stationary fingers and set the mode back to "Began" if above the threshold.
               
                if (Input.GetTouch (0).phase == TouchPhase.Stationary || Input.GetTouch (1).phase == TouchPhase.Stationary) {
                    _stationaryTime += Time.deltaTime;
                   
                    if (_stationaryTime > stationaryTimeThreshold) {
                        _touchMode = _MultiTouchMode.Began;
                        _prevVector = Input.GetTouch (1).position - Input.GetTouch (0).position;
                        _stationaryTime = 0;
                    }
                }
            }
        }
       
        bool DetectMultiTouchMotion ()
        {
            if (_touchMode == _MultiTouchMode.Began) {
                Vector2 currentVector = Input.GetTouch (1).position - Input.GetTouch (0).position;
               
                //check for rotation threshold
               
                float angleOffset = Vector2.Angle (_prevVector, currentVector);
               
                if (angleOffset > minRotationThreshold) {
                    _touchMode = _MultiTouchMode.Rotating;
                    _prevVector = currentVector;
                }
               
                // check for pinch threshold
               
                if (Mathf.Abs (currentVector.magnitude - _prevVector.magnitude) > minPinchThreshold) {
                    _touchMode = _MultiTouchMode.Pinching;
                    _prevVector = currentVector;
                }                   
               
                return false;
               
            } else if (_touchMode == _MultiTouchMode.Rotating) {
                Vector2 currentVector = Input.GetTouch (1).position - Input.GetTouch (0).position;
               
                float rotateDelta = Vector2.Angle (_prevVector, currentVector);
               
                // to get the direction of rotation
                Vector3 dirVec = Vector3.Cross (_prevVector, currentVector);
               
                _prevVector = currentVector;
               
                _rotateData.rotateDelta = dirVec.z < 0 ? -rotateDelta : rotateDelta;
               
                return true;
               
            } else if (_touchMode == _MultiTouchMode.Pinching) {
                Vector2 currentVector = Input.GetTouch (1).position - Input.GetTouch (0).position;
               
                float pinchDelta = currentVector.magnitude - _prevVector.magnitude;
               
                _prevVector = currentVector;
               
                _pinchData.pinchDelta = pinchDelta;
               
                return true;
            }
           
            return false;
        }
       
        public override string ToString ()
        {
            return string.Format ("[MultiTouchInputModule]");
        }
    }
   
    public class PinchEventData : BaseEventData
    {
        public PointerEventData[] data = new PointerEventData[2];
        public float pinchDelta;
       
        public PinchEventData (EventSystem ES, float d = 0) : base (ES)
        {
            pinchDelta = d;
        }
    }
   
    public class RotateEventData : BaseEventData
    {
        public PointerEventData[] data = new PointerEventData[2];
        public float rotateDelta;
       
        public RotateEventData (EventSystem ES, float d = 0) : base (ES)
        {
            rotateDelta = d;
        }
    }
   
    public interface IPinchHandler : IEventSystemHandler
    {
        void OnPinch (PinchEventData data);
    }
   
    public interface IRotateHandler : IEventSystemHandler
    {
        void OnRotate (RotateEventData data);
    }
   
   
    public static class MultiTouchModuleEvents
    {
        private static void Execute (IPinchHandler handler, BaseEventData eventData)
        {
            handler.OnPinch (ExecuteEvents.ValidateEventData<PinchEventData> (eventData));
        }
       
        private static void Execute (IRotateHandler handler, BaseEventData eventData)
        {
            handler.OnRotate (ExecuteEvents.ValidateEventData<RotateEventData> (eventData));
        }
       
        public static ExecuteEvents.EventFunction<IPinchHandler> pinchHandler {
            get { return Execute; }
        }
       
        public static ExecuteEvents.EventFunction<IRotateHandler> rotateHandler {
            get { return Execute; }
        }
    }
}
3 Likes

Does this play nice with other touch inputs? Say icon buttons on a zoomable map?

Also thank you for the continued work on this!

Hello.

As Unity have yet to open source their TouchInputModule code ( correct me on this, if at all ), this is standalone code for now.

Functionality can be added to this.

Will add the most commonly used ones soon. Something like pointerClick just has to be there. Then you should be able to zoom the map and / or tap the buttons.

Thanks for checking it out.
Nihal

Maybe a dumb question. But how can i use your code?
I tried it like this on a canvas but did not have any logging event.

using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;

public class test2 : MonoBehaviour, IRotateHandler, IPinchHandler {
   public void OnPinch (PinchEventData data)
   {
     GameState.Instance.debugPanel.addLine("Pinch :" + data.data[0].pointerId +" " + data.data[1].pointerId);
     Camera.main.transform.Translate(new Vector3(data.pinchDelta, 0, data.pinchDelta),Space.World);
   }

   public void OnRotate (RotateEventData data)
   {
     GameState.Instance.debugPanel.addLine("Rotate :" + data.data[0].pointerId +" " + data.data[1].pointerId);
     Camera.main.transform.Rotate(Vector3.forward, data.rotateDelta);
   }
}

After getting a logging event I wanted to try exchanging “Input.GetTouch (0)”/“Input.GetTouch (1)” with “(Input.GetTouch(_pinchData.data[0].pointerId))” and “(Input.GetTouch(_pinchData.data[1].pointerId))”
and instead of calling “Input.GetTouch (1).position” i wanted to try “_pinchData.data[1].position” to nearly remove the usage of the normal Input class. (Still have to think about what to do with TouchPhase.Ended and TouchPhase.Canceled)
If my understanding is not completely wrong that could remove the limitation of only registering the first two fingers.

1 Like

Hey Kogar.

Did you add the MultiTouchInput to the EventSystem Gameobject in your scene ? I disable the default Standard Input and Touch input Module that are on there, to make sure there is no interference.

So I have a scene with default Cube, with this script on it.

using UnityEngine;
using System.Collections;

using UnityEngine.EventSystems;

public class MultiTouch : MonoBehaviour, IPinchHandler, IRotateHandler
{
    public float pinchDrag = 10;
    public float rotateDrag = 1;
   
    public void OnPinch (PinchEventData data)
    {
        GetComponent<Transform> ().localScale += new Vector3 (data.pinchDelta / pinchDrag, data.pinchDelta / pinchDrag, data.pinchDelta / pinchDrag);
    }
   
    public void OnRotate (RotateEventData data)
    {
        GetComponent<Transform> ().Rotate (Vector3.forward, data.rotateDelta / rotateDrag);
    }
}

The Main Camera has a Physics Raycaster on it to detect the 3d collider attached to the Cube. and it should work just fine. It should call the handlers.

1 Like

the cube will rotate and scale depending on your gestures. The camera will remain unchanged.

The events are called on the objects / colliders in the scene that the camera can raycast to. But what you do in the Handler scripts is totally upto you, like rotate the camera around the object.
e.g.

public class RotateCamera : Monobehaviour, IRotateHandler 
{
    public Transform mainCamera;

    public void OnRotate(RotateEventData data)
    {
        mainCamera.rotateAround(gameObject.Transform.position, Vector3.up, data.rotateDelta);
    }
}

Something to that effect.

Hope that helps. :slight_smile:

1 Like

Yes i had not added the class to the eventsystem gameobject. I thought that it would somehow extend the normal methods.
To completely exchange TouchInputModule in the EventSystem gameobject i had to change a few lines.
public class MultiTouchInputModule : PointerInputModule with public class MultiTouchInputModule : TouchInputModule and in these functions i had to add super() methods

protected override void Start ()
{
base.Start();
//your code
}
public override void Process (
base.Process();
//your code
)

MultitouchInput added and TouchInput Component is removed. Now I get logging events and normal TouchInput is still functioning.
Time to see what can be done about always getting the right fingers and how i can best use it for my use cases :slight_smile:

2 Likes