¿Cómo diferenciar correctamente los clics simples y los clics dobles en Unity3D usando C #?

15

Lo que estoy tratando de entender y aprender aquí es algo muy básico: ¿cuál es la forma más general y pulida de diferenciar adecuadamente los clics simples y dobles para los 3 botones principales del mouse en Unity, usando C #?

Mi énfasis en la palabra correctamente no es para nada. Aunque me sorprendió que esto nunca haya sido preguntado en este sitio web, por supuesto, he buscado y encontrado numerosas preguntas similares en los foros de Unity y en las propias páginas de preguntas y respuestas de Unity. Sin embargo, como suele ser el caso con las respuestas publicadas en estos recursos, todos parecían estar completamente equivocados o al menos un poco demasiado aficionados.

Dejame explicar. Las soluciones propuestas siempre tuvieron cualquiera de los dos enfoques y sus defectos correspondientes:

  1. cada vez que ocurre un clic, calcule el delta de tiempo desde el último clic y si fuera más pequeño que un umbral dado, se detectaría un doble clic. Sin embargo, esta solución da como resultado que se detecte un solo clic rápido antes de que se detecte el doble clic, lo que no es deseable en la mayoría de las situaciones porque luego activa las acciones tanto de un solo clic como de doble clic.

Áspero ejemplo:

float dclick_threshold = 0.25f;
double timerdclick = 0;

if (Input.GetMouseButtonDown(0) 
{
   if (Time.time - timerdclick) > dclick_threshold)
   {
       Debug.Log("single click");
       //call the SingleClick() function, not shown
   } else {
       Debug.Log("double click");
       //call the DoubleClick() function, not shown
   }
   timerdclick = Time.time;
}
  1. establezca un retraso de espera para que se active el clic único, lo que significa que, con cada clic, solo active las acciones de un solo clic si el tiempo transcurrido desde el último clic supera un umbral determinado. Sin embargo, esto da resultados lentos, ya que esperar antes de que sea posible notar la espera antes de que se detecten los clics únicos. Peor que eso, este tipo de solución adolece de discrepancias entre las máquinas, ya que no tiene en cuenta qué tan lenta o rápida es la configuración de velocidad de doble clic del usuario en el nivel del sistema operativo.

Áspero ejemplo:

   float one_click = false;
   float dclick_threshold = 0.25f;
   double timerdclick = 0;

   if (one_click && ((Time.time - timerdclick) > dclick_threshold))
   {
       Debug.Log("single click");
       //call the SingleClick() function, not shown
       one_click = false;
   }

   if (Input.GetMouseButtonDown(0))
   {

       if (!one_click)
       {
           dclick = -1;
           timerdclick = Time.time;
           one_click = true;
       }

       else if (one_click && ((Time.time - timerdclick) < dclick_threshold))
       {
           Debug.Log("double click");
           //call the DoubleClick() function, not shown
           one_click = false;
       }

   }

Por lo tanto, mis preguntas se convierten en lo siguiente. ¿Existe una forma más pulida y confiable de detectar los clics dobles en Unity usando C #, aparte de estas soluciones, y una que maneje los problemas antes mencionados?

Bennton
fuente
Hmm Detectando correctamente los clics dobles ... Solo puedo pensar en cómo lo haría , lo que probablemente no sea menos aficionado.
Draco18s ya no confía en SE
2
No creo que pueda detectar mejor los clics, no puede * predecir los resultados de la acción física del usuario. Prefiero centrarme en diseñar mi GUI y mecánica para que las acciones desencadenadas oculten el hecho tan bien como puedan. * bueno, en teoría podrías, implementando algunos comportamientos de uso de análisis de IA, pero los resultados no serían consistentes
wondra
1
Refiriéndose a la respuesta 1, "Sin embargo, esta solución da como resultado que se detecta un solo clic rápido antes de que se detecte el doble clic, lo que no es deseable". Realmente no veo lo que estás buscando. Si hay un problema con esta solución, creo que es la mecánica del juego la que necesita ajustes. Tome un RTS por ejemplo. Haga clic en una unidad para seleccionarla (acción A), haga doble clic para seleccionar todas las demás unidades (acción B). Si hace doble clic, no solo desea la acción B, desea A y B. Si desea que suceda algo completamente diferente, debe ser un clic derecho, o ctrl + clic, etc.
Peter

Respuestas:

13

Las corutinas son divertidas:

using UnityEngine;
using System.Collections;

public class DoubleClickTest : MonoBehaviour 
{
private float doubleClickTimeLimit = 0.25f;

protected void Start()
{
    StartCoroutine(InputListener());
}

// Update is called once per frame
private IEnumerator InputListener() 
{
    while(enabled)
    { //Run as long as this is activ

        if(Input.GetMouseButtonDown(0))
            yield return ClickEvent();

        yield return null;
    }
}

private IEnumerator ClickEvent()
{
    //pause a frame so you don't pick up the same mouse down event.
    yield return new WaitForEndOfFrame();

    float count = 0f;
    while(count < doubleClickTimeLimit)
    {
        if(Input.GetMouseButtonDown(0))
        {
            DoubleClick();
            yield break;
        }
        count += Time.deltaTime;// increment counter by change in time between frames
        yield return null; // wait for the next frame
    }
    SingleClick();
}


private void SingleClick()
{
    Debug.Log("Single Click");
}

private void DoubleClick()
{
    Debug.Log("Double Click");
}

}
CostelloNicho
fuente
Esto no parece detectar cuando se presiona un botón individual; solo cuando se hace clic en la pantalla o se hace doble clic en general (aunque OP no lo solicitó, por lo que no es justo preguntar tanto). ¿Alguna sugerencia sobre cómo se puede hacer eso?
zavtra
No veo cómo esto es diferente al segundo método propuesto en la pregunta.
John Hamilton
5

No puedo leer ingles Pero UniRx puede ayudarlo.

https://github.com/neuecc/UniRx#introduction

void Start () {

    var clickStream = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(0));
    var bufferStream = clickStream.Buffer (clickStream.Throttle (TimeSpan.FromMilliseconds (250))).Publish ().RefCount ();
    bufferStream.Where (xs => xs.Count == 1).Subscribe (_ => Debug.Log ("single click"));
    bufferStream.Where (xs => xs.Count >= 2).Subscribe (_ => Debug.Log ("double click"));
}
usuario2971260
fuente
Funciona, pero no exactamente como los clics dobles se implementan en Windows. Cuando hace clic, por ejemplo, 6 veces seguidas, solo se produce un doble clic. En Windows, serían 3 clics dobles. Puedes intentarlo Control Panel → Mouse.
Maxim Kamalov
2

El retraso de doble clic predeterminado de Windows es de 500 ms = 0,5 segundos.

En general, en un juego es mucho más fácil manejar el doble clic en el control que se está haciendo clic, no globalmente. Si decide manejar los clics dobles en todo el mundo, debe implementar soluciones alternativas para evitar 2 efectos secundarios muy indeseables:

  1. Cada clic puede retrasarse 0,5 segundos.
  2. Un solo clic en un control seguido rápidamente por un solo clic en otro control podría malinterpretarse como un doble clic en el segundo control.

Si tiene que usar una implementación global, también deberá mirar la ubicación del clic: si el mouse se movió demasiado, no es un doble clic. Normalmente implementaría un rastreador de enfoque que restablezca el contador de doble clic cada vez que cambie el enfoque.

A la pregunta de si debe esperar hasta que pase el tiempo de espera de doble clic, esto debe manejarse de manera diferente para cada control. Tienes 4 eventos diferentes:

  1. Flotar.
  2. Un solo clic, que puede o no convertirse en un doble clic.
  3. Haga doble clic.
  4. Tiempo de espera de doble clic, se confirmó que un solo clic no es un doble clic.

Siempre que sea posible, intente diseñar las interacciones de la GUI de modo que el último evento no se use: el Explorador de archivos de Windows seleccionará un elemento con un solo clic de inmediato, lo abrirá con un doble clic y no hará nada en un solo confirmado hacer clic.

En otras palabras: usa ambas opciones que sugirió, dependiendo de cuál sea el comportamiento deseado del control.

Peter
fuente
2

Las soluciones anteriores funcionan para los clics realizados en toda la pantalla. Si desea detectar doble clic en botones individuales, he encontrado que esta modificación a una respuesta anterior está funcionando bien:

using UnityEngine;
using System.Collections;

public class DoubleClickTest : MonoBehaviour
{
    private float doubleClickTimeLimit = 0.35f;
    bool clickedOnce = false;
    public string singleTapMove;
    public string doubleTapMove;
    float count = 0f;

    public void startClick(){
        StartCoroutine (ClickEvent ());
    }

    public IEnumerator ClickEvent()
    {
        if (!clickedOnce && count < doubleClickTimeLimit) {
            clickedOnce = true;
        } else {
            clickedOnce = false;
            yield break;  //If the button is pressed twice, don't allow the second function call to fully execute.
        }
        yield return new WaitForEndOfFrame();

        while(count < doubleClickTimeLimit)
        {
            if(!clickedOnce)
            {
                DoubleClick();
                count = 0f;
                clickedOnce = false;
                yield break;
            }
            count += Time.deltaTime;// increment counter by change in time between frames
            yield return null; // wait for the next frame
        }
        SingleClick();
        count = 0f;
        clickedOnce = false;
    }
    private void SingleClick()
    {
        Debug.Log("Single Click");
    }

    private void DoubleClick()
    {
        Debug.Log("Double Click");
    }
}

Adjunte este script a todos los botones que desee. Luego, en la sección Al hacer clic del editor (para cada botón al que adjunte esto), haga clic en '+', agregue el botón como el objeto del juego y luego seleccione "DoubleClickTest -> startClick () 'como la función para llamar cuando se presiona el botón.

zavtra
fuente
1

Estaba buscando un controlador adecuado de un solo doble clic. Encontré este tema y reuní algunas ideas realmente buenas. Así que finalmente se me ocurrió la siguiente solución y quiero compartirla con ustedes. Espero que encuentre útil este método.

Creo que es un gran enfoque usar el inspector de Unity, porque de esta manera su código es más flexible, ya que puede hacer referencia a cualquiera de los objetos de su juego de forma visual y segura.

Primero agregue el componente Sistema de eventos a cualquiera de sus objetos de juego. Esto también agregará el Módulo de entrada independiente componente . Esto se encargará de los eventos de entrada como clics y toques del mouse. Recomiendo hacer esto en un objeto de juego individual.

A continuación, agregue el componente Disparador de eventos a cualquiera de sus objetos de juego habilitados para Raycast . Como un elemento UI o un sprite, un objeto 3D con un colisionador . Esto entregará el evento click a nuestro script. Agregue el tipo de evento Pointer Click y configure el objeto del juego del receptor que tiene el componente de script Click Controller y finalmente seleccione su onClick () método . (También puede modificar el script para recibir todos los clics en una Actualización () y omitir este paso).

Por lo tanto, el objeto del juego receptor, que por supuesto puede ser el que activa el evento, hará cualquier cosa con los eventos de clic simple o doble.

Puede establecer el límite de tiempo para doble clic, el botón deseado y los eventos personalizados para un solo clic y doble clic. La única limitación es que puedes elegir solo un botón a la vez para un objeto del juego .

ingrese la descripción de la imagen aquí

El script en sí comienza una rutina en cada primer clic con dos temporizadores. FirstClickTime y currentTime sincronizados con el primero.

Esta rutina se repite una vez al final de cada cuadro hasta que expire el límite de tiempo. Mientras tanto, el método onClick () continúa contando los clics.

Cuando expira el límite de tiempo, llama al evento de uno o dos clics de acuerdo con el clickCounter. Luego establece este contador en cero para que la rutina pueda terminar y todo el proceso comience desde el principio.

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

public class ClickController : MonoBehaviour
{
    public float timeLimit = 0.25f;
    public PointerEventData.InputButton button;

    [System.Serializable]
    public class OnSingleClick : UnityEvent {};
    public OnSingleClick onSingleClick;

    [System.Serializable]
    public class OnDoubleClick : UnityEvent {};
    public OnDoubleClick onDoubleClick;

    private int clickCount;
    private float firstClickTime;
    private float currentTime;

    private ClickController () {
        clickCount = 0;
    }

    public void onClick (BaseEventData data) {

        PointerEventData pointerData = data as PointerEventData;

        if (this.button != pointerData.button) {
            return;
        }

        this.clickCount++;

        if (this.clickCount == 1) {
            firstClickTime = pointerData.clickTime;
            currentTime = firstClickTime;
            StartCoroutine (ClickRoutine ());
        }
    }

    private IEnumerator ClickRoutine () {

        while (clickCount != 0)
        {
            yield return new WaitForEndOfFrame();

            currentTime += Time.deltaTime;

            if (currentTime >= firstClickTime + timeLimit) {
                if (clickCount == 1) {
                    onSingleClick.Invoke ();
                } else {
                    onDoubleClick.Invoke ();
                }
                clickCount = 0;
            }
        }
    }
}
Norb
fuente
Bueno, claro, mi primer post debería ser un poco más informativo. Así que agregué una descripción detallada y perfeccioné un poco el script. Elimina tu -1 si te gusta.
Norb
0

Creo que no hay forma de leer la mente del usuario, ya sea que haga clic en simple o doble, después de todo, tenemos que esperar la entrada. Aunque puede establecer una espera de 0.01 segundos (tanto como mínimo) para el segundo clic. En este sentido, iría por la opción de espera.

Y si Coroutines son divertidos ...

Una alternativa

float _threshold = 0.25f;

void Start ()
{
    StartCoroutine ("DetectTouchInput");
}

IEnumerator DetectTouchInput ()
{
    while (true) {
        float duration = 0;
        bool doubleClicked = false;
        if (Input.GetMouseButtonDown (0)) {
            while (duration < _threshold) {
                duration += Time.deltaTime;
                yield return new WaitForSeconds (0.005f);
                if (Input.GetMouseButtonDown (0)) {
                    doubleClicked = true;
                    duration = _threshold;
                    // Double click/tap
                    print ("Double Click detected");
                }
            }
            if (!doubleClicked) {
                // Single click/tap
                print ("Single Click detected");
            }
        }
        yield return null;
    }
}
Hamza Hasan
fuente