Wie kann man Einzelklicks und Doppelklicks in Unity3D mit C # richtig unterscheiden?

15

Was ich hier zu verstehen und zu lernen versuche, ist eine sehr grundlegende Sache: Was ist die allgemeinste und ausgefeilteste Methode, um Einzel- und Doppelklicks für die 3 wichtigsten Maustasten in Unity mit C # richtig zu unterscheiden?

Meine Betonung auf dem Wort richtig ist nicht umsonst. Obwohl ich überrascht war, dass dies auf dieser Website anscheinend noch nie gestellt wurde, habe ich natürlich zahlreiche ähnliche Fragen in den Foren von Unity und auf den Frage-Antwort-Seiten von Unity gesucht und gefunden. Wie es jedoch häufig bei Antworten in diesen Ressourcen der Fall ist, schienen sie alle entweder einfach falsch oder zumindest ein bisschen zu amateurhaft zu sein.

Lassen Sie mich erklären. Die vorgeschlagenen Lösungen hatten immer einen der beiden Ansätze und die entsprechenden Mängel:

  1. Berechnen Sie bei jedem Klick das Zeit-Delta seit dem letzten Klick. Wenn es kleiner als ein bestimmter Schwellenwert ist, wird ein Doppelklick erkannt. Diese Lösung führt jedoch dazu, dass ein schneller Einzelklick erkannt wird, bevor der Doppelklick erkannt wird, was in den meisten Situationen unerwünscht ist, da dann sowohl Einzelklick- als auch Doppelklickaktionen aktiviert werden.

Grobes Beispiel:

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. Legen Sie eine Wartezeit für die Aktivierung des Einzelklicks fest. Dies bedeutet, dass bei jedem Klick nur dann Einzelklickaktionen aktiviert werden, wenn die Zeitverzögerung seit dem letzten Klick einen bestimmten Schwellenwert überschreitet. Dies führt jedoch zu schleppenden Ergebnissen, da ein Warten, bevor es möglich ist, das Warten zu bemerken, bevor Einzelklicks erkannt werden. Schlimmer noch, diese Art von Lösung weist maschinenübergreifende Diskrepanzen auf, da nicht berücksichtigt wird, wie langsam oder wie schnell die Doppelklickgeschwindigkeitseinstellung des Benutzers auf Betriebssystemebene ist.

Grobes Beispiel:

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

   }

Daher werden meine Fragen wie folgt. Gibt es eine bessere und zuverlässigere Methode zum Erkennen von Doppelklicks in Unity mithilfe von C # als diese Lösungen und eine, mit der die oben genannten Probleme behoben werden können?

Bennton
quelle
Hmm. Doppelklicks richtig erkennen ... Ich kann mir nur überlegen, wie ich es machen würde, was wahrscheinlich nicht weniger amateurhaft ist.
Draco18s
2
Ich glaube nicht, dass du kannst Klicks besser erkennen können - Sie können die Ergebnisse der körperlichen Aktivität des Benutzers nicht vorhersagen. Ich würde mich lieber darauf konzentrieren, meine GUI und Mechanik so zu gestalten, dass ausgelöste Aktionen die Tatsache so gut wie möglich verbergen. *
Nun
1
In Bezug auf Antwort 1 führt diese Lösung jedoch dazu, dass ein schneller Einzelklick erkannt wird, bevor der Doppelklick erkannt wird, was unerwünscht ist. Ich sehe nicht wirklich, wonach Sie suchen. Wenn es ein Problem mit dieser Lösung gibt, denke ich, ist es die Spielmechanik, die optimiert werden muss. Nehmen Sie zum Beispiel ein RTS. Sie klicken auf eine Einheit, um sie auszuwählen (Aktion A), doppelklicken Sie, um alle anderen Einheiten auszuwählen (Aktion B). Wenn Sie doppelklicken, möchten Sie nicht nur Aktion B, sondern auch A und B. Wenn Sie möchten, dass etwas völlig anderes passiert, sollten Sie mit der rechten Maustaste klicken oder mit gedrückter Strg-Taste auf die Schaltfläche klicken.
Peter

Antworten:

13

Coroutinen machen Spaß:

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
quelle
Dies scheint nicht zu erkennen, wenn eine einzelne Taste gedrückt wird; Nur wenn der Bildschirm angeklickt oder doppelt angeklickt wird (obwohl OP dies nicht angefordert hat, ist es nicht fair, so viel zu fragen). Irgendwelche Vorschläge, wie man das machen könnte?
Zavtra
Ich sehe keinen Unterschied zu der in der Frage vorgeschlagenen zweiten Methode.
John Hamilton
5

Ich kann kein Englisch lesen. Möglicherweise kann UniRx Ihnen jedoch weiterhelfen.

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
quelle
Es funktioniert, aber nicht genau so, wie Doppelklicks in Windows implementiert sind. Wenn Sie beispielsweise sechsmal hintereinander klicken, wird nur ein Doppelklick erzeugt. In Windows wären es 3 Doppelklicks. Sie können es versuchen Control Panel → Mouse.
Maxim Kamalov
2

Die Windows-Standardverzögerung für Doppelklicks beträgt 500 ms = 0,5 Sekunden.

Im Allgemeinen ist es in einem Spiel viel einfacher, einen Doppelklick auf das angeklickte Steuerelement auszuführen, als global. Wenn Sie sich entscheiden, Doppelklicks global zu verarbeiten, müssen Sie Problemumgehungen implementieren, um zwei sehr unerwünschte Nebenwirkungen zu vermeiden:

  1. Jeder einzelne Klick kann um 0,5 Sekunden verzögert werden.
  2. Ein einfacher Klick auf ein Steuerelement, gefolgt von einem einfachen Klick auf ein anderes Steuerelement, kann als Doppelklick auf das zweite Steuerelement fehlinterpretiert werden.

Wenn Sie eine globale Implementierung verwenden müssen, müssen Sie auch die Position des Klicks überprüfen. Wenn die Maus zu weit bewegt wurde, ist dies kein Doppelklick. Normalerweise implementieren Sie einen Fokus-Tracker, der den Doppelklickzähler zurücksetzt, wenn sich der Fokus ändert.

Bei der Frage, ob Sie warten sollen, bis das Doppelklick-Timeout abgelaufen ist, muss dies für jedes Steuerelement unterschiedlich gehandhabt werden. Sie haben 4 verschiedene Ereignisse:

  1. Schweben.
  2. Einfacher Klick, der zu einem Doppelklick werden kann oder nicht.
  3. Doppelklick.
  4. Zeitlimit für Doppelklick, einfacher Klick als kein Doppelklick bestätigt.

Wann immer möglich, versuchen Sie, die GUI-Interaktionen so zu gestalten, dass das letzte Ereignis nicht verwendet wird: Der Windows-Datei-Explorer wählt ein Element sofort mit einem Klick aus und öffnet es mit einem Doppelklick. Bei einem bestätigten Ereignis wird nichts unternommen klicken.

Mit anderen Worten: Sie verwenden beide von Ihnen vorgeschlagenen Optionen, abhängig vom gewünschten Verhalten des Steuerelements.

Peter - Unban Robert Harvey
quelle
2

Die obigen Lösungen funktionieren für Klicks auf dem gesamten Bildschirm. Wenn Sie Doppelklicks auf einzelne Schaltflächen erkennen möchten, habe ich festgestellt, dass diese Änderung an der obigen Antwort gut funktioniert:

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

Hängen Sie dieses Skript an beliebig viele Schaltflächen an. Klicken Sie dann im Abschnitt "On Click" des Editors (für jede Schaltfläche, an die Sie diese anhängen) auf "+", fügen Sie die Schaltfläche als Spielobjekt hinzu und wählen Sie "DoubleClickTest -> startClick ()" als aufzurufende Funktion wenn die Taste gedrückt wird.

zavtra
quelle
1

Ich suchte nach einem geeigneten Single-Double-Click-Handler. Ich habe dieses Thema gefunden und einige wirklich gute Ideen gesammelt. Also habe ich mir endlich die folgende Lösung ausgedacht und möchte sie mit Ihnen teilen. Ich hoffe, Sie finden diese Methode hilfreich.

Ich denke, es ist ein großartiger Ansatz, den Inspector von Unity zu verwenden, da Ihr Code auf diese Weise flexibler ist, da Sie jedes Ihrer Spielobjekte visuell und sicher referenzieren können.

Fügen Sie zuerst die Event System- Komponente zu einem Ihrer Spielobjekte hinzu. Dadurch wird auch die Komponente Standalone Input Module hinzugefügt . Dies kümmert sich um die Eingabeereignisse wie Mausklicks und Berührungen. Ich empfehle dies an einem einzelnen Spielobjekt zu tun.

Fügen Sie als Nächstes die Event Trigger- Komponente zu einem Ihrer Raycast-fähigen Spielobjekte hinzu. Wie ein Oberflächenelement oder ein Sprite ein 3D-Objekt mit einem Collider . Dadurch wird das Click-Ereignis an unser Skript übergeben. Fügen Sie den Ereignistyp " Zeigerklick" hinzu, und legen Sie das Empfängerspielobjekt fest, auf dem sich die Skriptkomponente " Click Controller" befindet , und wählen Sie schließlich die Methode " onClick () " aus. (Sie können auch das Skript ändern, um alle Klicks in einem Update () zu erhalten, und diesen Schritt überspringen.)

Das Empfängerspielobjekt, das natürlich dasjenige mit dem Ereignisauslöser sein kann, wird also alles mit den Einzel- oder Doppelklickereignissen tun.

Sie können das Zeitlimit für Doppelklick, die gewünschte Schaltfläche und die benutzerdefinierten Ereignisse für Einzel- und Doppelklick festlegen. Die einzige Einschränkung ist , dass Sie können wählen , nur eine Taste zu einem Zeitpunkt für ein Spiel Objekt .

Bildbeschreibung hier eingeben

Das Skript selbst startet bei jedem ersten Klick eine Coroutine mit zwei Timern. Die firstClickTime und die currentTime werden mit der ersten synchronisiert.

Diese Coroutine wiederholt sich einmal am Ende jedes Frames, bis das Zeitlimit abgelaufen ist. Währenddessen zählt die onClick () -Methode die Klicks weiter.

Wenn das Zeitlimit abgelaufen ist, ruft es das Einzel- oder Doppelklick-Ereignis entsprechend dem clickCounter auf. Dann setzt es diesen Zähler auf Null, so dass die Coroutine beendet werden kann und der gesamte Prozess von vorne beginnt.

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
quelle
Naja, mein erster Beitrag sollte etwas informativer sein. Also habe ich eine detaillierte Beschreibung hinzugefügt und das Skript ein wenig verfeinert. Bitte entfernen Sie Ihre -1, wenn es Ihnen gefällt.
Norb
0

Ich denke, es gibt keine Möglichkeit, die Gedanken des Benutzers zu lesen, entweder er klickt einfach oder doppelt, schließlich müssen wir auf die Eingabe warten. Sie können jedoch eine Wartezeit von 0,01 Sekunden (so niedrig wie möglich) für den zweiten Klick festlegen. In dieser Hinsicht würde ich mich für die Wartemöglichkeit entscheiden.

Und JA Coroutinesmacht Spaß ...

Eine 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
quelle