C#を使用してUnity3Dでシングルクリックとダブルクリックを適切に区別する方法は?


15

ここで理解し、学習しようとしているのは非常に基本的なことです:C#を使用して、Unityの3つのメインマウスボタンのシングルクリックとダブルクリックを適切に区別する最も一般的で洗練された方法は何ですか?

言葉を適切に強調することは、何の役にも立ちません。このウェブサイトでこれが一度も聞かれたことがないことに驚きましたが、もちろん、UnityのフォーラムやUnityの質問/回答ページで同様の質問を数多く見つけました。ただし、これらのリソースに回答が投稿される場合が多いため、それらはすべて明らかに間違っているか、少なくとも少しアマチュアすぎるように見えました。

説明させてください。提案されたソリューションには、常に2つのアプローチと対応する欠陥のいずれかがありました。

  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. アクティブ化されるシングルクリックの待機遅延を設定します。つまり、クリックごとに、最後のクリックからの遅延時間が所定のしきい値を超える場合にのみ、シングルクリックアクションをアクティブにします。ただし、シングルクリックが検出される前に待機に気づくことができるため、これは結果が遅くなります。さらに悪いことに、この種のソリューションは、OSレベルでのユーザーのダブルクリック速度設定の速度や速度を考慮していないため、マシン間の不一致に悩まされています。

大まかな例:

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

   }

したがって、私の質問は次のようになります。これらのソリューション以外に、C#を使用してUnityでダブルクリックを検出するより洗練された信頼性の高い方法と、前述の問題を処理する方法はありますか?


うーん。ダブルクリックを適切に検出しています...私はそれをどうやってやろうと思うかしか考えられませんが、おそらくそれはそれほど素人ではありません。
Draco18sは、

2
検出できるとは思わないクリックをより良くない-ユーザーの身体的行動の結果を予測することはできません*。むしろ、トリガーされたアクションができる限り事実を隠すように、GUIとメカニクスの設計に集中します。* まあ、理論的には、いくつかのAI分析の使用動作を実装できますが、結果は一貫していません
-wondra

1
回答1を参照すると、「ただし、このソリューションでは、ダブルクリックが検出される前にクイックシングルクリックが検出されるため、望ましくありません。」あなたが探しているものが本当に見当たりません。このソリューションに問題がある場合、調整が必要なのはゲームの仕組みだと思います。RTSを例にとります。ユニットをクリックして選択し(アクションA)、ダブルクリックして他のすべてのユニットを選択します(アクションB)。あなたは、ダブルクリック、あなただけのアクション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");
}

}

これは、個々のボタンが押されたことを検出しないようです。通常、画面がクリックまたはダブルクリックされたとき(OPはそれを要求しなかったので、あまり質問するのは公平ではありません)。どのようにそれを行うことができますか?
ザヴトラ

これが質問で提案された2番目の方法とどのように異なるかわかりません。
ジョンハミルトン

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回クリックすると、ダブルクリックが1回だけ発生します。Windowsでは、3回のダブルクリックになります。で試すことができControl Panel → Mouseます。
マキシムカマロフ

2

Windowsのデフォルトのダブルクリック遅延は500ms = 0.5秒です。

一般に、ゲームでは、グローバルではなく、クリックされているコントロールのダブルクリックを処理する方がはるかに簡単です。ダブルクリックをグローバルに処理することにした場合、2つの非常に望ましくない副作用を回避するための回避策を実装する必要があります。

  1. すべてのクリックは0.5秒遅れる可能性があります。
  2. 1つのコントロールを1回クリックしてすぐに別のコントロールを1回クリックすると、2番目のコントロールをダブルクリックすると誤解される可能性があります。

グローバル実装を使用する必要がある場合は、クリックの場所も確認する必要があります-マウスがあまりにも遠くに移動した場合、ダブルクリックではありません。通常、フォーカスが変更されるたびにダブルクリックカウンターをリセットするフォーカストラッカーを実装します。

ダブルクリックのタイムアウトが経過するまで待つ必要があるかどうかという質問に、これはコントロールごとに異なる方法で処理する必要があります。4つの異なるイベントがあります。

  1. ホバー。
  2. シングルクリック。ダブルクリックになる場合としない場合があります。
  3. ダブルクリック。
  4. ダブルクリックのタイムアウト、シングルクリックはダブルクリックではないことが確認されました。

可能な限り、最後のイベントが使用されないようにGUIの相互作用を設計しようとします。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");
    }
}

このスクリプトを必要な数のボタンに添付します。次に、エディターの[クリック時]セクション(これをアタッチする各ボタン)で[+]をクリックし、ボタンをゲームオブジェクトとして自分自身に追加してから、呼び出す関数として[DoubleClickTest-> startClick()]を選択します。ボタンが押されたとき。


1

適切なシングルダブルクリックハンドラーを探していました。このトピックを見つけて、いくつかの本当に良いアイデアを集めました。それでようやく次のソリューションを思いついたので、それをあなたと共有したいと思います。この方法がお役に立てば幸いです。

Unityのインスペクターを使用するのは素晴らしい方法だと思います。なぜなら、この方法では、ゲームオブジェクトを視覚的かつ安全に参照できるため、コードがより柔軟になるからです。

最初に、イベントシステムコンポーネントを任意のゲームオブジェクトに追加します。これにより、スタンドアロン入力モジュールコンポーネントも追加されます。これにより、マウスクリックやタッチなどの入力イベントが処理されます。個々のゲームオブジェクトでこれを行うことをお勧めします。

次に、イベントトリガーコンポーネントをレイキャスト対応のゲームオブジェクトのいずれかに追加します。UI要素またはスプライトのように、コライダーを持つ 3Dオブジェクト。これにより、クリックイベントがスクリプトに配信されます。Pointer Clickイベントタイプを追加し、Click Controllerスクリプトコンポーネントを含むレシーバーゲームオブジェクトを設定し、最後にそのonClick()を選択しますメソッドを。(また、Update()ですべてのクリックを受け取るようにスクリプトを変更し、この手順をスキップすることもできます。)

したがって、レシーバーゲームオブジェクトは、もちろんイベントトリガーを持つものである可能性がありますが、シングルまたはダドルクリックイベントで何でもします。

ダブルクリックの時間制限、目的のボタン、およびシングルクリックとダブルクリックのカスタムイベントを設定できます。唯一の制限は、1つのゲームオブジェクトに対して一に1 つのボタンしか選択できないことです。

ここに画像の説明を入力してください

スクリプト自体は、2つのタイマーで最初のクリックごとにコルーチンを開始します。firstClickTimeとcurrentTimeは、最初と同期しています。

このコルーチンは、時間制限が切れるまで、すべてのフレームの終わりに1回ループします。その間、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

入力を待つ必要があるため、ユーザーがシングルまたはダブルをクリックするかどうかを判断する方法はないと思います。ただし、2回目のクリックに対して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.