Как правильно различать одиночные и двойные щелчки в Unity3D с помощью C #?

15

То, что я пытаюсь понять и изучить здесь, является очень простой вещью: каков наиболее общий и наиболее удобный способ правильно различать одиночные и двойные щелчки для 3 основных кнопок мыши в 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 #, помимо этих решений, и способ, который решает вышеупомянутые проблемы?

Bennton
источник
Хм. Правильное обнаружение двойных щелчков ... Я могу думать только о том, как поступить, что, вероятно, не менее любительски.
Draco18s больше не доверяет SE
2
Я не думаю, что вы можете лучше определять клики - вы не можете * предсказать результаты физических действий пользователя. Я бы предпочел сосредоточиться на разработке своего графического интерфейса и механики, чтобы инициируемые действия скрывали факт настолько хорошо, насколько они могли. * ну, теоретически вы могли бы реализовать некоторый ИИ, анализирующий поведение использования, но результаты были бы непоследовательными
wondra
1
Ссылаясь на ответ 1: «Однако это решение приводит к тому, что быстрый одиночный щелчок обнаруживается до того, как обнаруживается двойной щелчок, что нежелательно». Я действительно не вижу, что вы ищете. Если есть проблема с этим решением, я думаю, что игровая механика нуждается в доработке. Взять хотя бы RTS. Вы щелкаете юнит, чтобы выбрать его (действие A), дважды щелкните, чтобы выбрать все другие юниты (действие B). Если вы дважды щелкнете мышью, вы не просто захотите действие B, вы хотите, чтобы и A, и B. Если вы хотите, чтобы произошло нечто совершенно иное, это должен быть щелчок правой кнопкой мыши, или Ctrl + щелчок и т. Д.
Peter

Ответы:

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

}
CostelloNicho
источник
Это, кажется, не обнаруживает, когда отдельная кнопка нажата; просто когда по экрану щелкают или вообще дважды щелкают (хотя OP не просил об этом, поэтому нечестно так много просить). Любые предложения о том, как можно это сделать?
Завтра
Я не вижу, чем это отличается от второго метода, предложенного в вопросе.
Джон Гамильтон
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"));
}
user2971260
источник
Это работает, но не совсем так, как двойные щелчки реализованы в Windows. Когда вы нажимаете, например, 6 раз подряд, создается только один двойной щелчок. В Windows это будет 3 двойных клика. Вы можете попробовать Control Panel → Mouse.
Максим Камалов
2

Задержка двойного щелчка по умолчанию для Windows составляет 500 мс = 0,5 секунды.

Как правило, в игре гораздо проще обрабатывать двойной щелчок на элементе управления, а не глобально. Если вы решите обрабатывать двойные щелчки по всему миру, вы должны применить обходные пути, чтобы избежать 2 очень нежелательных побочных эффектов:

  1. Каждый клик может быть задержан на 0,5 секунды.
  2. Один щелчок по одному элементу управления, за которым быстро следует один щелчок по другому элементу управления, может быть неверно истолкован как двойной щелчок по второму элементу управления.

Если вам нужно использовать глобальную реализацию, вам также нужно посмотреть на местоположение щелчка - если мышь переместилась слишком далеко, это не двойной щелчок. Обычно вы используете трекер фокусировки, который сбрасывает счетчик двойного щелчка при изменении фокуса.

На вопрос, следует ли вам ждать истечения времени ожидания двойного щелчка, это нужно обрабатывать по-разному для каждого элемента управления. У вас есть 4 разных события:

  1. Hover.
  2. Один клик, который может или не может стать двойным кликом.
  3. Двойной клик.
  4. Тайм-аут двойного щелчка, подтвержденный один клик не является двойным щелчком.

Когда бы ни было возможно, вы пытаетесь спроектировать взаимодействия GUI таким образом, чтобы последнее событие не использовалось: Windows File Explorer сразу выберет элемент одним щелчком мыши, откроет его двойным щелчком и ничего не изменит для подтвержденного одиночного события. нажмите кнопку.

Другими словами: вы используете оба предложенных вами варианта в зависимости от желаемого поведения элемента управления.

Питер
источник
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");
    }
}

Прикрепите этот скрипт к любому количеству кнопок. Затем в разделе редактора «По щелчку» (для каждой кнопки, к которой вы его прикрепляете), нажмите «+», добавьте кнопку к себе в качестве игрового объекта, а затем выберите «DoubleClickTest -> startClick ()» в качестве функции для вызова. когда кнопка нажата.

Завтра
источник
1

Я искал подходящий обработчик двойного клика. Я нашел эту тему и собрал несколько действительно хороших идей. Итак, наконец, я пришел к следующему решению и хочу поделиться им с вами. Я надеюсь, что вы найдете этот метод полезным.

Я думаю, что это отличный способ использовать инспектор Unity, потому что таким образом ваш код становится более гибким, так как вы можете ссылаться на любой из ваших игровых объектов визуально и безопасно.

Сначала добавьте компонент Event System к любому из ваших игровых объектов. Это также добавит компонент Автономный модуль ввода . Это позаботится о событиях ввода, таких как щелчки мыши и касания. Я рекомендую делать это на отдельном игровом объекте.

Затем добавьте компонент Event Trigger к любому игровому объекту с поддержкой raycast . Как элемент пользовательского интерфейса или спрайт, 3D-объект с коллайдером . Это доставит событие click в наш скрипт. Добавьте тип события Pointer Click и установите игровой объект-приемник, в котором есть компонент сценария Click Controller, и, наконец, выберите его метод onClick () . (Также вы можете изменить скрипт, чтобы получать все клики в Update () и пропустить этот шаг.)

Таким образом, игровой объект-приемник, который, конечно, может быть объектом с триггером события, будет делать что-либо с событиями одиночного или двойного щелчка.

Вы можете установить ограничение по времени для двойного щелчка, нужную кнопку и пользовательские события для одиночного и двойного щелчка. Единственным ограничением является то, что вы можете выбрать только одну кнопку за раз для одного игрового объекта .

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

Сам скрипт запускает сопрограмму при каждом первом нажатии с двумя таймерами. FirstClickTime и 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;
            }
        }
    }
}
Norb
источник
Ну, правильно, мой первый пост должен быть более информативным. Поэтому я добавил подробное описание и немного усовершенствовал скрипт. Пожалуйста, удалите -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;
    }
}
Хамза Хасан
источник