วิธีแยกความแตกต่างคลิกครั้งเดียวและดับเบิลคลิกอย่างถูกต้องใน Unity3D โดยใช้ C #


15

สิ่งที่ฉันพยายามที่จะเข้าใจและเรียนรู้ที่นี่เป็นสิ่งที่เป็นพื้นฐาน: สิ่งที่เป็นวิธีทั่วไปมากที่สุดและมากที่สุดในการขัดถูกต้องความแตกต่างคลิกเดียวและสองคลิกสำหรับ 3 ปุ่มเมาส์หลักในความสามัคคีโดยใช้ 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 # นอกเหนือจากวิธีแก้ปัญหาเหล่านี้และวิธีที่จัดการปัญหาดังกล่าวข้างต้นหรือไม่


อืมมม ตรวจจับการคลิกสองครั้งอย่างถูกต้อง ... ฉันสามารถคิดได้ว่าฉันจะทำอย่างไรซึ่งอาจไม่ใช่เรื่องที่ไม่ชำนาญ
Draco18 ไม่ไว้ใจ SE

2
ฉันไม่คิดว่าคุณจะสามารถตรวจพบการคลิกได้ดีขึ้น - คุณไม่สามารถ * คาดการณ์ผลลัพธ์ของการกระทำทางกายภาพของผู้ใช้ได้ ฉันอยากจะมุ่งเน้นไปที่การออกแบบ GUI และกลไกของฉันเพื่อที่การกระทำที่ถูกกระตุ้นจะซ่อนความจริงที่ดีที่สุดเท่าที่จะทำได้ * ตามทฤษฎีแล้วคุณสามารถใช้การวิเคราะห์ AI ในการใช้งานได้ แต่ผลลัพธ์จะไม่สอดคล้องกัน
wondra

1
อ้างถึงคำตอบ 1, "อย่างไรก็ตามวิธีการแก้ปัญหานี้ส่งผลให้มีการตรวจพบการคลิกครั้งเดียวอย่างรวดเร็วก่อนที่จะตรวจพบดับเบิลคลิกซึ่งไม่พึงประสงค์" ฉันไม่เห็นสิ่งที่คุณกำลังมองหา หากมีปัญหากับวิธีนี้ฉันคิดว่าเป็นกลไกของเกมที่ต้องมีการปรับแต่ง ใช้ RTS เป็นตัวอย่าง คุณคลิกหน่วยเพื่อเลือก (การกระทำ A) ดับเบิลคลิกเพื่อเลือกหน่วยอื่นทั้งหมด (การกระทำ B) หากคุณดับเบิลคลิกคุณไม่ต้องการเพียงแค่การกระทำ B คุณต้องการทั้ง A และ B หากคุณต้องการบางสิ่งที่แตกต่างอย่างสิ้นเชิงที่เกิดขึ้นมันควรจะเป็นการคลิกขวาหรือ ctrl + คลิก ฯลฯ
Peter

คำตอบ:


13

Coroutines สนุกมาก:

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

}

ดูเหมือนว่านี่จะตรวจไม่พบเมื่อกดปุ่มแต่ละปุ่ม เมื่อหน้าจอถูกคลิกหรือคลิกสองครั้งโดยทั่วไป (แม้ว่า OP ไม่ได้ร้องขอดังนั้นจึงไม่ยุติธรรมที่จะถามมาก) ข้อเสนอแนะใด ๆ เกี่ยวกับวิธีการทำเช่นนั้น?
zavtra

ฉันไม่เห็นว่าวิธีนี้แตกต่างจากวิธีที่สองที่เสนอในคำถามอย่างไร
จอห์นแฮมิลตัน

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 จะเป็นการคลิกสองครั้ง Control Panel → Mouseคุณสามารถลองใน
Maxim Kamalov

2

การหน่วงเวลาการดับเบิลคลิกเริ่มต้นของ Windows คือ 500ms = 0.5 วินาที

โดยทั่วไปในเกมมันง่ายกว่ามากในการจัดการดับเบิลคลิกบนตัวควบคุมที่ถูกคลิกไม่ใช่ทั่วโลก หากคุณตัดสินใจที่จะจัดการกับการคลิกสองครั้งทั่วโลกคุณต้องดำเนินการแก้ไขเพื่อหลีกเลี่ยงผลข้างเคียงที่ไม่พึงประสงค์ 2 ประการ:

  1. การคลิกแต่ละครั้งอาจล่าช้าได้ 0.5 วินาที
  2. การคลิกเพียงครั้งเดียวบนตัวควบคุมอย่างใดอย่างหนึ่งตามมาด้วยการคลิกเพียงครั้งเดียวบนตัวควบคุมอื่นอาจตีความได้ว่าเป็นการดับเบิลคลิกที่ตัวควบคุมที่สอง

หากคุณต้องใช้การใช้งานทั่วโลกคุณจะต้องดูที่ตั้งของการคลิกเช่นกัน - หากเมาส์เคลื่อนไปไกลเกินไปไม่ใช่การดับเบิลคลิก โดยปกติแล้วคุณจะใช้ตัวติดตามโฟกัสที่รีเซ็ตตัวนับการคลิกสองครั้งเมื่อใดก็ตามที่การเปลี่ยนแปลงโฟกัส

สำหรับคำถามที่คุณควรรอจนกว่าการหมดเวลาดับเบิลคลิกจะต้องได้รับการจัดการที่แตกต่างกันสำหรับแต่ละตัวควบคุม คุณมี 4 กิจกรรมที่แตกต่าง:

  1. โฉบ
  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");
    }
}

แนบสคริปต์นี้กับปุ่มได้มากเท่าที่คุณต้องการ จากนั้นในส่วน On Click ของเครื่องมือแก้ไข (สำหรับแต่ละปุ่มที่คุณแนบไว้) คลิก '+' เพิ่มปุ่มไปยังตัวเองเป็นวัตถุเกมแล้วเลือก "DoubleClickTest -> startClick () 'เป็นฟังก์ชันในการโทร เมื่อกดปุ่ม


1

ฉันกำลังมองหาตัวจัดการการคลิกสองครั้งที่เหมาะสม ฉันได้พบหัวข้อนี้และรวบรวมความคิดที่ดีจริงๆ ในที่สุดฉันก็มาพร้อมกับวิธีแก้ไขปัญหาต่อไปนี้และฉันต้องการแบ่งปันกับคุณ ฉันหวังว่าคุณจะพบวิธีนี้มีประโยชน์

ฉันคิดว่ามันเป็นวิธีการที่ยอดเยี่ยมในการใช้ตัวตรวจสอบของ Unity เพราะวิธีนี้โค้ดของคุณมีความยืดหยุ่นมากขึ้นเนื่องจากคุณสามารถอ้างอิงวัตถุเกมใด ๆ ของคุณได้ด้วยตาและปลอดภัย

ก่อนอื่นให้เพิ่มองค์ประกอบระบบเหตุการณ์ลงในออบเจ็กต์เกมของคุณ สิ่งนี้จะเพิ่มคอมโพเนนต์ของโมดูลอินพุตแบบสแตนด์อโลน สิ่งนี้จะดูแลกิจกรรมอินพุตเช่นการคลิกเมาส์และสัมผัส ฉันขอแนะนำให้ทำสิ่งนี้กับวัตถุแต่ละเกม

จากนั้นเพิ่มองค์ประกอบEvent Triggerไปยังวัตถุเกมใด ๆ ที่เปิดใช้งาน raycastของคุณ เช่นเดียวกับองค์ประกอบ UI หรือเทพดาวัตถุ 3 มิติด้วย Collider สิ่งนี้จะส่งเหตุการณ์การคลิกไปที่สคริปต์ของเรา เพิ่มประเภทเหตุการณ์ Pointer Clickและตั้งค่าวัตถุเกมตัวรับซึ่งมีองค์ประกอบสคริปต์Click Controllerอยู่และสุดท้ายเลือกวิธีonClick () (นอกจากนี้คุณสามารถแก้ไขสคริปต์เพื่อรับการคลิกทั้งหมดในอัปเดต () และข้ามขั้นตอนนี้)

ดังนั้นวัตถุเกมตัวรับซึ่งแน่นอนว่าสามารถเป็นหนึ่งเดียวกับตัวเรียกเหตุการณ์จะทำทุกอย่างด้วยการคลิกครั้งเดียว

คุณสามารถตั้งค่าจำกัด เวลาสำหรับการดับเบิลคลิกปุ่มที่ต้องการและเหตุการณ์ที่กำหนดเองสำหรับการคลิกครั้งเดียวและดับเบิล เพียงข้อ จำกัดคือการที่คุณสามารถเลือกได้เพียงปุ่มเดียวในเวลาสำหรับวัตถุหนึ่งเกม

ป้อนคำอธิบายรูปภาพที่นี่

สคริปต์เริ่มต้น coroutine ในการคลิกครั้งแรกทุกครั้งที่มีสองตัวนับ คลิกครั้งแรกและเวลาปัจจุบันซิงค์กับครั้งแรก

coroutine นี้ลูปหนึ่งครั้งในตอนท้ายของทุกเฟรมจนหมดเวลา ในขณะเดียวกันวิธีการ onClick () ยังคงนับจำนวนคลิกต่อไป

เมื่อหมดเวลาที่กำหนดจะเรียกเหตุการณ์เดียวหรือสองครั้งตาม clickCounter จากนั้นจะตั้งค่าตัวนับนี้เป็นศูนย์เพื่อให้ coroutine สามารถเสร็จสิ้นและกระบวนการทั้งหมดเริ่มต้นตั้งแต่ต้น

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 ของคุณถ้าคุณชอบ
Norb

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;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.