Làm cách nào để phân biệt chính xác các nhấp chuột đơn và nhấp đúp trong Unity3D bằng C #?


15

Điều tôi đang cố gắng hiểu và tìm hiểu ở đây là một điều rất cơ bản: cách tổng quát nhất và lịch sự nhất để phân biệt chính xác các lần nhấp chuột và nhấp đúp cho 3 nút chuột chính trong Unity, sử dụng C # là gì?

Tôi nhấn mạnh vào từ đúng cách không phải là không có gì. Mặc dù tôi rất ngạc nhiên vì điều này dường như chưa bao giờ được hỏi trong trang web này trước đó, tất nhiên tôi đã tìm kiếm và tìm thấy rất nhiều câu hỏi tương tự trên các diễn đàn của Unity và các trang câu hỏi của riêng Unity. Tuy nhiên, vì nó thường là trường hợp với câu trả lời được đăng trong các tài nguyên này, tất cả chúng dường như đều sai hoặc ít nhất là hơi quá nghiệp dư.

Hãy để tôi giải thích. Các giải pháp đề xuất luôn có một trong hai cách tiếp cận và sai sót tương ứng của chúng:

  1. mỗi lần nhấp chuột xảy ra, hãy tính thời gian delta kể từ lần nhấp cuối cùng và nếu nó nhỏ hơn một ngưỡng nhất định, một lần nhấp đúp sẽ được phát hiện. Tuy nhiên, giải pháp này dẫn đến việc phát hiện một lần nhấp nhanh trước khi nhấp đúp chuột, điều này là không mong muốn trong hầu hết các tình huống vì sau đó nó kích hoạt cả hành động nhấp chuột đơn và nhấp đúp.

Ví dụ thô sơ:

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. đặt độ trễ chờ cho một lần nhấp được kích hoạt, có nghĩa là, tại mỗi lần nhấp, chỉ kích hoạt các hành động nhấp chuột đơn nếu thời gian trễ kể từ lần nhấp cuối cùng vượt quá ngưỡng cho phép. Tuy nhiên, điều này mang lại kết quả chậm chạp, vì chờ đợi trước khi có thể nhận thấy sự chờ đợi trước khi phát hiện một lần nhấp chuột. Tồi tệ hơn thế, loại giải pháp này chịu sự khác biệt giữa các máy, vì nó không tính đến việc cài đặt tốc độ nhấp đúp của người dùng ở cấp độ hệ điều hành chậm như thế nào.

Ví dụ thô sơ:

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

   }

Do đó, câu hỏi của tôi trở thành như sau. Có cách nào đáng tin cậy và đáng tin cậy hơn để phát hiện các nhấp chuột đôi trong Unity bằng C #, ngoài các giải pháp này và một giải pháp xử lý các vấn đề đã nói ở trên không?


Hừm. Phát hiện đúng hai lần nhấp chuột ... Tôi chỉ có thể nghĩ về cách tôi sẽ thực hiện nó, điều mà có lẽ không kém phần nghiệp dư.
Draco18 không còn tin tưởng vào SE

2
Tôi không nghĩ rằng bạn có thể phát hiện các nhấp chuột tốt hơn - bạn không thể * dự đoán kết quả hành động vật lý của người dùng. Tôi thà tập trung vào việc thiết kế GUI và cơ học của mình để các hành động được kích hoạt che giấu sự thật tốt nhất có thể. * tốt, theo lý thuyết bạn có thể, thực hiện một số hành vi sử dụng phân tích AI, nhưng kết quả sẽ không nhất quán
wonderra

1
Tham khảo câu trả lời 1, "Tuy nhiên, giải pháp này dẫn đến việc phát hiện một lần nhấp nhanh trước khi nhấp đúp chuột, điều này là không mong muốn." Tôi không thực sự thấy những gì bạn đang tìm kiếm. Nếu có vấn đề với giải pháp này, tôi nghĩ đó là cơ chế trò chơi cần tinh chỉnh. Lấy một RTS chẳng hạn. Bạn bấm vào một đơn vị để chọn nó (hành động A), nhấp đúp để chọn tất cả các đơn vị khác (hành động B). Nếu bạn nhấp đúp chuột, bạn không chỉ muốn hành động B, bạn muốn cả A và B. Nếu bạn muốn điều gì đó hoàn toàn khác xảy ra, đó phải là nhấp chuột phải hoặc nhấp chuột ctrl, v.v.
Peter

Câu trả lời:


13

Quân đoàn rất vui:

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

}

Điều này dường như không phát hiện khi nhấn một nút riêng lẻ; chỉ khi màn hình được nhấp hoặc nhấp đúp nói chung (mặc dù OP không yêu cầu điều đó, vì vậy thật không công bằng khi hỏi quá nhiều). Bất kỳ đề xuất về cách một người có thể làm điều đó?
zavtra

Tôi không thấy cách này khác với phương pháp thứ hai được đề xuất trong câu hỏi.
John Hamilton

5

Tôi không thể đọc tiếng anh Nhưng UniRx có thể giúp bạn.

https://github.com/neuecc/UniRx#int sinhtion

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

Nó hoạt động, nhưng không chính xác như nhấp đúp được thực hiện trong Windows. Khi bạn nhấp, chẳng hạn, 6 lần liên tiếp, chỉ một lần nhấp đúp được tạo ra. Trong Windows, nó sẽ là 3 lần nhấp đúp. Bạn có thể thử Control Panel → Mouse.
Maxim Kamalov 16/2/2016

2

Độ trễ nhấp chuột mặc định của Windows là 500ms = 0,5 giây.

Nói chung trong một trò chơi, việc xử lý nhấp đúp chuột vào điều khiển được nhấp chứ không phải trên toàn cầu sẽ dễ dàng hơn nhiều. Nếu bạn quyết định xử lý các nhấp chuột đôi trên toàn cầu, bạn phải triển khai các cách giải quyết để tránh 2 tác dụng phụ rất không mong muốn:

  1. Mỗi lần nhấp có thể bị trễ 0,5 giây.
  2. Một lần bấm vào một điều khiển được theo dõi nhanh chóng bằng một lần bấm vào điều khiển khác có thể bị hiểu sai là một lần bấm đúp vào điều khiển thứ hai.

Nếu bạn phải sử dụng triển khai toàn cầu, bạn cũng cần xem vị trí của nhấp chuột - nếu chuột di chuyển quá xa thì đó không phải là một lần nhấp đúp. Thông thường bạn sẽ triển khai trình theo dõi tiêu điểm đặt lại bộ đếm nhấp đúp bất cứ khi nào tiêu điểm thay đổi.

Đối với câu hỏi nếu bạn nên đợi cho đến khi hết thời gian nhấp đúp, điều này cần được xử lý khác nhau cho mỗi điều khiển. Bạn có 4 sự kiện khác nhau:

  1. Bay lượn.
  2. Nhấp chuột một lần, có thể hoặc không thể trở thành nhấp đúp.
  3. Nhấn đúp chuột.
  4. Hết thời gian nhấp đúp, nhấp một lần xác nhận không phải là nhấp đúp.

Bất cứ khi nào có thể, bạn cố gắng thiết kế các tương tác GUI sao cho sự kiện cuối cùng sẽ không được sử dụng: Windows File Explorer sẽ chọn một mục trên một lần nhấp ngay lập tức và sẽ mở nó khi nhấp đúp và sẽ không làm gì trên một lần xác nhận nhấp chuột.

Nói cách khác: Bạn sử dụng cả hai tùy chọn mà bạn đề xuất, tùy thuộc vào hành vi mong muốn của điều khiển là gì.


2

Các giải pháp trên hoạt động cho các nhấp chuột được thực hiện trên toàn màn hình. Nếu bạn muốn phát hiện nhấp đúp vào các nút riêng lẻ, tôi đã thấy sửa đổi này cho câu trả lời ở trên đang hoạt động tốt:

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

Đính kèm tập lệnh này vào nhiều nút như bạn muốn. Sau đó, trong phần Bật Nhấp của trình chỉnh sửa (đối với mỗi nút bạn đính kèm), nhấp vào '+', thêm nút vào chính nó làm đối tượng trò chơi, sau đó chọn "DoubleClickTest -> startClick () 'làm chức năng gọi khi nhấn nút.


1

Tôi đang tìm kiếm một trình xử lý nhấp đúp đơn thích hợp. Tôi đã tìm thấy chủ đề này và thu thập một số ý tưởng thực sự tốt. Vì vậy, cuối cùng tôi đã đưa ra giải pháp sau đây và tôi muốn chia sẻ nó với bạn. Tôi hy vọng bạn tìm thấy phương pháp này hữu ích.

Tôi nghĩ rằng đó là một cách tiếp cận tuyệt vời để sử dụng trình kiểm tra của Unity, bởi vì cách này mã của bạn linh hoạt hơn khi bạn có thể tham chiếu bất kỳ đối tượng trò chơi nào của mình một cách trực quan và an toàn.

Trước tiên, thêm thành phần Hệ thống sự kiện vào bất kỳ đối tượng trò chơi nào của bạn. Điều này cũng sẽ thêm thành phần Mô-đun đầu vào độc lập . Điều này sẽ chăm sóc các sự kiện đầu vào như nhấp chuột và chạm. Tôi khuyên bạn nên làm điều này trên một đối tượng trò chơi cá nhân.

Tiếp theo, thêm thành phần Kích hoạt sự kiện vào bất kỳ đối tượng trò chơi kích hoạt raycast nào của bạn . Giống như một yếu tố UI hoặc sprite, một đối tượng 3D với máy va chạm . Điều này sẽ cung cấp sự kiện bấm vào kịch bản của chúng tôi. Thêm loại sự kiện Nhấp chuột và đặt đối tượng trò chơi người nhận có thành phần tập lệnh Nhấp chuột điều khiển trên đó và cuối cùng chọn phương thức onClick () của nó . (Ngoài ra, bạn có thể thay đổi tập lệnh để nhận tất cả các lần nhấp trong Cập nhật () và bỏ qua bước này.)

Vì vậy, đối tượng trò chơi máy thu, tất nhiên có thể là đối tượng kích hoạt sự kiện, sẽ làm bất cứ điều gì với các sự kiện nhấp chuột đơn hoặc doudle.

Bạn có thể đặt giới hạn thời gian cho nhấp đúp, nút mong muốn và các sự kiện tùy chỉnh cho nhấp chuột đơn và nhấp đúp. Hạn chế duy nhất là bạn chỉ có thể chọn một nút tại một thời điểm cho một đối tượng trò chơi .

nhập mô tả hình ảnh ở đây

Chính kịch bản bắt đầu một coroutine trên mỗi lần nhấp đầu tiên với hai bộ định thời. FirstClickTime và currentTime đồng bộ hóa với lần đầu tiên.

Vòng lặp coroutine này một lần vào cuối mỗi khung cho đến khi hết thời gian giới hạn. Trong khi đó, phương thức onClick () tiếp tục đếm số lần nhấp.

Khi hết thời gian, nó sẽ gọi sự kiện một hoặc hai lần theo clickCount. Sau đó, nó đặt bộ đếm này về 0 để coroutine có thể kết thúc và toàn bộ quá trình bắt đầu lại từ đầu.

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

Vâng, đúng, bài viết đầu tiên của tôi nên có nhiều thông tin hơn. Vì vậy, tôi đã thêm mô tả chi tiết và tinh chỉnh kịch bản một chút. Vui lòng xóa -1 của bạn nếu bạn thích nó.
Norb

0

Tôi nghĩ rằng không có cách nào để đọc được suy nghĩ của người dùng dù anh ta sẽ nhấp một hoặc hai lần, sau tất cả chúng ta phải chờ đầu vào. Mặc dù bạn có thể đặt chờ 0,01 giây (thấp nhất có thể) cho lần nhấp thứ hai. Về vấn đề này tôi sẽ đi cho các tùy chọn chờ đợi.

Và CÓ Coroutinesrất vui ...

Một sự thay thế

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;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.