《圖說VR》——HTC Vive控制器按鍵事件解耦使用


本文章由cartzhang編寫,轉載請註明出處。 所有權利保留。
文章鏈接:http://blog.csdn.net/cartzhang/article/details/53915229
作者:cartzhang

Unity的 Steam VR插件本身也帶有事件處理。但是我還想把事件給解耦出來,這樣方便在各個項目中,不用關心硬件的各種處理而只用關心使用的,且可以任意的通過接受事件來觸發相應的操作。

項目的參考圖片可下載地址:https://github.com/cartzhang/ImgSayVRabc/tree/master/ViveEventDemo/Img

今天我們說談論的就是下面這個東西:

這裏寫圖片描述
圖1.1

一、所需資源


所需資源,很少。
需要用Steam VR插件 ,可以從Untiy商店下載。當然你可以使用文章後面給出本工程的導出包,文章後面有下載地址:

這裏寫圖片描述
圖0

但是電腦還是需要安裝steam的,這個暫時還是需要翻牆的。你懂的,翻牆是一項技能。
安裝後:

這裏寫圖片描述
圖4
點擊右上角的VR字樣,鏈接你的Vive設備,然後就可以看到他們的狀態了。
這裏設備的各種設置方法和使用就不逐個說明講解了。網上搜索下吧,或去官方最正宗的。

這裏寫圖片描述
圖6

當然也可以使用桌面的快捷方式,當然前提是你有:

這裏寫圖片描述
圖5

二、製作Demo


首先,打開Unity 導入插件

這裏寫圖片描述
圖1

這裏寫圖片描述
圖2

然後,可以打開其給點樣例來看看:

這裏寫圖片描述
圖3

接着就是,添加代碼,給Controller添加控制代碼:

這裏寫圖片描述
圖7


再然後就是需要自己寫代碼。

三、消息解耦


先說下,這個代碼來自於其他同事,我基本沒太多修改。但是確實很好用,非常感謝!!若有問題,請及時告知。


消息發送機制:


namespace SLQJ
{
    /// <summary>
    /// 消息分發,解耦
    /// </summary>
    public class NotificationManager
    {
        public static NotificationManager Instance { get { return SingletonProvider<NotificationManager>.Instance; } }

        public delegate void MsgCallback(MessageObject eb);
        /// <summary>
        /// 回調隊列
        /// </summary>
        private Dictionary<string, List<MsgCallback>> registedCallbacks = new Dictionary<string, List<MsgCallback>>();
        /// <summary>
        /// 延遲消息隊列
        /// </summary>
        private readonly List<MessageObject> delayedNotifyMsgs = new List<MessageObject>();
        /// <summary>
        /// 主消息隊列
        /// </summary>
        private readonly List<MessageObject> realCallbacks = new List<MessageObject>();
        private static bool isInCalling = false;

        public  void Init()
        {

        }

        public void Update()
        {
            lock (this)
            {
                if (realCallbacks.Count == 0)
                {
                    //主消息隊列處理完時,加入延時消息到主消息列表 
                    foreach (MessageObject eb in delayedNotifyMsgs)
                    {
                        realCallbacks.Add(eb);
                    }
                    delayedNotifyMsgs.Clear();
                    return;
                }
                //調用主消息處理隊列
                isInCalling = true;
                foreach (MessageObject eb in realCallbacks)
                {
                    if (registedCallbacks.ContainsKey(eb.MsgName))
                    {
                        for (int i = 0; i < registedCallbacks[eb.MsgName].Count; i++)
                        {
                            MsgCallback ecb = registedCallbacks[eb.MsgName][i];
                            if (ecb == null)
                            {
                                continue;
                            }
#if UNITY_EDITOR
                            ecb(eb);
#else
                            try
                            {
                                 ecb(eb);
                            }
                            catch (Exception e)
                            {
                                Debug.LogError("CallbackError:" + eb.MsgName + " : " + e.ToString());
                            }    
#endif
                        }
                    }
                    else
                    {
                        Debug.Log("MSG_ALREADY_DELETED:" + eb.MsgName);
                    }

                }
                realCallbacks.Clear();
            }
            isInCalling = false;
        }

        public void Reset()
        {
            Dictionary<string, List<MsgCallback>> systemMsg = new Dictionary<string, List<MsgCallback>>();
            foreach (KeyValuePair<string, List<MsgCallback>> item in this.registedCallbacks)
            {
                if (item.Key.StartsWith("_"))
                {
                    systemMsg.Add(item.Key, item.Value);
                }
            }
            this.registedCallbacks = systemMsg;
        }

        public void Destroy()
        {
            Reset();
        }

        /// <summary>
        /// 訂閱消息
        /// </summary>
        /// <param name="msgName"></param>
        /// <param name="msgCallback"></param>
        public void Subscribe(string msgName, MsgCallback msgCallback)
        {
            lock (this)
            {
                if (!registedCallbacks.ContainsKey(msgName))
                {
                    registedCallbacks.Add(msgName, new List<MsgCallback>());
                }
                {
                    //防止重複訂閱消息回調
                    List<MsgCallback> list = registedCallbacks[msgName];
                    for (int i = 0; i < list.Count; i++)
                    {
                        if (list[i].Equals(msgCallback))
                        {
                            return;
                        }
                    }
                    list.Add(msgCallback);
                }

            }
        }
        /// <summary>
        /// 取消訂閱
        /// </summary>
        /// <param name="msgName"></param>
        /// <param name="msgCallback"></param>
        public void UnSubscribe(string msgName, MsgCallback msgCallback)
        {
            lock (this)
            {
                if (!registedCallbacks.ContainsKey(msgName))
                {
                    return;
                }
                //Debug.Log(msgName + ":-s-" + registedCallbacks[msgName].Count);
                registedCallbacks[msgName].Remove(msgCallback);
                //Debug.Log(msgName + ":-e-" + registedCallbacks[msgName].Count);
            }
        }

        public void PrintMsg()
        {
            string content = "";
            foreach (KeyValuePair<string, List<MsgCallback>> registedCallback in registedCallbacks)
            {
                int total = registedCallback.Value.Count;
                if (total > 0)
                {
                    content += registedCallback.Key + ":" + total + "\n";
                    for (int i = 0; i < total; i++)
                    {
                        content += "\t" + registedCallback.Value[i].Method.Name + "--" + registedCallback.Value[i].Target + "\n";
                    }
                }
            }
        }

        /// <summary>
        /// 派發消息
        /// </summary>
        /// <param name="MsgName"></param>
        /// <param name="MsgParam"></param>
        public void Notify(string MsgName, params object[] MsgParam)
        {

            object msgValueParam = null;
            if (MsgParam != null)
            {
                if (MsgParam.Length == 1)
                {
                    msgValueParam = MsgParam[0];
                }
                else
                {
                    msgValueParam = MsgParam;
                }
            }


            lock (this)
            {
                if (!registedCallbacks.ContainsKey(MsgName))
                {
                    return;
                }
                if (isInCalling)
                {
                    delayedNotifyMsgs.Add(new MessageObject(MsgName, msgValueParam));
                }
                else
                {
                    realCallbacks.Add(new MessageObject(MsgName, msgValueParam));
                }
            }
        }
    }

    public class MessageObject
    {
        public object MsgValue;
        public string MsgName;

        public MessageObject()
        {
            MsgName = this.GetType().FullName;
        }

        public MessageObject(string msgName, object ev)
        {
            MsgValue = ev;
            MsgName = msgName;
        }
    }
}

你可以看到原著者寫的還是很嚴謹的,使用消息隊列來實現的,然後在unity某組件的Update中實現輪詢調用。

先看看這個消息機制的啓動,特別簡單:

public class main : MonoBehaviour {

    // Use this for initialization
    void Awake ()
    {
        NotificationManager.Instance.Init();
    }

    // Update is called once per frame
    void Update ()
    {
        NotificationManager.Instance.Update();
    }
}


與上面說的一模一樣,初始化,然後update。

至於說機制怎麼用,這個在後面會接實戰給出。

四、手柄Controller消息觸發


手柄的事件很多,我就撿了幾個常用的來做個例子來說明問題,若需要,你們自己可以來添加自己的需要。


/// <summary>
/// 可以自定義添加事件,然後實現消息的傳遞。
/// </summary>
// 實現手柄的案件事件功能
public class ViveEvent : MonoBehaviour
{
    void Start()
    {
        var trackedController = GetComponent<SteamVR_TrackedController>();
        if (trackedController == null)
        {
            trackedController = gameObject.AddComponent<SteamVR_TrackedController>();
        }

        trackedController.TriggerClicked += new ClickedEventHandler(OnTriggerClicked);
        trackedController.TriggerPressDown += new ClickedEventHandler(OnTriggerPressDn);
        trackedController.TriggerUnclicked += new ClickedEventHandler(OnTriggerUnclicked);

        trackedController.PadClicked += new ClickedEventHandler(OnPadClicked);
        trackedController.PadUnclicked += new ClickedEventHandler(OnPadUnclicked);
    }

    void OnTriggerClicked(object sender, ClickedEventArgs e)
    {
        Debug.Log(e.controllerIndex + "trigger clicked");
        // 開火
        NotificationManager.Instance.Notify(NotificationType.Gun_Fire.ToString());
    }

    void OnTriggerPressDn(object sender, ClickedEventArgs e)
    {
        Debug.Log(e.controllerIndex + "trigger press down");
        // 
        NotificationManager.Instance.Notify(NotificationType.Gathering_Stength.ToString());
    }

    void OnTriggerUnclicked(object sender, ClickedEventArgs e)
    {
        Debug.Log(e.controllerIndex + "trigger  unclicked");
        NotificationManager.Instance.Notify(NotificationType.Gun_KeyUp.ToString());
    }

    void OnPadClicked(object sender, ClickedEventArgs e)
    {
        // 扔雷
        NotificationManager.Instance.Notify(NotificationType.Throw_Bomb.ToString());
        Debug.Log(e.controllerIndex + "pad clicked");
    }

    void OnPadUnclicked(object sender, ClickedEventArgs e)
    {
        Debug.Log(e.controllerIndex + "padd  un clicked");
    }
}


主要寫了按鍵Trigger 按下,按住和彈起和Pad的按下和彈起事件。

然後是觸發事件的接受,這裏就體現瞭解耦事件的好處。這裏真的不止於使用在vive按鍵處理這裏。


public class ControlButtonAns : MonoBehaviour
{

    // Use this for initialization
    void Start()
    {
        NotificationManager.Instance.Subscribe(NotificationType.Gun_Fire.ToString(), GunFire);
        NotificationManager.Instance.Subscribe(NotificationType.Gathering_Stength.ToString(), GatheringStength);
        NotificationManager.Instance.Subscribe(NotificationType.Throw_Bomb.ToString(), ThrowBomb);
        NotificationManager.Instance.Subscribe(NotificationType.Gun_KeyUp.ToString(), GunKeyUp);
    }

    void GunFire(MessageObject obj)
    {
        Debug.Log("response gun fire , trigger button click");
    }

    void GatheringStength(MessageObject obj)
    {
        Debug.Log("response gathering stength, trigger button hold");
    }

    void GunKeyUp(MessageObject obj)
    {
        Debug.Log("response key up, trigger button unclicked");
    }

    void ThrowBomb(MessageObject obj)
    {
        Debug.Log("response throw bomb , pad button click");
    }
}


這個就根據個人的需要來添加自己的代碼。這裏僅僅是舉例說明。

代碼寫完了,添加吧!!

手柄contorller接受事件:

這裏寫圖片描述
圖7.1

消息觸發解耦代碼:

這裏寫圖片描述
圖7.2

相應消息腳本:

這裏寫圖片描述
圖7.3

這樣基本就搞定了。

五、結果


一圖勝千言:

這裏寫圖片描述
圖8

就這樣。

六、下載地址


工程下載地址:github

https://github.com/cartzhang/ImgSayVRabc/tree/master/ViveEventDemo

steam 插件工程導出地址:

https://github.com/cartzhang/ImgSayVRabc/blob/master/ViveEventDemo/SteamViveControllerEventDemoCartzhang.unitypackage

2017-01-09更新,給事件添加觸發手柄ID。
https://github.com/cartzhang/ImgSayVRabc/blob/master/ViveEventDemo/HTVVive_event_add_controller_inedex%20_Cartzhang.unitypackage

七、參考

[1] http://www.cnblogs.com/czaoth/p/5610883.html

[2] http://www.htc.com/managed-assets/shared/desktop/vive/Vive_PRE_User_Guide.pdf

[3] http://blog.csdn.net/qiaochaoqc/article/details/52086790

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章