最新学习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类的目的
