Bagaimana membedakan dengan benar klik tunggal dan klik dua kali di Unity3D menggunakan C #?


15

Apa yang saya coba pahami dan pelajari di sini adalah hal yang sangat mendasar: apa cara yang paling umum dan paling halus untuk membedakan dengan benar klik tunggal dan klik ganda untuk 3 tombol mouse utama di Unity, menggunakan C #?

Penekanan saya pada kata dengan benar bukan untuk apa-apa. Meskipun saya terkejut bahwa ini sepertinya belum pernah ditanyakan di situs web ini sebelumnya, tentu saja saya telah mencari dan menemukan banyak pertanyaan serupa di forum Unity dan halaman tanya jawab sendiri. Namun, seperti yang sering terjadi dengan jawaban yang diposting di sumber daya ini, mereka semua tampaknya salah atau paling tidak terlalu amatir.

Biarkan saya jelaskan. Solusi yang diajukan selalu memiliki salah satu dari dua pendekatan dan kelemahan yang sesuai:

  1. setiap kali klik terjadi, hitung delta waktu sejak klik terakhir dan jika lebih kecil dari ambang yang diberikan, klik ganda akan terdeteksi. Namun, solusi ini menghasilkan klik cepat yang terdeteksi sebelum klik dua kali terdeteksi, yang tidak diinginkan di sebagian besar situasi karena kemudian mengaktifkan tindakan klik tunggal dan ganda.

Contoh kasar:

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. menetapkan penundaan waktu tunggu untuk klik tunggal yang akan diaktifkan, yang berarti, pada setiap klik, hanya mengaktifkan tindakan klik tunggal jika penundaan waktu sejak klik terakhir di atas ambang yang diberikan. Namun, ini memberikan hasil yang lamban, karena menunggu sebelum dimungkinkan untuk melihat menunggu sebelum satu klik terdeteksi. Lebih buruk dari itu, solusi semacam ini menderita ketidaksesuaian antar mesin, karena tidak memperhitungkan seberapa lambat atau seberapa cepat pengaturan kecepatan klik dua kali pengguna pada level OS.

Contoh kasar:

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

   }

Karena itu, pertanyaan saya menjadi sebagai berikut. Apakah ada cara yang lebih halus dan dapat diandalkan untuk mendeteksi klik ganda di Unity menggunakan C #, selain solusi ini, dan yang menangani masalah yang disebutkan di atas?


Hmm. Benar mendeteksi double-klik ... Saya hanya bisa berpikir tentang bagaimana saya akan pergi untuk melakukan hal itu, yang mungkin tidak kurang amatir.
Draco18 tidak lagi mempercayai SE

2
Saya tidak berpikir Anda dapat mendeteksi klik dengan lebih baik - Anda tidak dapat * memprediksi hasil tindakan fisik pengguna. Saya lebih suka fokus merancang GUI dan mekanik saya sehingga tindakan yang dipicu menyembunyikan fakta sebaik mungkin. * baik, secara teori Anda bisa, menerapkan beberapa perilaku penggunaan menganalisis AI, tetapi hasilnya tidak akan konsisten
wonderra

1
Mengacu pada jawaban 1, "Namun, solusi ini menghasilkan satu klik cepat terdeteksi sebelum klik dua kali terdeteksi, yang tidak diinginkan." Saya tidak benar-benar melihat apa yang Anda cari. Jika ada masalah dengan solusi ini, saya pikir itu adalah mekanisme permainan yang perlu diubah. Ambil RTS misalnya. Anda mengklik sebuah unit untuk memilihnya (tindakan A), klik dua kali untuk memilih semua unit lainnya (tindakan B). Jika Anda mengklik dua kali, Anda tidak hanya ingin tindakan B, Anda ingin A dan B. Jika Anda ingin sesuatu yang sama sekali berbeda terjadi, itu harus menjadi klik kanan, atau ctrl + klik, dll.
Peter

Jawaban:


13

Coroutine menyenangkan:

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

}

Ini sepertinya tidak mendeteksi ketika satu tombol ditekan; tepat ketika layar diklik atau diklik dua kali secara umum (meskipun OP tidak meminta itu, jadi tidak adil untuk bertanya begitu banyak). Adakah saran tentang bagaimana seseorang dapat melakukan itu?
zavtra

Saya tidak melihat bagaimana ini berbeda dari metode kedua yang diusulkan dalam pertanyaan.
John Hamilton

5

Saya tidak bisa membaca bahasa Inggris. Tetapi UniRx mungkin dapat membantu Anda.

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

Ini berfungsi, tetapi tidak persis seperti klik ganda diterapkan di Windows. Ketika Anda mengklik, misalnya, 6 kali berturut-turut, hanya satu klik ganda yang dihasilkan. Di Windows, itu akan menjadi 3 klik ganda. Anda dapat mencoba Control Panel → Mouse.
Maxim Kamalov

2

Penundaan klik ganda standar Windows adalah 500ms = 0,5 detik.

Secara umum dalam gim, lebih mudah menangani klik dua kali pada kontrol yang diklik, bukan secara global. Jika Anda memutuskan untuk menangani klik ganda secara global, Anda harus menerapkan solusi untuk menghindari 2 efek samping yang sangat tidak diinginkan:

  1. Setiap klik bisa ditunda 0,5 detik.
  2. Satu klik pada satu kontrol diikuti dengan cepat dengan satu klik pada kontrol lain dapat disalahartikan sebagai klik dua kali pada kontrol kedua.

Jika Anda harus menggunakan implementasi global, Anda harus melihat lokasi klik juga - jika mouse bergerak terlalu jauh itu bukan klik ganda. Anda biasanya akan menerapkan pelacak fokus yang mereset penghitung klik dua kali setiap kali fokus berubah.

Untuk pertanyaan apakah Anda harus menunggu hingga batas waktu klik dua kali berlalu, ini perlu ditangani secara berbeda untuk setiap kontrol. Anda memiliki 4 acara berbeda:

  1. Arahkan.
  2. Klik tunggal, yang mungkin menjadi klik ganda atau tidak.
  3. Klik dua kali.
  4. Timeout klik ganda, klik sekali dikonfirmasi untuk tidak menjadi klik ganda.

Jika memungkinkan, Anda mencoba mendesain interaksi GUI sedemikian rupa sehingga acara terakhir tidak akan digunakan: Windows File Explorer akan memilih item pada satu klik segera, dan akan membukanya pada klik ganda, dan tidak akan melakukan apa pun pada single yang dikonfirmasi klik.

Dengan kata lain: Anda menggunakan kedua opsi yang Anda sarankan, tergantung pada apa perilaku kontrol yang diinginkan.


2

Solusi di atas berfungsi untuk klik yang dilakukan di seluruh layar. Jika Anda ingin mendeteksi klik dua kali pada masing-masing tombol, saya menemukan modifikasi ini untuk jawaban di atas berfungsi dengan baik:

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

Lampirkan skrip ini ke tombol sebanyak yang Anda inginkan. Kemudian di bagian On Click editor (untuk setiap tombol yang Anda lampirkan ini), klik '+', tambahkan tombol itu sendiri sebagai objek game, dan kemudian pilih "DoubleClickTest -> startClick () 'sebagai fungsi untuk memanggil ketika tombol ditekan.


1

Saya sedang mencari penangan klik satu-dua yang tepat. Saya telah menemukan topik ini dan mengumpulkan beberapa ide yang sangat bagus. Jadi akhirnya saya datang dengan solusi berikut dan saya ingin membaginya dengan Anda. Saya harap Anda menemukan metode ini bermanfaat.

Saya pikir ini adalah pendekatan yang bagus untuk menggunakan inspektur Unity, karena dengan cara ini kode Anda lebih fleksibel karena Anda dapat mereferensikan objek permainan Anda secara visual dan aman.

Pertama-tama tambahkan komponen Sistem Acara ke salah satu objek game Anda. Ini juga akan menambahkan komponen Modul Input Mandiri . Ini akan menangani acara input seperti klik dan sentuhan mouse. Saya merekomendasikan untuk melakukan ini pada objek game individual.

Selanjutnya tambahkan komponen Event Trigger ke salah satu objek game yang diaktifkan raycast Anda . Seperti elemen UI atau sprite, objek 3D dengan collider . Ini akan mengirimkan acara klik ke skrip kami. Tambahkan Pointer Click event type dan atur objek game penerima yang memiliki komponen skrip Click Controller di atasnya dan akhirnya pilih metode onClick () -nya . (Anda juga dapat mengubah skrip untuk menerima semua klik dalam Pembaruan () dan lewati langkah ini.)

Jadi objek game penerima, yang tentu saja dapat menjadi pemicu acara, akan melakukan apa saja dengan peristiwa klik tunggal atau doudle.

Anda dapat mengatur batas waktu untuk klik dua kali, tombol yang diinginkan dan acara khusus untuk klik tunggal dan ganda. Satu-satunya batasan adalah Anda hanya dapat memilih satu tombol pada satu waktu untuk satu objek game .

masukkan deskripsi gambar di sini

Script itu sendiri memulai coroutine pada setiap klik pertama dengan dua timer. The FirstClickTime dan currentTime di sinkronkan dengan yang pertama.

Coroutine ini berputar sekali pada akhir setiap frame hingga batas waktu berakhir. Sementara itu metode onClick () terus menghitung klik.

Ketika batas waktu kedaluwarsa itu memanggil acara satu atau dua kali klik sesuai dengan clickCounter. Kemudian ia menetapkan penghitung ini ke nol sehingga coroutine dapat selesai dan seluruh proses dimulai dari awal.

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

Baiklah, posting pertama saya harus sedikit lebih informatif. Jadi saya menambahkan deskripsi rinci dan sedikit memperbaiki skrip. Hapus -1 Anda jika Anda menyukainya.
Norb

0

Saya pikir tidak ada cara untuk membaca pikiran pengguna baik dia akan mengklik tunggal atau ganda, setelah semua kita harus menunggu input. Meskipun Anda dapat mengatur menunggu 0,01 detik (serendah) untuk klik kedua. Dalam hal ini saya akan memilih opsi menunggu.

Dan YA Coroutinesmenyenangkan ...

Sebuah alternatif

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;
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.