Як правильно розмежувати одиничні клацання та подвійне клацання в Unity3D за допомогою C #?


15

Те, що я намагаюся зрозуміти і дізнатись тут, є дуже базовим: що є найбільш загальним і найбільш відполірованим способом правильної диференціації одинарних клавіш та подвійних клацань для трьох основних кнопок миші в Unity, використовуючи C #?

Моє наголос на слові правильно не дарма. Хоча я був здивований, що цього, здається, ніколи раніше не запитували на цьому веб-сайті, але, звичайно, я шукав і знаходив численні подібні питання на форумах Unity та на власних сторінках відповідей на питання Unity. Однак, як це часто трапляється з відповідями, розміщеними в цих ресурсах, всі вони здавалися або явними неправильними, або, принаймні, занадто любительськими.

Дозволь пояснити. Запропоновані рішення завжди мали будь-який із двох підходів та відповідні вади:

  1. кожного разу, коли відбувається клацання, обчислюйте дельту часу з моменту останнього клацання, і якби він був меншим за заданий поріг, подвійний клацання буде виявлено. Однак це рішення призводить до того, що до виявлення подвійного клацання виявляється швидке одинарне клацання, що в більшості ситуацій небажано, оскільки тоді воно активує як дії одного, так і подвійного клацання.

Прикладний приклад:

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. встановіть затримку очікування для активації одного клацання, що означає, що при кожному натисканні активуйте дії одного натискання лише тоді, коли затримка часу з останнього клацання перевищує заданий поріг. Однак це дає мляві результати, оскільки очікування, перш ніж можна буде помітити очікування, перш ніж буде виявлено один клацання. Гірше того, що подібне рішення страждає від розбіжностей між машинами, оскільки не враховує, наскільки повільно або наскільки швидко відбувається швидкість подвійного клацання користувача на рівні ОС.

Прикладний приклад:

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

   }

Тому мої запитання стають такими. Чи є більш відшліфований і надійний спосіб виявлення подвійних клацань в Unity за допомогою C #, окрім цих рішень, і той, який вирішує вищезазначені проблеми?


Хм. Правильне виявлення подвійних клацань ... Я можу думати лише про те, як би мені це зробити, що, мабуть, не менш любительсько.
Draco18s більше не довіряє SE

2
Я не думаю, що ви можете краще виявити кліки - ви не можете * передбачити результати фізичної дії користувача. Я б скоріше зосередився на розробці свого графічного інтерфейсу та механіки, щоб спрацьовані дії приховували факт настільки добре, наскільки вони можуть. * ну, теоретично ви могли, реалізуючи деякі ІІ, аналізуючи поведінку використання, але результати не були б узгоджені
wondra

1
Посилаючись на відповідь 1, "Однак це рішення призводить до того, що швидке одинарне клацання виявляється до того, як подвійне клацання буде виявлено, що небажано". Я насправді не бачу, що ти шукаєш. Якщо з цим рішенням є проблеми, я думаю, що саме ігрова механіка потребує налаштування. Візьмемо для прикладу РТС. Ви клацаєте одиницю, щоб вибрати її (дія А), двічі клацніть, щоб вибрати всі інші одиниці (дія Б). Якщо ви подвійним клацанням миші, ви не просто хочете дії B, ви хочете, щоб і A, і B. Якщо ви хочете, щоб сталося щось зовсім інше, це повинен бути клацання правою кнопкою миші, або ctrl + клацання тощо.
Петро,

Відповіді:


13

Програми розваги:

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

}

Схоже, це не виявляє, коли натискається окрема кнопка; просто при натисканні на екран або подвійному клацанні в цілому (хоча ОП цього не вимагала, тому не чесно так багато запитувати). Будь-які пропозиції, як можна це зробити?
завтра

Я не бачу, чим це відрізняється від другого способу, запропонованого у питанні.
Джон Гамільтон

5

Я не можу читати англійську. Але UniRx може вам допомогти.

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

Він працює, але не зовсім так, як подвійні клацання реалізовані в Windows. При натисканні, наприклад, 6 разів поспіль, створюється лише один подвійний клацання. У Windows це було б 3 подвійних клацання. Ви можете спробувати Control Panel → Mouse.
Максим Камалов

2

Затримка подвійного клацання Windows за замовчуванням становить 500 мс = 0,5 секунди.

Взагалі в грі набагато простіше впоратися з подвійним клацанням на клавішу управління, на яку натискають, а не в усьому світі. Якщо ви вирішили обробляти подвійні кліки в усьому світі, вам доведеться здійснити обхідні шляхи, щоб уникнути двох дуже небажаних побічних ефектів:

  1. Кожен клік можна затримати на 0,5 секунди.
  2. Одне клацання на одному елементі управління, за яким швидко відбувається одне клацання, на іншому елементі управління може бути неправильно трактоване як подвійне клацання на другому елементі управління.

Якщо вам доведеться використовувати глобальну реалізацію, вам також потрібно буде переглянути місце розташування клацання - якщо миша пересунулася занадто далеко, це не подвійне клацання. Зазвичай ви реалізуєте трекер фокусування, який скидає лічильник подвійних клацань кожного разу, коли фокус змінюється.

На питання, чи варто чекати, поки пройде час очікування подвійного клацання, це потрібно вирішувати по-різному для кожного елемента управління. У вас є 4 різні події:

  1. Наведіть курсор.
  2. Одноразове клацання, яке може або не стане подвійним клацанням.
  3. Подвійне клацання.
  4. Час очікування подвійного клацання, один клік підтверджено не подвійним клацанням.

Коли це можливо, ви намагаєтесь спроектувати взаємодію з графічним інтерфейсом таким чином, щоб остання подія не використовувалася: Провідник файлів Windows негайно вибере елемент одним натисканням кнопки та відкриє його подвійним клацанням і нічого не зробить на підтвердженому синглі клацніть.

Іншими словами: ви використовуєте обидва запропоновані вами варіанти, залежно від бажаної поведінки елемента керування.


2

Вищезазначені рішення працюють за кліки на всьому екрані. Якщо ви хочете виявити подвійне клацання на окремих кнопках, я виявив, що ця модифікація вищезгаданої відповіді працює добре:

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

Прикріпіть цей скрипт до стільки кнопок, скільки вам потрібно. Потім у розділі редактора On Click (для кожної кнопки, до якої ви приєднаєте цю функцію) натисніть "+", додайте кнопку до себе як ігровий об'єкт, а потім виберіть "DoubleClickTest -> startClick ()" як функцію для виклику при натисканні кнопки.


1

Я шукав належного обробника з одним подвійним клацанням. Я знайшов цю тему і зібрав кілька справді хороших ідей. Отже, нарешті я придумав таке рішення і хочу поділитися ним з вами. Я сподіваюся, що Ви вважаєте цей метод корисним.

Я думаю, що це чудовий підхід до використання інспектора Unity, адже таким чином ваш код є більш гнучким, оскільки ви зможете візуально та безпечно посилатися на будь-який із своїх ігрових об’єктів.

Спочатку додайте компонент Система подій до будь-якого з ваших ігрових об’єктів. Це також додасть окремий компонент модуля вводу . Це дозволить потурбуватися про вхідні події, як натискання миші та торкання. Я рекомендую робити це на індивідуальному ігровому об’єкті.

Далі додайте компонент Тригер події до будь-якого ігрового об’єкта з включеною трансляцією . Як елемент UI або спрайт, 3D-об’єкт із колайдером . Це доставить подію натискання нашому сценарію. Додайте тип події вказівника клацання та встановіть об’єкт гри приймача, на якому є компонент скрипта контролера кліків, і, нарешті, виберіть його onClick () метод . (Також ви можете змінити скрипт, щоб отримати всі кліки в оновлення (), і пропустити цей крок.)

Таким чином, об'єкт гри приймача, який, звичайно, може бути тим, що має тригер події, буде робити щось із подіями одного чи подвійного клацання.

Ви можете встановити обмеження часу для подвійного клацання, потрібну кнопку та спеціальні події для одинарного та подвійного клацання. Єдиним обмеженням є те , що ви можете вибрати тільки одну кнопку в той час , в протягом одного ігрового об'єкта .

введіть тут опис зображення

Сам сценарій запускає підпрограму при кожному першому клацанні двома таймерами. ПершийClickTime і currentTime синхронізуються з першим.

Ця послідовність циклу циклу проводиться один раз у кінці кожного кадру, поки не закінчиться обмеження часу. Тим часом метод onClick () продовжує рахувати кліки.

Коли термін закінчується, він викликає подію одно- або подвійного клацання відповідно до ClickCounter. Потім він встановлює цей лічильник на нуль, щоб програма змогла закінчитись і весь процес розпочнеться з початку.

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, якщо вам це подобається.
Норб

0

Я думаю, що немає можливості прочитати розум користувача або він натисне одинарний або подвійний, адже нам доведеться чекати введення даних. Хоча для другого клацання можна встановити очікування 0,01 секунди (як мінімум). У зв'язку з цим я б пішов на варіант очікування.

І ТАК Coroutines весело ...

Альтернатива

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;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.