unity3D 游戲物體同時綁定單擊、雙擊事件


  前言

  在unity中我們常用的獲取鼠標點擊的方法有:

 

  1、在3D場景中,一般用在Update方法中,每一幀調用

void Update(){
    if(Input.GetMouseButtonDown(0)){
        Debug.log("鼠標左鍵點擊");  
    }
}

  2、在畫布場景中,一般用在OnGUI方法中,這個也是一個循環調用的方法,這個方法在3D場景也可以觸發點擊事件

    void OnGUI()
    {
        Event e = Event.current;
        if (e.isMouse && (e.clickCount == 2))
        {
            Debug.Log("用戶雙擊了鼠標");
        }
    }

 

  當我們想同時給一個游戲對象綁定單擊、雙擊事件是就會一個問題,雙擊事件必然會觸發至少一次單擊事件,不管你點的有多快,因此我們需要引入定時器來解決這個問題

 

  定時器介紹

 

  MonoBehaviour簡單介紹

using System;
using System.Collections;
using System.Runtime.CompilerServices;
using UnityEngine.Bindings;
using UnityEngine.Internal;
using UnityEngine.Scripting;

namespace UnityEngine
{
  //MonoBehaviour是每個Unity腳本派生的基類
  public class MonoBehaviour : Behaviour
  {
    //檢查當前是否有定時器
    public bool IsInvoking()

    //取消所有定時器調用
    public void CancelInvoke()

    //在time秒后,調用方法名為methodName的方法
    public void Invoke(string methodName, float time)

    //在time秒后,調用方法名為methodName的方法,然后每repeatRate秒重復一次
    public void InvokeRepeating(string methodName, float time, float repeatRate)

    //取消方法名為methodName的定時器調用
    public void CancelInvoke(string methodName)

    //檢查在方法名為methodName上是否有定時器調用 
    public bool IsInvoking(string methodName)
    
    //其他的介紹省略...
  }
}

  MonoBehaviour是每個Unity腳本派生的基類,只要腳本引入了UnityEngine可以直接使用

 

  前面也有用System.Timers.Timer來實現,定時器也能正常觸發,但有一個問題,在定時函數中,我無法訪問gameObject,但是可以訪問到我們的兩個標識,很奇怪,如果有在函數中調用到gameObject等其他屬性,程序也不打印報錯信息,腳本直接終止,再點擊對象已經沒有反應,后面通過打斷點調試發現,訪問這些屬性將會產生一個異常:Exception of type System.NotSupportedException,因此放棄使用這個定時器

 

  思路

 

  1、當觸發點擊,且點擊對象為當前綁定腳本的對象才繼續往下執行

  2、將單、雙擊標識設置取反,當前為false

  3、判斷是否為新一輪

  4、觸發定時器,在300毫秒后執行定時調用函數,同時鎖定本次判斷,再本次判斷沒結束之前不會觸發定時器

  5、在函數里進行單、雙擊的判斷(false單擊、true雙擊),同時重置標識,開啟下一輪

 

  那么在這300毫秒的時間里,如果我們再次點擊將會執行到第二步,單、雙擊標識將會被設置成true,則定時調用函數的if分支就會走雙擊

 

  隱藏bug

  那么問題來了,如果有人手速非常快,他在300毫秒內點了好幾下那豈不是會有問題?如果他點了兩下,那定時調用函數的if分支又會走單擊....

  這種情況下只能設置一個合適的觸發時間來解決了

 

 

  最終腳本、效果

 

  C#腳本

using UnityEngine;

/**
 * 鼠標點擊事件綁定
 */
public class Click : MonoBehaviour
{
    private Ray _ray;//物理射線相關

    public RaycastHit _hit;//物理射線相關

    private bool _first = true;//新一輪標識(或者也可以叫是否結束的標識)
    
    private bool _flag = true;//單擊或雙擊的標識(默認單擊)

    private void Update()
    {
        monitor();
    }

    /**
     * 鼠標單、雙擊監聽
     */
    private void monitor()
    {
        //觸發鼠標左鍵點擊
        if (!Input.GetMouseButtonDown(0)) return;
        
        //射線檢測到的對象是當前對象
        if (Camera.main != null) _ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (!Physics.Raycast(_ray, out _hit) || _hit.collider.gameObject != gameObject) return;
        
        _flag = !_flag;

        //上一次的事件是否已經執行完畢,也就是判斷是否為新一輪
        if (!_first) return;
        
        _first = false;
                  
        //初始化定時器,300毫秒后執行預定方法
        Invoke("Timer", 0.3f);
    }
    
    /**
     * 定時調用函數
     */
    private void Timer()
    {
        //進行判斷
        if (_flag)
        {
            OnDblclick();
        }
        else
        {
            OnClick();
        }
        
        //定時調用結束,重置標識
        _first = true;
        _flag = true;
    }

    /**
     * 單擊事件
     */
    private void OnClick()
    {
        Debug.Log(gameObject.name + "單擊事件被觸發");
    }

    /**
     * 雙擊事件
     */
    private void OnDblclick()
    {
        Debug.Log(gameObject.name + "雙擊事件被觸發");
    }
}

  把腳本綁定在具體的游戲對象即可,要注意的是,用物理射線檢測是否點擊的是當前對象,這個需要對象本身有Collider碰撞體組件,因為射線是與對象的碰撞體發生碰撞

 

   效果演示

  上圖的鼠標操作流程:單擊,雙擊,單擊,雙擊,雙擊,單擊;(具體打印情況看控制台右邊的打印次數)

 

  更新腳本

  2020-05-15更新

  更新一下腳本,之前是一個腳本只能綁定一個對像,因為事件處理時直接寫在腳本里的,現在改一下,改成事件處理需要傳進來UnityEvent,這樣一來綁定事件就更加靈活了

    using UnityEngine.Events;
    using UnityEngine;

    /**
     * 鼠標點擊事件綁定,利用射線檢測碰撞,需要對象本身有Collider碰撞體組件
     */
    public class Click : MonoBehaviour
    {
        private Ray _ray;//物理射線相關

        private RaycastHit _hit;//物理射線相關

        private bool _first = true;//新一輪標識(或者也可以叫是否結束的標識)
    
        private bool _flag = true;//單擊或雙擊的標識(默認單擊)
        
        public UnityEvent OnClickListener; //單擊事件監聽
        
        public UnityEvent OnDblclickListener; //雙擊事件監聽


        private void Update()
        {
            monitor();
        }

        /**
         * 鼠標單、雙擊監聽
         */
        private void monitor()
        {
            //觸發鼠標左鍵點擊
            if (!Input.GetMouseButtonDown(0)) return;
        
            //射線檢測到的對象是當前對象
            if (Camera.main != null) _ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (!Physics.Raycast(_ray, out _hit) || _hit.collider.gameObject != gameObject) return;
        
            _flag = !_flag;

            //上一次的事件是否已經執行完畢,也就是判斷是否為新一輪
            if (!_first) return;
        
            _first = false;
                  
            //初始化定時器,300毫秒后執行預定方法
            Invoke("Timer", 0.3f);
        }
    
        /**
         * 定時調用函數
         */
        private void Timer()
        {
            //進行判斷
            if (_flag)
            {
                OnDblclickListener.Invoke();
            }
            else
            {
                OnClickListener.Invoke();
            }
        
            //定時調用結束,重置標識
            _first = true;
            _flag = true;
        }
    }
View Code

  如何使用

        //添加Click組件
        Click gameObjectClick = gameObject.AddComponent<Click>();
        
        //綁定單擊事件
        gameObjectClick.OnClickListener = new UnityEvent();
        gameObjectClick.OnClickListener.AddListener(() =>
        {
            Debug.Log("單擊獲取對象名稱:"+gameObject.name);
        });

       //綁定雙擊事件
        gameObjectClick.OnDblclickListener= new UnityEvent();
        gameObjectClick.OnDblclickListener.AddListener(() =>
        {
            Debug.Log("雙擊獲取對象名稱:"+gameObject.name);
        });

 

 

  后記

  unity3D 游戲物體同時綁定單擊、雙擊事件暫時記錄到這,后續還可以進一步封裝,使游戲對象綁定單、雙擊更加簡單

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM