Input系统
为了实现面向不同系统时接收输入的能力,因此需要实现Input系统。我们定义一个Input基类,表示所有输入类要实现的方法,他基本上就是静态类和虚函数接口。
Input.h
#pragmaonce#include"core.h"namespaceXEngine{classX_APIInput{public:inlinestaticboolIsKeyPressed(intkey){returns_Instance->IsKeyPressedImpl(key);}inlinestaticboolIsMouseButtonPressed(intbutton){returns_Instance->IsMouseButtonPressedImpl(button);}inlinestaticfloatGetMouseX(){returns_Instance->GetMouseXImpl();}inlinestaticfloatGetMouseY(){returns_Instance->GetMouseYImpl();}inlinestaticstd::pair<float,float>GetMousePosition(){returns_Instance->GetMousePositionImpl();}protected:virtualboolIsKeyPressedImpl(intkey)=0;virtualboolIsMouseButtonPressedImpl(intbutton)=0;virtualfloatGetMouseXImpl()=0;virtualstd::pair<float,float>GetMousePositionImpl()=0;virtualfloatGetMouseYImpl()=0;private:staticInput*s_Instance;};}WindowsInput
通过继承Input,我们实现面向Windows平台的输入类。
//WindowsInput.h#pragmaonce#include"XEngine/core.h"#include"XEngine/Input.h"namespaceXEngine{classX_APIWindowsInput:publicInput{public:protected:virtualboolIsKeyPressedImpl(intkey)override;virtualboolIsMouseButtonPressedImpl(intbutton)override;virtualfloatGetMouseXImpl()override;virtualfloatGetMouseYImpl()override;virtualstd::pair<float,float>GetMousePositionImpl()override;};}//WindowsInput.cpp#include"xepch.h"#include"WindowsInput.h"#include"XEngine/Application.h"#include<GLFW/glfw3.h>namespaceXEngine{Input*Input::s_Instance=newWindowsInput();boolWindowsInput::IsKeyPressedImpl(intkey){autowindow=static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow());autostate=glfwGetKey(window,key);returnstate==GLFW_PRESS||state==GLFW_REPEAT;}boolWindowsInput::IsMouseButtonPressedImpl(intbutton){autowindow=static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow());autostate=glfwGetMouseButton(window,button);returnstate==GLFW_PRESS;}floatWindowsInput::GetMouseXImpl(){auto[x,y]=GetMousePositionImpl();return(float)x;}floatWindowsInput::GetMouseYImpl(){auto[x,y]=GetMousePositionImpl();returny;}std::pair<float,float>WindowsInput::GetMousePositionImpl(){autowindow=static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow());doublexpos,ypos;glfwGetCursorPos(window,&xpos,&ypos);return{xpos,ypos};}}- 通过实现接口来实现输入功能,包括获取键盘按键状态,获取鼠标按键状态,获取鼠标位置等。
- 其中返回鼠标位置时使用了
结构化绑定(Structured Binding)是 C++17 引入的一项特性,它允许你将一个结构体、数组、元组或类似聚合类型的多个成员一次性解包到多个独立的变量中,从而简化代码并提高可读性。
在你的代码中,GetMousePositionImpl()函数返回一个std::pair<float, float>。在 C++17 之前,你需要这样获取其成员:
std::pair<float,float>pos=GetMousePositionImpl();floatx=pos.first;floaty=pos.second;或者使用std::tie:
floatx,y;std::tie(x,y)=GetMousePositionImpl();而使用 C++17 的结构化绑定,你可以直接写成:
auto[x,y]=GetMousePositionImpl();这行代码做了以下几件事:
auto让编译器自动推导类型。[x, y]是结构化绑定的语法,它声明了两个变量x和y。- 函数返回的
std::pair<float, float>被自动“解包”,first成员赋值给x,second成员赋值给y。
结构化绑定的主要优点:
- 代码简洁:一行代码完成声明和赋值。
- 意图清晰:直接表明你要解包这个结构。
- 避免中间变量:无需先声明一个
std::pair临时对象。
适用场景:
除了std::pair,结构化绑定还适用于:
std::tuplestd::array和原生数组- 结构体或类(其所有非静态数据成员必须是 public 的)
在你的WindowsInput.cpp中的使用:
floatWindowsInput::GetMouseXImpl(){auto[x,y]=GetMousePositionImpl();// 使用结构化绑定解包鼠标坐标return(float)x;}这里,GetMousePositionImpl()返回的pair被直接解包到x和y中,然后函数返回x。这使得代码非常直观,一眼就能看出x是鼠标的横坐标。
因此,结构化绑定是一种让处理复合返回值(如坐标、多返回值函数)的代码变得更优雅和高效的现代 C++ 特性。
- 除此之外,由于在接口实现中需要使用到原生的glfw窗口,因此我们在Windowswindow.cpp中新加一个返回原生窗口的方法,用于返回保存的glfw窗口实例。
//Window.hvirtualvoid*GetNativeWindow()const=0;//WindowsWindowinlinevirtualvoid*GetNativeWindow()constoverride{returnm_Window;}本地化keycode
为了保持不同系统间的代码复用,我们需要一套自己的按键代码与目标平台的代码转换,一般有两种思路,一种是将代码存储在文件中,通过宏定义确认取用不同的按键码。一种是通过转换函数将目标码转换为自己的代码或者将自己的代码转换为目标码。这里直接存储glfw的代码作为引擎代码。
//KeysCode.h#pragmaonce#pragmaonce// From glfw3.h/* Printable keys */#defineX_KEY_SPACE32#defineX_KEY_APOSTROPHE39/* ' */#defineX_KEY_COMMA44/* , */#defineX_KEY_MINUS45/* - */#defineX_KEY_PERIOD46/* . */#defineX_KEY_SLASH47/* / */#defineX_KEY_048#defineX_KEY_149#defineX_KEY_250#defineX_KEY_351#defineX_KEY_452#defineX_KEY_553#defineX_KEY_654#defineX_KEY_755#defineX_KEY_856#defineX_KEY_957#defineX_KEY_SEMICOLON59/* ; */#defineX_KEY_EQUAL61/* = */#defineX_KEY_A65#defineX_KEY_B66#defineX_KEY_C67#defineX_KEY_D68#defineX_KEY_E69#defineX_KEY_F70#defineX_KEY_G71#defineX_KEY_H72#defineX_KEY_I73#defineX_KEY_J74#defineX_KEY_K75#defineX_KEY_L76#defineX_KEY_M77#defineX_KEY_N78#defineX_KEY_O79#defineX_KEY_P80#defineX_KEY_Q81#defineX_KEY_R82#defineX_KEY_S83#defineX_KEY_T84#defineX_KEY_U85#defineX_KEY_V86#defineX_KEY_W87#defineX_KEY_X88#defineX_KEY_Y89#defineX_KEY_Z90#defineX_KEY_LEFT_BRACKET91/* [ */#defineX_KEY_BACKSLASH92/* \ */#defineX_KEY_RIGHT_BRACKET93/* ] */#defineX_KEY_GRAVE_ACCENT96/* ` */#defineX_KEY_WORLD_1161/* non-US #1 */#defineX_KEY_WORLD_2162/* non-US #2 *//* Function keys */#defineX_KEY_ESCAPE256#defineX_KEY_ENTER257#defineX_KEY_TAB258#defineX_KEY_BACKSPACE259#defineX_KEY_INSERT260#defineX_KEY_DELETE261#defineX_KEY_RIGHT262#defineX_KEY_LEFT263#defineX_KEY_DOWN264#defineX_KEY_UP265#defineX_KEY_PAGE_UP266#defineX_KEY_PAGE_DOWN267#defineX_KEY_HOME268#defineX_KEY_END269#defineX_KEY_CAPS_LOCK280#defineX_KEY_SCROLL_LOCK281#defineX_KEY_NUM_LOCK282#defineX_KEY_PRINT_SCREEN283#defineX_KEY_PAUSE284#defineX_KEY_F1290#defineX_KEY_F2291#defineX_KEY_F3292#defineX_KEY_F4293#defineX_KEY_F5294#defineX_KEY_F6295#defineX_KEY_F7296#defineX_KEY_F8297#defineX_KEY_F9298#defineX_KEY_F10299#defineX_KEY_F11300#defineX_KEY_F12301#defineX_KEY_F13302#defineX_KEY_F14303#defineX_KEY_F15304#defineX_KEY_F16305#defineX_KEY_F17306#defineX_KEY_F18307#defineX_KEY_F19308#defineX_KEY_F20309#defineX_KEY_F21310#defineX_KEY_F22311#defineX_KEY_F23312#defineX_KEY_F24313#defineX_KEY_F25314#defineX_KEY_KP_0320#defineX_KEY_KP_1321#defineX_KEY_KP_2322#defineX_KEY_KP_3323#defineX_KEY_KP_4324#defineX_KEY_KP_5325#defineX_KEY_KP_6326#defineX_KEY_KP_7327#defineX_KEY_KP_8328#defineX_KEY_KP_9329#defineX_KEY_KP_DECIMAL330#defineX_KEY_KP_DIVIDE331#defineX_KEY_KP_MULTIPLY332#defineX_KEY_KP_SUBTRACT333#defineX_KEY_KP_ADD334#defineX_KEY_KP_ENTER335#defineX_KEY_KP_EQUAL336#defineX_KEY_LEFT_SHIFT340#defineX_KEY_LEFT_CONTROL341#defineX_KEY_LEFT_ALT342#defineX_KEY_LEFT_SUPER343#defineX_KEY_RIGHT_SHIFT344#defineX_KEY_RIGHT_CONTROL345#defineX_KEY_RIGHT_ALT346#defineX_KEY_RIGHT_SUPER347#defineX_KEY_MENU348//MouseButtonCodes.h#pragmaonce// From glfw3.h#defineNUT_MOUSE_BUTTON_10#defineNUT_MOUSE_BUTTON_21#defineNUT_MOUSE_BUTTON_32#defineNUT_MOUSE_BUTTON_43#defineNUT_MOUSE_BUTTON_54#defineNUT_MOUSE_BUTTON_65#defineNUT_MOUSE_BUTTON_76#defineNUT_MOUSE_BUTTON_87#defineNUT_MOUSE_BUTTON_LASTNUT_MOUSE_BUTTON_8#defineNUT_MOUSE_BUTTON_LEFTNUT_MOUSE_BUTTON_1#defineNUT_MOUSE_BUTTON_RIGHTNUT_MOUSE_BUTTON_2#defineNUT_MOUSE_BUTTON_MIDDLENUT_MOUSE_BUTTON_3然后我们就可以在layer中进行使用了。
测试一下。
Sandbox.cpp
voidOnEvent(XEngine::Event&event)override{if(event.GetEventType()==XEngine::EventType::KeyPressed){XEngine::KeyPressedEvent&e=(XEngine::KeyPressedEvent&)event;if(e.GetKeyCode()==X_KEY_TAB)X_TRACE("{0} is pressed","Tab");X_TRACE("{0} is pressed",(char)e.GetKeyCode());}}让我们改造一下Sandbox的exampleLayer
通过判断是否按下了X_KEY_TAB来证明我们的按键系统正确的工作。
当我们按下 Tab 时,可以看到正确打印了 “Tab”。但是转换为char的那行并没有正确打印,这是因为X_KEY_TAB的值为 258,而char类型(通常为有符号 8 位整数)的范围是 -128 到 127(或 0 到 255 对于无符号unsigned char)。将 258 强制转换为char会导致溢出,得到一个不可打印的字符(或乱码)。
解决方案:
- 避免对非 ASCII 字符进行
char转换:像 Tab、Enter、方向键等功能键的键码通常大于 255,不应直接当作字符打印。 - 使用条件判断或映射:对于特殊功能键,可以单独处理或使用查找表将其映射为可读的字符串。
例如,修改您的OnEvent函数:
voidOnEvent(XEngine::Event&event)override{if(event.GetEventType()==XEngine::EventType::KeyPressed){XEngine::KeyPressedEvent&e=(XEngine::KeyPressedEvent&)event;intkeyCode=e.GetKeyCode();if(keyCode==X_KEY_TAB){X_TRACE("Tab is pressed");// 对于 Tab 等特殊键,不尝试转换为 char// 或者可以映射为字符串X_TRACE("Key pressed: [Tab] (code: {0})",keyCode);}elseif(keyCode>=32&&keyCode<=126){// 仅当键码在可打印 ASCII 范围内时转换为 charX_TRACE("Key pressed: '{0}' (code: {1})",(char)keyCode,keyCode);}else{// 其他功能键X_TRACE("Key pressed: [Special Key] (code: {0})",keyCode);}}}根本原因:glfwGetKey返回的键码(如GLFW_KEY_TAB)是 GLFW 定义的整型常量,它们与 ASCII 码并不一一对应。直接将其强制转换为char只对部分字母、数字和标点符号有效(键码与 ASCII 码重合的部分)。