Comment bien différencier les clics simples et double-cliquer dans Unity3D en utilisant C #?

15

Ce que j'essaie de comprendre et d'apprendre ici est une chose très basique: quelle est la façon la plus générale et la plus raffinée de différencier correctement les clics simples et les double-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 semble n'avoir jamais été posé sur ce site Web auparavant, j'ai bien sûr cherché et trouvé de nombreuses questions similaires sur les forums d'Unity et les propres pages de questions-réponses d'Unity. Cependant, comme c'est souvent le cas avec les réponses publiées dans ces ressources, elles semblaient toutes être soit complètement fausses, soit au moins un peu trop amateures.

Laissez-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 de temps depuis le dernier clic et s'il était inférieur à un seuil donné, un double clic serait détecté. Cependant, cette solution a pour résultat qu'un simple clic rapide est détecté avant que le double-clic ne soit détecté, ce qui n'est pas souhaitable dans la plupart des situations car il active alors les actions de 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 simple clic, ce qui signifie qu'à chaque clic, n'activez les actions de simple clic que si le délai depuis le dernier clic est supérieur à un seuil donné. Cependant, cela donne des résultats lents, car une attente avant qu'il soit possible de remarquer l'attente avant que des clics simples ne soient détectés. Pire que cela, ce type de solution souffre de divergences entre les machines, car il ne prend pas en compte la vitesse ou la vitesse du paramètre de 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 plus fiable de détecter les doubles-clics dans Unity en utilisant C #, autre que ces solutions, et un autre qui gère les problèmes susmentionnés?

Bennton
la source
Hmm. Détecter correctement les doubles-clics ... Je ne peux que penser à la façon dont j'y arriverais , ce qui n'est probablement pas moins amateur.
Draco18s ne fait plus confiance au SE
2
Je ne pense pas que vous puissiez mieux détecter les clics - vous ne pouvez pas * prédire les résultats de l'action physique de l'utilisateur. Je préfère me concentrer sur la conception de mon interface graphique et de ma mécanique afin que les actions déclenchées cachent le fait aussi bien qu'elles le peuvent. *
Eh
1
En se référant à la réponse 1, "Cependant, cette solution a pour résultat qu'un simple clic rapide est détecté avant que le double-clic ne soit détecté, ce qui n'est pas souhaitable." Je ne vois pas vraiment ce que tu cherches. S'il y a un problème avec cette solution, je pense que c'est la mécanique du jeu qui a besoin d'être modifiée. Prenez 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 à la fois A et B. Si vous voulez que quelque chose de complètement différent se produise, ce devrait être un clic droit, ou ctrl + clic, etc.
Peter

Réponses:

13

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

}
CostelloNicho
la source
Cela ne semble pas détecter lorsqu'un bouton individuel est enfoncé; juste quand l'écran est cliqué ou double-cliqué en général (bien que OP ne l'ait pas demandé, il n'est donc pas juste de demander autant). Avez-vous des suggestions sur la façon de faire cela?
zavtra
Je ne vois pas en quoi cela diffère de la deuxième méthode proposée dans la question.
John Hamilton
5

Je ne peux pas lire l'anglais. Mais UniRx peut être en mesure de 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"));
}
user2971260
la source
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 produit. Sous Windows, ce serait 3 doubles clics. Vous pouvez essayer Control Panel → Mouse.
Maxim Kamalov
2

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 le double-clic sur le contrôle sur lequel vous cliquez, pas de manière globale. Si vous décidez de gérer les doubles-clics globalement, 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 simple clic sur un autre contrôle pourrait être mal interprété 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 s'est déplacée trop loin, ce n'est pas un double clic. Vous implémenteriez normalement un tracker de focus qui réinitialise le compteur de double-clic chaque fois que le focus change.

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

  1. Flotter.
  2. Un simple clic, qui peut ou non devenir un double clic.
  3. Double-cliquez.
  4. Délai d'expiration du double clic, simple clic confirmé comme n'étant pas un double clic.

Dans la mesure du possible, vous essayez de concevoir les interactions de l'interface graphique de telle 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 seul 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.

Peter
la source
2

Les solutions ci-dessus fonctionnent pour les clics effectués sur l'ensemble de l'écran. Si vous souhaitez détecter les double-clics sur des boutons individuels, j'ai trouvé que cette modification d'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");
    }
}

Attachez ce script à autant de boutons que vous le souhaitez. Ensuite, dans la section On Click de l'éditeur (pour chaque bouton auquel vous l'attachez), cliquez sur '+', ajoutez le bouton à lui-même en tant qu'objet de jeu, puis sélectionnez "DoubleClickTest -> startClick () 'comme fonction à appeler lorsque le bouton est enfoncé.

zavtra
la source
1

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

Je pense que c'est une excellente approche pour utiliser l'inspecteur d'Unity, car de cette façon, votre code est plus flexible car vous pouvez référencer n'importe lequel de vos objets de jeu visuellement et en toute sécurité.

Ajoutez d'abord le composant Event System à 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 comme les clics et les touches de la souris. Je recommande de le faire sur un objet de jeu individuel.

Ajoutez ensuite le composant Event Trigger à l'un de vos objets de jeu compatibles avec la diffusion de rayons . Comme un élément d'interface utilisateur ou un sprite, un objet 3D avec un collisionneur . Cela fournira l'événement click à notre script. Ajoutez le type d'événement Pointer Click et définissez l'objet de jeu récepteur qui contient le composant de script Click Controller et enfin sélectionnez son onClick () méthode . (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 tout avec les événements de clic simple ou double.

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

entrez la description de l'image ici

Le script lui-même démarre une coroutine à chaque premier clic avec deux temporisateurs. Le firstClickTime et le currentTime en synchronisation avec le premier.

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

Lorsque le délai expire, 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 recommence 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;
            }
        }
    }
}
Norb
la source
Eh bien, à droite, mon premier post devrait être un peu plus informatif. J'ai donc ajouté une description détaillée et affiné un peu le script. Veuillez supprimer votre -1 si vous l'aimez.
Norb
0

Je pense qu'il n'y a aucun moyen de lire l'esprit de l'utilisateur, qu'il clique sur simple ou double, après tout, nous devons attendre l'entrée. Bien que vous puissiez définir une attente de 0,01 seconde (aussi faible) pour le deuxième clic. À cet égard, je choisirais l'option d'attente.

Et OUI, Coroutinesc'est amusant ...

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;
    }
}
Hamza Hasan
la source