microsoft / MixedRealityToolkit-Unity

This repository is for the legacy Mixed Reality Toolkit (MRTK) v2. For the latest version of the MRTK please visit https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity
https://aka.ms/mrtkdocs
MIT License
6k stars 2.12k forks source link

Double Tap recognition #654

Closed ghost closed 7 years ago

ghost commented 7 years ago

Hello,

I want to use a double Tap gesture. I noticed, that the InputClickedEventData provides a TapCount.

But when I try the follwoing, only the Signle Tap is displayed.

Am I missing something? Or do I need to set up some timer to recognize two single Taps in a short time?

    public void OnInputClicked(InputClickedEventData eventData)
    {
      if (eventData.TapCount > 1)
        Debug.Log("Double Tap");
      else
        Debug.Log("Single Tap");
    }

Thanks in advance.

ghost commented 7 years ago

Hi ! I use that and it's work, try it :)

    public class a_clickcount : MonoBehaviour, IPointerClickHandler
    {
        int tap;
        public void OnPointerClick(PointerEventData eventData)
        {
            tap = eventData.clickCount;

            if (tap == 2)
            {
                // do something
            }

        }
    }
ghost commented 7 years ago

@EmreSuzenExia Thanks for your answer. In which namespace does the IPointerClickHandler lives? I tried the "HoloToolkit.Unity.InputModule", but this one does not contain the interface.

killerantz commented 7 years ago

The HoloToolkit.Unity.InputModule is the correct namespace. The interface and method have been renamed to IInputClickHandler and OnInputClicked(InputClickedEventData eventData). The property to check is eventData.TapCount, but I am only getting "1" as a result in the Editor. The EditorHandsInput does state that only single taps are handled in the editor.

// We currently only support single taps in editor.
inputManager.RaiseInputClicked(this, editorHandData.HandId, 1);
ghost commented 7 years ago

@killerantz Thank you for that answer.

ghost commented 7 years ago

I tried out the TapCount Property using the HoloLens. But it only picks up two single taps... Even using the Bluetooth Clicker does not change the behaviour.

Any more ideas on how to enable double Tapping?

ghost commented 7 years ago

Why you don't use my example ?

int tap;
public void OnPointerClick(PointerEventData eventData)
{
    tap = eventData.clickCount;
}
killerantz commented 7 years ago

The IPointerClickHandler is in the UnityEngine.EventSystems namespace.

ghost commented 7 years ago

@EmreSuzenExia : I tried that one out. Put I think I'm missing something out. The method is not called.

I have an InputManager, a HoloCamera, a cursor and a simple cube (to which my test script is attached) is the scene. Is there somethin else missing?

killerantz commented 7 years ago

I have not been able to get double-tap to work with the IPointerClickHandler, I only get zeros as the clickCount. You'll need to have EventSystem added to the scene for IPointerClickEvents to occur. Just add a UI button to the scene and the EventSystem comes in as well.

I was able to get double-taps to work using IInputClickHandler and OnInputClicked(InputClickedEventData eventData) though.

First thing is the GesturesInput is not setup to handle double-tap detection, so you need to add it to the recognizable gestures by opening HoloToolkit/UI/Scripts/InputSources/GesturesInput.cs

On line 46 add GestureSettings.DoubleTap to the gestureRecognizer like this.

gestureRecognizer.SetRecognizableGestures(GestureSettings.Tap | GestureSettings.ManipulationTranslate | GestureSettings.Hold | GestureSettings.DoubleTap);

Then in the OnInputClicked handler look at the tapCount which should be 1 or 2. public virtual void OnInputClicked(InputClickedEventData eventData) { print(eventData.TapCount); }

Two events will fire on a double-tap (1 and 2), so if you want to capture both single-taps and double-taps on the same object, you will have to delay the single-tap execution to see if a double-tap does not immediately follow. I hope that makes sense.

Maybe there's a part of the EventSystem that needs to register to listen for double taps to get the IPointer stuff working, but I haven't dug that far into it yet.

StephenHodgson commented 7 years ago

First thing is the GesturesInput is not setup to handle double-tap detection, so you need to add it to the recognizable gestures by opening HoloToolkit/UI/Scripts/InputSources/GesturesInput.cs

Does it make sense that we actually make this change to the project?

killerantz commented 7 years ago

I think so, we have references to double-tap in the InputManager, but it is not enabled. Looks like it was an oversight.

I was wondering if there were any issues by having it enabled but not expecting it, like getting the double events fired during a double tap. Maybe it's something that we make it easier to enable as part of the framework? Just in case there is something we are missing.

Right now you can rapid fire single taps when double-tap is not recognized. This is the same outcome of having double-tap enabled, but not checking for tap counts. Having it enabled doesn't seem to change the behavior of the click event, just provides more data from my quick tests.

ghost commented 7 years ago

@killerantz : Thank you very much for your throughout investigation of this.

I'm not sure, but maybe we can define a new interface to only register doubleTaps? Or might it be better to mention the detection of single and double taps in the IInputClickedHandler?

Deaa-B commented 7 years ago

I've tried this but the problem i always get the single tap then the double tap is there any idea of how to make the single tap waits if there is another tap or not ? like one second for example ?

void Start()
    {
        StartCoroutine(HoldSphere());

        _gestureRecognizer = new GestureRecognizer();
        _gestureRecognizer.TappedEvent += GestureRecognizerOnTappedEvent;
        _gestureRecognizer.SetRecognizableGestures(GestureSettings.Tap | GestureSettings.DoubleTap );
        _gestureRecognizer.StartCapturingGestures();
        _gestureRecognizer.TappedEvent += (source, tapCount, ray) =>
        {
            if (tapCount == 1)
           {
                HoldSphere();
                if (tapCount == 2)
                    Shoot1();
          }
                else Shoot();

        };
    }
    IEnumerator HoldSphere()
    {
        yield return new WaitForSeconds(1);
    }
killerantz commented 7 years ago

When listening for a double tap there needs to be a way to cancel the single tap code execution.

Adding a local bool, like _hasDoubleTap could do it. I believe there's a 0.3 - 0.4 second threshold for a double tap to register.

bool _hasDoubleTap;
void Start()
{    
    StartCoroutine(HoldSphere());

    _gestureRecognizer = new GestureRecognizer();
    _gestureRecognizer.TappedEvent += GestureRecognizerOnTappedEvent;
    _gestureRecognizer.SetRecognizableGestures(GestureSettings.Tap | GestureSettings.DoubleTap );
    _gestureRecognizer.StartCapturingGestures();
    _gestureRecognizer.TappedEvent += (source, tapCount, ray) =>
    {
        if (tapCount == 2)
       {
            _hasDoubleTap = true;
            Shoot();
      }
      else 
      {
          _hasDoubleTap = false;
          HoldSpere();
      }
    };
}
IEnumerator HoldSphere()
{
    yield return new WaitForSeconds(0.4f);
    if(!_hasDounleTap){
      // execute single tap
    }
}
void Shoot()
{
   // execute double tap
{
Deaa-B commented 7 years ago

@killerantz thanks, but still the same problem. I only can define one gesture, either the first or the second tap, or the first and then the second. i want for example if i have one tap to throw a sphere, a double tap to throw a cube but first see if i have a single or double tap and then act. I've tried everything i hope I've cleared my point now. here is my whole code

public GestureRecognizer _gestureRecognizer;

    public float ForceMagnitude = 300f;
    void Start()
    {

        _gestureRecognizer = new GestureRecognizer();
        _gestureRecognizer.TappedEvent += GestureRecognizerOnTappedEvent;
        _gestureRecognizer.SetRecognizableGestures(GestureSettings.Tap | GestureSettings.DoubleTap );
        _gestureRecognizer.StartCapturingGestures();
        _gestureRecognizer.TappedEvent += (source, tapCount, ray) =>
        {
            if (tapCount == 1)
                StartCoroutine(HoldSphere());

            else if (tapCount == 2)
                Shoot();
            else
                Shoot1();
        };
    }
    IEnumerator HoldSphere()
    {
        yield return new WaitForSeconds(1);
    }

    private void GestureRecognizerOnTappedEvent(InteractionSourceKind source, int tapCount, Ray headRay)
    {

    }

    private void Shoot()
    {
        var eyeball = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        eyeball.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
        var rigidBody = eyeball.AddComponent<Rigidbody>();
        rigidBody.mass = 0.5f;
        rigidBody.position = transform.position;
        var forward =  transform.forward;
        forward = Quaternion.AngleAxis(-7, transform.right) * forward;
        rigidBody.AddForce(forward * ForceMagnitude);
    }
    private void Shoot1()
    {
        var eyeball = GameObject.CreatePrimitive(PrimitiveType.Cube);
        eyeball.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
        var rigidBody = eyeball.AddComponent<Rigidbody>();
        rigidBody.mass = 0.5f;
        rigidBody.position = transform.position;
        var forward = transform.forward;
        forward = Quaternion.AngleAxis(-7, transform.right) * forward;
        rigidBody.AddForce(forward * ForceMagnitude);
    }
killerantz commented 7 years ago

Honestly, if we have to check the tap count and wait for seconds to tell the difference between a single and double tap, then the double tap functionality is not really in place. If it were, there would be a DoubleTap Event and the system would handle all this. So it may be best to roll your own for now, it's the same amount of code and less hassle.

private Coroutine _tapRoutine;
void Start()
{
    _gestureRecognizer = new GestureRecognizer();
    _gestureRecognizer.TappedEvent += GestureRecognizerOnTappedEvent;
    _gestureRecognizer.SetRecognizableGestures(GestureSettings.Tap);
    _gestureRecognizer.StartCapturingGestures();
    _gestureRecognizer.TappedEvent += (source, tapCount, ray) =>
    {
            if (_tapRoutine == null)
            {
                _tapRoutine = StartCoroutine(TapTimer());
            }
            else
            {
                StopCoroutine(_tapRoutine);
                _tapRoutine = null;
                print("doubleTap!");
                Shoot();
            }
    };
}

private IEnumerator TapTimer()
{
      yield return new WaitForSeconds(0.3f);
      print("singleTap!");
      _tapRoutine = null;
      Shoot1();
}