Comment différencier correctement les simples clics et les doubles clics dans Unity3D en utilisant C #?

Ce que j'essaie de comprendre et d’apprendre ici est une chose très fondamentale: quelle est la manière la plus générale et la plus raffinée de différencier correctement les différenciations en un ou deux clics pour les 3 principaux boutons de la souris dans Unity, en utilisant C #?

Mon accent sur le mot correctement n’est pas pour rien. Bien que je sois surpris que cela ne semble pas avoir été demandé auparavant sur ce site, bien sûr, j'ai cherché et trouvé de nombreuses questions similaires sur les forums d'Unity et sur ses propres pages de questions-réponses. Cependant, comme c'est souvent le cas avec les réponses affichées dans ces ressources, elles semblaient toutes être soit totalement fausses, soit du moins un peu trop amateures.

Laisse-moi expliquer. Les solutions proposées ont toujours eu l’une des deux approches et leurs défauts correspondants:

  1. chaque fois qu'un clic se produit, calculez le delta temporel depuis le dernier clic et s'il était inférieur à un seuil donné, un double clic serait détecté. Cependant, cette solution entraîne la détection d’un simple clic rapide avant le double-clic, ce qui est indésirable dans la plupart des situations car elle active alors les actions en simple et double-clic.

Exemple approximatif:

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. définissez un délai d'attente pour l'activation du clic simple, ce qui signifie que chaque clic n'active les actions que si le délai écoulé depuis le dernier clic est supérieur à un seuil donné. Cependant, cela donne des résultats lents, car il est possible d'attendre avant de pouvoir détecter l'attente avant la détection de simples clics. Pire que cela, ce type de solution souffre de divergences d’une machine à l’autre, car elle ne prend pas en compte la lenteur ou la rapidité du réglage de la vitesse de double-clic de l’utilisateur au niveau du système d’exploitation.

Exemple approximatif:

   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;
       }

   }

Par conséquent, mes questions deviennent les suivantes. Existe-t-il un moyen plus sophistiqué et fiable de détecter les doubles clics dans Unity à l'aide de C#, et un autre qui traite les problèmes susmentionnés?

16
Je ne pense pas que vous puissiez détecter mieux les clics - vous ne pouvez pas * prédire les résultats des actions physiques de l'utilisateur. Je préférerais me concentrer sur la conception de mon interface utilisateur graphique et de mes mécanismes, de manière à ce que les actions déclenchées masquent le plus possible les faits. * eh bien, en théorie, vous pouvez implémenter certains comportements d'analyse de l'IA, mais les résultats ne seraient pas cohérents
ajouté l'auteur tetranz, source
En vous référant à la réponse 1, "Cependant, cette solution entraîne la détection d'un simple clic rapide avant la détection du double-clic, ce qui n'est pas souhaitable." Je ne vois pas vraiment ce que vous cherchez. S'il y a un problème avec cette solution, je pense que ce sont les mécanismes de jeu qui doivent être peaufinés. Prenons un RTS par exemple. Vous cliquez sur une unité pour la sélectionner (action A), double-cliquez pour sélectionner toutes les autres unités (action B). Si vous double-cliquez, vous ne voulez pas seulement l'action B, vous voulez les actions A et B. Si vous voulez que quelque chose de totalement différent se produise, vous devez cliquer avec le bouton droit ou ctrl + clic, etc.
ajouté l'auteur Morais, source
Hmm. Détecter correctement les doubles clics ... Je ne peux que penser à la façon dont je le ferais, ce qui n’est probablement pas moins amateur.
ajouté l'auteur Draco18s, source

7 Réponses

Les coroutines sont amusantes:

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");
}

}
13
ajouté
Je ne vois pas en quoi cela diffère de la deuxième méthode proposée dans la question.
ajouté l'auteur John Hamilton, source
Cela ne semble pas détecter le moment où un bouton est enfoncé. juste au moment où vous cliquez ou double-cliquez sur l'écran en général (bien que OP ne l'ait pas demandé, il est donc injuste de demander autant). Des suggestions sur la façon dont on pourrait faire ça?
ajouté l'auteur JeremyMann, source

Je ne peux pas lire l'anglais. Mais UniRx pourra peut-être vous aider.

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"));
}
5
ajouté
Cela fonctionne, mais pas exactement comme les doubles clics sont implémentés dans Windows. Lorsque vous cliquez, par exemple, 6 fois de suite, un seul double-clic est généré. Sous Windows, ce serait 3 doubles clics. Vous pouvez essayer dans Panneau de configuration → Souris .
ajouté l'auteur Brenden Riggs, source

Le délai de double-clic par défaut de Windows est de 500 ms = 0,5 seconde.

Généralement, dans un jeu, il est beaucoup plus facile de gérer un double-clic sur le contrôle sur lequel vous cliquez, et non pas globalement. Si vous décidez de gérer les doubles clics de manière globale, vous devez implémenter des solutions de contournement pour éviter 2 effets secondaires très indésirables:

  1. Chaque clic peut être retardé de 0,5 seconde.
  2. Un simple clic sur un contrôle suivi rapidement d'un clic sur un autre contrôle peut être interprété à tort comme un double-clic sur le deuxième contrôle.

Si vous devez utiliser une implémentation globale, vous devrez également regarder l'emplacement du clic - si la souris est déplacée trop loin, il ne s'agit pas d'un double clic. Vous devez normalement implémenter un suivi du focus qui réinitialise le compteur de double-clic à chaque changement de focus.

À la question de savoir si vous devez attendre que le délai d’expiration du double-clic soit écoulé, cela doit être traité différemment pour chaque contrôle. Vous avez 4 événements différents:

  1. Survolez.
  2. Un seul clic, qui peut ou non devenir un double-clic.
  3. Double-cliquez sur.
  4. Le délai d'attente du double clic, le clic simple a confirmé qu'il ne s'agissait pas d'un double clic.

Dans la mesure du possible, vous essayez de concevoir les interactions avec l'interface graphique de sorte que le dernier événement ne soit pas utilisé: l'Explorateur de fichiers Windows sélectionnera immédiatement un élément en un seul clic, l'ouvrira en un double-clic et ne fera rien sur un single confirmé. Cliquez sur.

En d'autres termes: vous utilisez les deux options que vous avez suggérées, en fonction du comportement souhaité du contrôle.

2
ajouté

Les solutions ci-dessus fonctionnent pour les clics effectués sur tout l'écran. Si vous voulez détecter les doubles-clics sur des boutons individuels, j'ai trouvé que cette modification à une réponse ci-dessus fonctionne 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");
    }
}

Attach this script to as many buttons as you want. Then in the On Click section of the editor (for each button you attach this to), click '+', add the button to itself as the game object, and then select "DoubleClickTest -> startClick()' as the function to call when the button is pressed.

2
ajouté

Je cherchais un bon gestionnaire de simple-double clic. J'ai trouvé ce sujet et rassemblé de très bonnes idées. Alors finalement, je suis venu avec la solution suivante et je veux la partager avec vous. J'espère que vous trouverez cette méthode utile.

Je pense que l'utilisation de l'inspecteur de Unity est une excellente solution, car votre code est ainsi plus flexible, car vous pouvez référencer n'importe lequel de vos objets de jeu de manière visuelle et en toute sécurité.

Tout d'abord, ajoutez le composant Système d'événements à l'un de vos objets de jeu. Cela ajoutera également le composant Module d'entrée autonome . Cela prendra en charge les événements d'entrée tels que les clics de souris et les contacts. Je recommande de faire cela sur un objet de jeu individuel.

Ajoutez ensuite le composant Déclencheur d’événements à n’importe quel de vos objets de jeu Raycast activé . Comme un élément d'interface utilisateur ou , une image-objet, un objet 3D avec un collisionneur . Cela livrera l'événement click à notre script. Ajoutez le type d'événement Pointer Click et définissez l'objet de jeu récepteur contenant le composant de script Contrôleur de clics , puis sélectionnez sa méthode onClick() . . (Vous pouvez également modifier le script pour recevoir tous les clics dans une mise à jour() et ignorer cette étape.)

Ainsi, l’objet de jeu récepteur, qui peut bien sûr être celui avec le déclencheur d’événement, fera quoi que ce soit avec les événements en un ou deux clics.

Vous pouvez définir la limite de temps pour le double-clic, le bouton souhaité et les événements personnalisés pour les clics simples et doubles. La seule limitation est que vous ne pouvez choisir qu'un seul bouton à la fois pour un seul objet de jeu .

enter image description here

Le script lui-même lance une coroutine à chaque premier clic avec deux minuteries. FirstClickTime et currentTime sont synchronisés avec le premier.

Cette coroutine effectue une boucle une fois à la fin de chaque image jusqu'à l'expiration du délai imparti. Pendant ce temps, la méthode onClick() continue à compter les clics.

À l'expiration du délai imparti, il appelle l'événement simple ou double clic en fonction du clickCounter. Ensuite, il met ce compteur à zéro pour que la coroutine puisse se terminer et que tout le processus commence depuis le début.

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;
            }
        }
    }
}
1
ajouté
Eh bien, oui, mon premier post devrait être un peu plus informatif. J'ai donc ajouté une description détaillée et affiné le script un peu. Veuillez retirer votre -1 si vous l'aimez.
ajouté l'auteur polyknowmeal, source

Je pense qu'il n'y a aucun moyen de lire dans la tête de l'utilisateur, qu'il clique en double ou en double, après tout, nous devons attendre l'entrée. Bien que vous puissiez définir une attente de 0,01 seconde (le moins possible) pour le second clic. À cet égard, je choisirais l'option d'attente.

Et oui les Coroutines sont amusants ...

Une alternative

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;
    }
}
0
ajouté

la manière la plus générale et la plus sophistiquée de différencier correctement les simples clics et les double-clics

Vous êtes un programmeur maintenant, ... où vous allez, vous n'avez pas besoin de routes. Je ne voudrais pas m'attarder à ce qui est «convenable» ou «poli», mais plutôt me concentrer sur «ça marche» et «les autres peuvent-ils comprendre pourquoi ça marche».

Par exemple, quelque chose d'aussi simple que: (juste moi noodling, n'a pas vérifié que cela est conforme)

DateTime lastButtonDown;//last time the button was pressed
bool isListeningForDoubleClick = false;
void Update(){

    int timeSinceLastClick = DateTime.Now.Subtract(lastButtonDown).TotalMiliseconds;

    if(Input.MouseButtonDown(0)){//when you press a button
         //if you pressed the mouse twice within 100 milliseconds (or w/e)
         if(timeSinceLastClick<100 && isListeningForDoubleClick){
             Debug.Log("Double Clicked");
             isListeningForDoubleClick = false;
         }else{
             lastButtonDown = DateTime.Now;//store the last time you clicked.
             isListeningForDoubleClick = true;
         }
    }else{
         if(timeSinceLastClick>=100 && isListeningForDoubleClick){
             Debug.Log("Single Click");
             isListeningForDoubleClick = false;
         }
    }
}

En bref, cela détectera si vous double-cliquez ou attendez après un simple clic jusqu'à ce que la vérification de l'heure du double-clic échoue, puis le compte comme un simple clic.

Maintenant, si vous utilisez uGUI, alors ils ont un double clic même (iirc) que vous pouvez utiliser.

Donc… la meilleure chose que je puisse vous apprendre est de ne pas vous soucier du «meilleur» en ce qui concerne ce que les autres pourraient faire, et de ce qui est «meilleur» pour vous - comme dans: quelque chose qui fonctionne bien, et quelque chose que vous et les autres pouvez comprendre.

J'espère que cela pourra vous aider, bonne chance!!

0
ajouté