在Windows 中,我们在键盘上按下了某一个按键,会触发相应的事件,大致过程如下:
1.键盘会检测到这个动作,并通过键盘控制器把扫描码(scan code)传送到计算机;
键盘扫描码跟具体的硬件有关的,不同厂商对同一个键的扫描码有可能不同。
2.计算机接收到扫描码后,将其交给键盘驱动程序;
3.键盘驱动程序把这个扫描码转换为键盘虚拟码;
虚拟码与具体硬件无关,不同厂商的键盘,同一个键的虚拟码总是相同的。
3.然后,键盘驱动程序把该键盘操作的扫描码和虚拟码以及其它信息传递给操作系统;
4.操作系统将获得的信息封装在一个键盘消息中,并把该键盘消息插入到消息列队。
5.通过Windows的消息系统,该键盘消息被送到某个窗口中;
6.窗口所在的应用程序接收到消息后,可以了解到有关键盘操作的信息,然后决定作出一定的响应。
而我们的应用程序从Windows接受的关于键盘事件的消息可以分为击键消息和字符消息,即按下或松开键盘上的按键消息为击键消息,而字符消息简单理解就是操作的是哪个按键。
在UE4里面,UE4首先自定义了FKey类型来表示按键,并通过结构体EKeys声明了包括所有键鼠按键但不限于键鼠的按键输入类型,如:移动端、VR、PS等触屏或手势输入,本文仅讨论键鼠相关的输入。
通过FGenericPlatformInput及其子类FWindowsPlatformInput里的两个函数拿到windows系统的按键映射
struct INPUTCORE_API FWindowsPlatformInput : FGenericPlatformInput
{
static uint32 GetKeyMap( uint32* KeyCodes, FString* KeyNames, uint32 MaxMappings );
static uint32 GetCharKeyMap(uint32* KeyCodes, FString* KeyNames, uint32 MaxMappings);
};
uint32 FWindowsPlatformInput::GetKeyMap( uint32* KeyCodes, FString* KeyNames, uint32 MaxMappings )
{
#define ADDKEYMAP(KeyCode, KeyName) if (NumMappings<MaxMappings) { KeyCodes[NumMappings]=KeyCode; KeyNames[NumMappings]=KeyName; ++NumMappings; };
uint32 NumMappings = 0;
if ( KeyCodes && KeyNames && (MaxMappings > 0) )
{
ADDKEYMAP( VK_LBUTTON, TEXT("LeftMouseButton") );
ADDKEYMAP( VK_RBUTTON, TEXT("RightMouseButton") );
ADDKEYMAP( VK_MBUTTON, TEXT("MiddleMouseButton") );
ADDKEYMAP( VK_XBUTTON1, TEXT("ThumbMouseButton") );
ADDKEYMAP( VK_XBUTTON2, TEXT("ThumbMouseButton2") );
ADDKEYMAP( VK_BACK, TEXT("BackSpace") );
ADDKEYMAP( VK_TAB, TEXT("Tab") );
ADDKEYMAP( VK_RETURN, TEXT("Enter") );
ADDKEYMAP( VK_PAUSE, TEXT("Pause") );
ADDKEYMAP( VK_CAPITAL, TEXT("CapsLock") );
ADDKEYMAP( VK_ESCAPE, TEXT("Escape") );
ADDKEYMAP( VK_SPACE, TEXT("SpaceBar") );
ADDKEYMAP( VK_PRIOR, TEXT("PageUp") );
ADDKEYMAP( VK_NEXT, TEXT("PageDown") );
ADDKEYMAP( VK_END, TEXT("End") );
ADDKEYMAP( VK_HOME, TEXT("Home") );
ADDKEYMAP( VK_LEFT, TEXT("Left") );
ADDKEYMAP( VK_UP, TEXT("Up") );
ADDKEYMAP( VK_RIGHT, TEXT("Right") );
ADDKEYMAP( VK_DOWN, TEXT("Down") );
ADDKEYMAP( VK_INSERT, TEXT("Insert") );
ADDKEYMAP( VK_DELETE, TEXT("Delete") );
ADDKEYMAP( VK_NUMPAD0, TEXT("NumPadZero") );
ADDKEYMAP( VK_NUMPAD1, TEXT("NumPadOne") );
ADDKEYMAP( VK_NUMPAD2, TEXT("NumPadTwo") );
ADDKEYMAP( VK_NUMPAD3, TEXT("NumPadThree") );
ADDKEYMAP( VK_NUMPAD4, TEXT("NumPadFour") );
ADDKEYMAP( VK_NUMPAD5, TEXT("NumPadFive") );
ADDKEYMAP( VK_NUMPAD6, TEXT("NumPadSix") );
ADDKEYMAP( VK_NUMPAD7, TEXT("NumPadSeven") );
ADDKEYMAP( VK_NUMPAD8, TEXT("NumPadEight") );
ADDKEYMAP( VK_NUMPAD9, TEXT("NumPadNine") );
ADDKEYMAP( VK_MULTIPLY, TEXT("Multiply") );
ADDKEYMAP( VK_ADD, TEXT("Add") );
ADDKEYMAP( VK_SUBTRACT, TEXT("Subtract") );
ADDKEYMAP( VK_DECIMAL, TEXT("Decimal") );
ADDKEYMAP( VK_DIVIDE, TEXT("Divide") );
ADDKEYMAP( VK_F1, TEXT("F1") );
ADDKEYMAP( VK_F2, TEXT("F2") );
ADDKEYMAP( VK_F3, TEXT("F3") );
ADDKEYMAP( VK_F4, TEXT("F4") );
ADDKEYMAP( VK_F5, TEXT("F5") );
ADDKEYMAP( VK_F6, TEXT("F6") );
ADDKEYMAP( VK_F7, TEXT("F7") );
ADDKEYMAP( VK_F8, TEXT("F8") );
ADDKEYMAP( VK_F9, TEXT("F9") );
ADDKEYMAP( VK_F10, TEXT("F10") );
ADDKEYMAP( VK_F11, TEXT("F11") );
ADDKEYMAP( VK_F12, TEXT("F12") );
ADDKEYMAP( VK_NUMLOCK, TEXT("NumLock") );
ADDKEYMAP( VK_SCROLL, TEXT("ScrollLock") );
ADDKEYMAP( VK_LSHIFT, TEXT("LeftShift") );
ADDKEYMAP( VK_RSHIFT, TEXT("RightShift") );
ADDKEYMAP( VK_LCONTROL, TEXT("LeftControl") );
ADDKEYMAP( VK_RCONTROL, TEXT("RightControl") );
ADDKEYMAP( VK_LMENU, TEXT("LeftAlt") );
ADDKEYMAP( VK_RMENU, TEXT("RightAlt") );
ADDKEYMAP( VK_LWIN, TEXT("LeftCommand") );
ADDKEYMAP( VK_RWIN, TEXT("RightCommand") );
TMap<uint32, uint32> ScanToVKMap;
#define MAP_OEM_VK_TO_SCAN(KeyCode) { const uint32 CharCode = MapVirtualKey(KeyCode,2); if (CharCode != 0) { ScanToVKMap.Add(CharCode,KeyCode); } }
MAP_OEM_VK_TO_SCAN(VK_OEM_1);
MAP_OEM_VK_TO_SCAN(VK_OEM_2);
MAP_OEM_VK_TO_SCAN(VK_OEM_3);
MAP_OEM_VK_TO_SCAN(VK_OEM_4);
MAP_OEM_VK_TO_SCAN(VK_OEM_5);
MAP_OEM_VK_TO_SCAN(VK_OEM_6);
MAP_OEM_VK_TO_SCAN(VK_OEM_7);
MAP_OEM_VK_TO_SCAN(VK_OEM_8);
MAP_OEM_VK_TO_SCAN(VK_OEM_PLUS);
MAP_OEM_VK_TO_SCAN(VK_OEM_COMMA);
MAP_OEM_VK_TO_SCAN(VK_OEM_MINUS);
MAP_OEM_VK_TO_SCAN(VK_OEM_PERIOD);
MAP_OEM_VK_TO_SCAN(VK_OEM_102);
#undef MAP_OEM_VK_TO_SCAN
static const uint32 MAX_KEY_MAPPINGS(256);
uint32 CharCodes[MAX_KEY_MAPPINGS];
FString CharKeyNames[MAX_KEY_MAPPINGS];
//此处调用了GetCharKeyMap函数
const int32 CharMappings = GetCharKeyMap(CharCodes, CharKeyNames, MAX_KEY_MAPPINGS);
for (int32 MappingIndex = 0; MappingIndex < CharMappings; ++MappingIndex)
{
//移除重复的按键
ScanToVKMap.Remove(CharCodes[MappingIndex]);
}
for (auto It(ScanToVKMap.CreateConstIterator()); It; ++It)
{
ADDKEYMAP(It.Value(), FString::Chr(It.Key()));
}
}
check(NumMappings < MaxMappings);
return NumMappings;
#undef ADDKEYMAP
}
uint32 FWindowsPlatformInput::GetCharKeyMap(uint32* KeyCodes, FString* KeyNames, uint32 MaxMappings)
{
return FGenericPlatformInput::GetStandardPrintableKeyMap(KeyCodes, KeyNames, MaxMappings, true, false);
}
然后在全局对象FInputKeyManager的初始化函数中保存从系统拿到的按键映射:
void FInputKeyManager::InitKeyMappings()
{
static const uint32 MAX_KEY_MAPPINGS(256);
uint32 KeyCodes[MAX_KEY_MAPPINGS], CharCodes[MAX_KEY_MAPPINGS];
FString KeyNames[MAX_KEY_MAPPINGS], CharKeyNames[MAX_KEY_MAPPINGS];
//从系统获取字符按键映射(主要是字母和数字,它们的虚拟键码是ASCII码)
uint32 const CharKeyMapSize(FPlatformInput::GetCharKeyMap(CharCodes, CharKeyNames, MAX_KEY_MAPPINGS));
//从系统获取其他按键映射(拥有系统虚拟码的按键)
uint32 const KeyMapSize(FPlatformInput::GetKeyMap(KeyCodes, KeyNames, MAX_KEY_MAPPINGS));
for (uint32 Idx=0; Idx<KeyMapSize; ++Idx)
{
FKey Key(*KeyNames[Idx]);
if (!Key.IsValid())
{
//把系统按键映射添加到UE4自定义的按键映射
EKeys::AddKey(FKeyDetails(Key, Key.GetDisplayName()));
}
//保存按键映射
KeyMapVirtualToEnum.Add(KeyCodes[Idx], Key);
}
for (uint32 Idx=0; Idx<CharKeyMapSize; ++Idx)
{
// repeated linear search here isn't ideal, but it's just once at startup
const FKey Key(*CharKeyNames[Idx]);
if (ensureMsgf(Key.IsValid(), TEXT("Failed to get key for name %s"), *CharKeyNames[Idx]))
{
//保存按键字符映射
KeyMapCharToEnum.Add(CharCodes[Idx], Key);
}
}
}
KeyMapVirtualToEnum和KeyMapCharToEnum这两个都是TMap类型的变量,然后该对象分别提供了相应的函数来通过输入的Key来获取到相应的虚拟码和字符:
void FInputKeyManager::GetCodesFromKey(const FKey Key, const uint32*& KeyCode, const uint32*& CharCode) const
{
CharCode = KeyMapCharToEnum.FindKey(Key);
KeyCode = KeyMapVirtualToEnum.FindKey(Key);
}
键鼠按键触发流程参考 Windows 编程 键盘