最新學習Unity的Animator,做了一個簡單的控制人物待機、走跑和跳躍動作的study project。其中一個控制規則是按下W走,按下Shift+W跑,按着Shift+W的同時按空格鍵則播放跳的動作。
走跑都很順利,跳卻一直跳不起來。一開始以為是狀態機或代碼問題,但是檢查了一下,沒有問題。如果動作、狀態機和控制代碼都沒有問題,那就很可能是按下空格鍵的時候沒有響應咯?
OK,寫測試代碼:
1 void Update() 2 { 3 if (Input.GetKeyDown(KeyCode.LeftShift)) 4 { 5 Debug.Log("Shift key down"); 6 } 7 if (Input.GetKeyDown(KeyCode.Space)) 8 { 9 Debug.Log("Space key down"); 10 } 11
12 if (Input.GetKey(KeyCode.LeftShift) && Input.GetKeyDown(KeyCode.Space))//①
13 { 14 Debug.Log("LeftShift and Space key down"); 15 } 16 }
注意①使用Input.GetKey(KeyCode.LeftShift) && Input.GetKeyDown(KeyCode.Space)而非Input.GetKeyDown(KeyCode.LeftShift) && Input.GetKeyDown(KeyCode.Space)的原因:Input.GetKeyDown只會在你按下鍵位的那一幀檢測到並返回True,后面將返回False,直到你重新再按下該鍵。所以,如果你能保證可以在同一幀同時按下shift+space,那你也可以這么用...我試過了,概率很低很低==!
來看看輸出:
按下左shift時:

按下空格鍵時:

同時按下shift和空格鍵時:

空格鍵呢....
OK,確認是鍵位沒有正常響應了,shift正常,space正常,那到底是什么導致shift+space沒有正常響應呢?
最有可能的就是該組合鍵(熱鍵)被其他程序占用了。那怎么知道shift+space是不是被占用了,被什么程序占用了呢?

Windows Hotkey Explorer 可以顯示當前已被占用的快捷鍵,並定位到相關程序
需要膽量是因為:

結果我也中招了(Win 10)...所以慎用。但是好歹檢測出來shift+space的確已經被注冊占用了。一般這種情況下多看看桌面的右下角...找到了

原來是輸入法程序占用了Shift+Space
其實知道怎么注冊熱鍵的話也可以自己寫一小段代碼測試有沒有被占用:
1 using System; 2 using System.Collections.Generic; 3 using System.Runtime.InteropServices; 4
5 namespace Neo 6 { 7 public class HotKeyRegisterCheck 8 { 9 [DllImport("user32.dll")] 10 public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint control, uint keycode); 11
12 [DllImport("user32.dll")] 13 public static extern bool UnregisterHotKey(IntPtr hWnd, int id); 14
15 /// <summary>
16 /// 通過嘗試注冊熱鍵來判斷是否已被占用 17 /// </summary>
18 /// <param name="control">控制鍵,如ctrl,alt和shift等</param>
19 /// <param name="keyCode">和控制鍵組合的鍵值</param>
20 /// <returns>指定的組合鍵是否已經注冊過</returns>
21 public static bool CheckHotKeyIsRegistered(uint control,uint keyCode) 22 { 23 bool registerResult = RegisterHotKey((IntPtr)null, 999, control, keyCode);//注冊熱鍵
24 if (registerResult) UnregisterHotKey((IntPtr)null, 999);//因為我們僅是檢測,所以如果注冊成功了還得取消注冊
25
26 return !registerResult; 27 } 28 } 29 }
使用:
1 void Start() 2 { 3 bool result = HotKeyRegisterCheck.CheckHotKeyIsRegistered(0x0004, 0x20);//Shift+Space
4 Debug.Log("熱鍵是否已被注冊過:" + result); 5 }
看看結果:
其實就是用win api嘗試注冊熱鍵,如果能注冊成功則說明還沒有被占用,否則就是已被其他程序注冊過了。關於要傳入的組合鍵的值可以參考
控制鍵支持Ctrl、Alt、shift和Win
既然可以動態檢測組合鍵有沒有被占用,那我們就有辦法來規避這種情況。比如游戲內默認設定Shift+Space為跑跳的同時再設置幾組組合鍵作為備用,通過上述檢測方法一一驗證,直到發現沒有被占用的組合鍵。但是,這不僅增加了工作量,更為關鍵的是對組合鍵使用默認習慣的一種挑戰,畢竟大家基本都習慣Shift跑Space跳Shift+Space跑跳。為了解決這個問題,我們可能就要舍棄Unity提供的Input類或者可以與其結合使用--自己實現鍵盤監控
先看代碼(網上找的,略微做了些修改):
1 using System; 2 using System.Collections.Generic; 3 using UnityEngine; 4 using System.Runtime.InteropServices; 5 using System.Windows.Forms; 6
7 namespace Neo 8 { 9 public class KeybordHook 10 { 11 public struct KeyMSG 12 { 13 public int vkCode; 14 public int scanCode; 15 public int flags; 16 public int time; 17 public int dwExtraInfo; 18 } 19
20 #region 第一步:聲明API函數
21 //使用鈎子,需要使用WindowsAPI函數,所以要先聲明這些API函數。 22
23 // 安裝鈎子
24 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 25 public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId); 26
27 // 卸載鈎子
28 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 29 public static extern bool UnhookWindowsHookEx(int idHook); 30
31 // 繼續下一個鈎子
32 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 33 public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam); 34
35 // 取得當前線程編號
36 [DllImport("kernel32.dll")] 37 static extern int GetCurrentThreadId(); 38
39 #endregion
40
41 #region 第二步:聲明,定義委托
42 public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam); 43
44 static int hKeyboardHook = 0;//如果hKeyboardHook不為0則說明鈎子安裝成功
45
46 HookProc KeyboardHookProcedure; 47 #endregion
48
49 #region 第三步:編寫鈎子子程
50 //鈎子子程就是鈎子所要做的事情。
51 private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr IParam) 52 { 53 if (nCode >= 0) 54 { 55 int ret = 1; 56 //這一步是將IParam的信息提取到自定義的struct里面。注釋掉是因為在我這里會導致unity莫名退出,很奇怪 57 //KeyMSG m = (KeyMSG)Marshal.PtrToStructure(IParam, typeof(KeyMSG));
58 if (IParam.ToInt32() > 0) 59 { 60 Debug.Log("鍵盤按下:" + wParam); 61 } 62 else if (IParam.ToInt32() < 0) 63 { 64 Debug.Log("鍵盤抬起:" + wParam); 65 } 66
67 if (ret == 1) CallNextHookEx(hKeyboardHook, nCode, wParam, IParam);//出於禮貌,如果要丟棄按鍵消息,最好先讓后續的鈎子處理該消息
68 return ret;//0:按鍵消息將繼續傳遞給windows消息處理函數 1:直接丟棄按鍵消息,不會傳給windows消息處理函數
69 } 70 return CallNextHookEx(hKeyboardHook, nCode, wParam, IParam); 71 } 72 #endregion
73
74 #region 第四步:正式啟用鈎子
75 //鈎子安裝
76 public void HookStart() 77 { 78 if (hKeyboardHook == 0) 79 { 80 //創建HookProc實例
81 KeyboardHookProcedure = new HookProc(KeyboardHookProc); 82 //設置鈎子,第一個參數2表示此鈎子為線程鈎子
83 hKeyboardHook = SetWindowsHookEx(2, KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId()); 84
85 if (hKeyboardHook == 0) 86 { 87 //終止鈎子
88 throw new Exception("安裝鈎子失敗"); 89 } 90 else
91 { 92 Debug.Log("安裝鈎子成功"); 93 } 94 } 95 } 96
97 //鈎子卸載
98 public void HookStop() 99 { 100 bool retKeyboard = true; 101 if (hKeyboardHook != 0) 102 { 103 retKeyboard = UnhookWindowsHookEx(hKeyboardHook); 104 hKeyboardHook = 0; 105 } 106 if (!retKeyboard) 107 throw new Exception("鈎子卸載失敗"); 108
109 } 110 #endregion
111 } 112 }
使用起來也很簡單:
1 KeybordHook kh = new KeybordHook(); 2 //開始鍵盤監控
3 kh.HookStart();
當要結束監控時:
1 kh.HookStop();
輸出:

在shift按下的時候成功的監聽到了space按下的消息
KeyboardHookProc函數獲得了鍵盤的物理擊鍵數據,然后就可以根據這些數據編寫自己的Input類了,這里就不繼續深入了。關於鍵盤監控,可以參考http://www.cnblogs.com/grenet/archive/2010/12/07/1898840.html
目前來說,比較好的做法是,如果你的游戲需要使用組合鍵而你又擔心會被占用時,可以通過實現自己的鍵盤監控來避免這種情況的發生。實際操作時,可以先通過上邊提到的檢測方法來確定游戲所使用的所有組合鍵有沒有被占用,沒有被占用就使用Unity提供的Input,否則使用自己的Input。當然,兩者其實可以結合使用以達到減少自己實現Input的代碼量和盡量使用原生Input類的目的

