简介
这一篇中,主要介绍GLM的导入和设置,Input System的搭建,以及使用跨平台的KeyCode。
GLM全程OpenGL Mathematics,主要是一个用于辅助图形编程的数学库,其中定义了包括常用的vec3,vec4,mat3,mat4等数据类型,为之后的渲染做好铺垫。
项目的导入
也是和其他项目一样,用submodule就可以导入了,不再赘述
git submodule add https://github.com/g-truc/glm JEngine/ThirdParty/glm
Premake.lua
// 在table里面加入一个key pair
IncludeDir["glm"] = "JEngine/ThirdParty/glm"
//...省略
//选择要包括进引擎项目的文件目录
files
{
"%{prj.name}/src/**.h",
"%{prj.name}/src/**.cpp",
"%{prj.name}/ThirdParty/glm/glm/**.hpp",
"%{prj.name}/ThirdParty/glm/glm/**.inl",
}
//...省略
//用于#include查找的目录,在JEngine中
includedirs
{
"%{prj.name}/src/Public",
"%{IncludeDir.GLFW}",
"%{IncludeDir.Glad}",
"%{IncludeDir.Imgui}",
"%{IncludeDir.glm}"
}
//...省略
//用于#include查找的目录,在Sandbox中
includedirs
{
"JEngine/ThirdParty/SpdLog/include",
"JEngine/src",
"%{IncludeDir.glm}"
}
输入
接下来说说输入,对于一般的键鼠操作而言,输入其实仅仅只是一个keycode事件的callback,其基本可以分为鼠标的事件和键盘的事件,从代码中看:
//Input.h
#pragma once
#include "Core.h"
namespace JEngine
{
class Input
{
public:
inline static bool IsKeyDown(int keycode) { return s_Instance->IsKeyDownImpl(keycode); };
inline static bool IsMouseButtonDown(int mouseButton) { return s_Instance->IsMouseButtonDownImpl(mouseButton); };
inline static std::pair<float, float> GetMousePos() { return s_Instance->GetMousePosImpl(); };
inline static float GetMouseX() { return s_Instance->GetMouseXImpl(); };
inline static float GetMouseY() { return s_Instance->GetMouseYImpl(); };
protected:
virtual bool IsKeyDownImpl(int keycode) = 0;
virtual bool IsMouseButtonDownImpl(int mouseButton) = 0;
virtual std::pair<float, float> GetMousePosImpl() = 0;
virtual float GetMouseXImpl() = 0;
virtual float GetMouseYImpl() = 0;
private:
static Input* s_Instance;
};
}
不难看出,这个Input是一个singleton的接口,而且甚至没有构造函数,意味着不能直接把这个类实例出来。所有的public函数都是静态函数,并且只是protected函数实现的一个wrap,再看这些protected函数都是纯虚函数/抽象函数,这也意味着派生类必须根据平台做不同的实现。而高层逻辑在调用时,则不用考虑具体实现,直接从这里调用静态函数即可。
WindowsInput.h/.cpp
既然说到了不同平台的实现,就直接看一下代码:
#pragma once
#include "Input.h"
namespace JEngine
{
class WindowsInput : public Input
{
protected:
virtual bool IsKeyDownImpl(int keycode) override;
virtual bool IsMouseButtonDownImpl(int mouseButton) override;
virtual std::pair<float, float> GetMousePosImpl() override;
virtual float GetMouseXImpl() override;
virtual float GetMouseYImpl() override;
};
}
非常简单直白的,只是给纯虚函数一个override定义而已。
#include "JE_PCH.h"
#include "Platform/Windows/WindowsInput.h"
#include "Application.h"
#include "GLFW/glfw3.h"
namespace JEngine
{
Input* Input::s_Instance = new WindowsInput();
bool WindowsInput::IsKeyDownImpl(int keycode)
{
auto window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow());
auto status = glfwGetKey(window, keycode);
return status == GLFW_PRESS || status == GLFW_REPEAT;
}
bool WindowsInput::IsMouseButtonDownImpl(int mouseButton)
{
auto window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow());
auto status = glfwGetKey(window, mouseButton);
return status == GLFW_PRESS;
}
std::pair<float, float> WindowsInput::GetMousePosImpl()
{
auto window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow());
double x, y;
glfwGetCursorPos(window, &x, &y);
return { (float)x, (float)y };
}
float WindowsInput::GetMouseXImpl()
{
auto [x, y] = GetMousePosImpl();
return x;
}
float WindowsInput::GetMouseYImpl()
{
auto [x, y] = GetMousePosImpl();
return y;
}
}
首先要说的是,由于目前还没有资源管理器,所以逻辑中有相当一部分的raw pointer和new关键字来开辟新的内存空间,这些在日后资源管理一章中会重新讨论并冲构成对应的内存管理方式。
接下来的逻辑其实很好理解,本质上IsKey/MouseButtonDownImpl就三行:
auto window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow());
auto status = glfwGetKey(window, mouseButton);
return status == GLFW_PRESS;
- 找到当前input所对应平台的window实例,cast成GLFWwindow指针。
- glfwGetKey用于找到给定window中,最后一次输入的指定Key的状态。
- 这个状态包含三种,松开,按下,重复。
- 判断是否是按下或重复状态,返回判断的结果。

GetMousePosImpl也是类似的,glfwGetCursorPos(window, &x, &y)可以拿到给定window中鼠标的position,并且赋值给引用传参进来的x和y两个double。
GetNativeWindow
在上文中需要注意的是,拿到当前window的这行逻辑:
auto window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow());
还并没有实现,就简单介绍一下这段逻辑实际上做的事:
- Application::Get()返回了Application实例的引用。
- GetWindow()返回了当前Application的主Window的引用。
- GetNativeWindow()返回了实际平台的Window的指针。
- 将第三步拿到的指针静态cast成GLFWwindow*
- 赋值给window变量。
也就是说:
//Application.h
inline static Application& Get() { return *s_Instance; }
inline Window& GetWindow() { return *m_Window; }
//...
private:
unique_ptr<Window> m_Window;
static Application* s_Instance;
//...
然后在Window.h中:
public:
virtual void* GetNativeWindow() const = 0;
最后在WindowsWindow中:
inline virtual void* GetNativeWindow() const override { return m_Window; }
//...
private:
GLFWwindow* m_Window;
这样,就完成了拿到GLFW所需要的glfwWindow的方式,也可以看出,在这里WindowsInput和WindowsWindow是紧密绑定的。(也就是说WindowsInput并不可能拿到其他不使用GLFW实现的window的返回值,因为input本身也在使用GLFW,其它的平台静态转换不会成功)
KeyCodes
在输入系统写完之前还有最后一个问题,之前在ImguiLayer中,我们不得不使用glfw的KeyCode来进行编码,这就意味着只要用到了KeyCode,我们就不得不include GLFW,这就降低了跨平台的友好性,因此,我们会用自己的KeyCode来解决这件事:
//KeyCodes.h
#pragma once
// From glfw3.h
#define JE_KEY_SPACE 32
#define JE_KEY_APOSTROPHE 39 /* ' */
#define JE_KEY_COMMA 44 /* , */
#define JE_KEY_MINUS 45 /* - */
#define JE_KEY_PERIOD 46 /* . */
#define JE_KEY_SLASH 47 /* / */
#define JE_KEY_0 48
#define JE_KEY_1 49
#define JE_KEY_2 50
#define JE_KEY_3 51
#define JE_KEY_4 52
#define JE_KEY_5 53
#define JE_KEY_6 54
#define JE_KEY_7 55
#define JE_KEY_8 56
#define JE_KEY_9 57
#define JE_KEY_SEMICOLON 59 /* ; */
#define JE_KEY_EQUAL 61 /* = */
#define JE_KEY_A 65
#define JE_KEY_B 66
#define JE_KEY_C 67
#define JE_KEY_D 68
#define JE_KEY_E 69
#define JE_KEY_F 70
#define JE_KEY_G 71
#define JE_KEY_H 72
#define JE_KEY_I 73
#define JE_KEY_J 74
#define JE_KEY_K 75
#define JE_KEY_L 76
#define JE_KEY_M 77
#define JE_KEY_N 78
#define JE_KEY_O 79
#define JE_KEY_P 80
#define JE_KEY_Q 81
#define JE_KEY_R 82
#define JE_KEY_S 83
#define JE_KEY_T 84
#define JE_KEY_U 85
#define JE_KEY_V 86
#define JE_KEY_W 87
#define JE_KEY_X 88
#define JE_KEY_Y 89
#define JE_KEY_Z 90
#define JE_KEY_LEFT_BRACKET 91 /* [ */
#define JE_KEY_BACKSLASH 92 /* */
#define JE_KEY_RIGHT_BRACKET 93 /* ] */
#define JE_KEY_GRAVE_ACCENT 96 /* ` */
#define JE_KEY_WORLD_1 161 /* non-US #1 */
#define JE_KEY_WORLD_2 162 /* non-US #2 */
/* Function keys */
#define JE_KEY_ESCAPE 256
#define JE_KEY_ENTER 257
#define JE_KEY_TAB 258
#define JE_KEY_BACKSPACE 259
#define JE_KEY_INSERT 260
#define JE_KEY_DELETE 261
#define JE_KEY_RIGHT 262
#define JE_KEY_LEFT 263
#define JE_KEY_DOWN 264
#define JE_KEY_UP 265
#define JE_KEY_PAGE_UP 266
#define JE_KEY_PAGE_DOWN 267
#define JE_KEY_HOME 268
#define JE_KEY_END 269
#define JE_KEY_CAPS_LOCK 280
#define JE_KEY_SCROLL_LOCK 281
#define JE_KEY_NUM_LOCK 282
#define JE_KEY_PRINT_SCREEN 283
#define JE_KEY_PAUSE 284
#define JE_KEY_F1 290
#define JE_KEY_F2 291
#define JE_KEY_F3 292
#define JE_KEY_F4 293
#define JE_KEY_F5 294
#define JE_KEY_F6 295
#define JE_KEY_F7 296
#define JE_KEY_F8 297
#define JE_KEY_F9 298
#define JE_KEY_F10 299
#define JE_KEY_F11 300
#define JE_KEY_F12 301
#define JE_KEY_F13 302
#define JE_KEY_F14 303
#define JE_KEY_F15 304
#define JE_KEY_F16 305
#define JE_KEY_F17 306
#define JE_KEY_F18 307
#define JE_KEY_F19 308
#define JE_KEY_F20 309
#define JE_KEY_F21 310
#define JE_KEY_F22 311
#define JE_KEY_F23 312
#define JE_KEY_F24 313
#define JE_KEY_F25 314
#define JE_KEY_KP_0 320
#define JE_KEY_KP_1 321
#define JE_KEY_KP_2 322
#define JE_KEY_KP_3 323
#define JE_KEY_KP_4 324
#define JE_KEY_KP_5 325
#define JE_KEY_KP_6 326
#define JE_KEY_KP_7 327
#define JE_KEY_KP_8 328
#define JE_KEY_KP_9 329
#define JE_KEY_KP_DECIMAL 330
#define JE_KEY_KP_DIVIDE 331
#define JE_KEY_KP_MULTIPLY 332
#define JE_KEY_KP_SUBTRACT 333
#define JE_KEY_KP_ADD 334
#define JE_KEY_KP_ENTER 335
#define JE_KEY_KP_EQUAL 336
#define JE_KEY_LEFT_SHIFT 340
#define JE_KEY_LEFT_CONTROL 341
#define JE_KEY_LEFT_ALT 342
#define JE_KEY_LEFT_SUPER 343
#define JE_KEY_RIGHT_SHIFT 344
#define JE_KEY_RIGHT_CONTROL 345
#define JE_KEY_RIGHT_ALT 346
#define JE_KEY_RIGHT_SUPER 347
#define JE_KEY_MENU 348
MouseCode也是类似的:
//MouseButtonCode.h
#pragma once
// From glfw3.h
#define JE_MOUSE_BUTTON_1 0
#define JE_MOUSE_BUTTON_2 1
#define JE_MOUSE_BUTTON_3 2
#define JE_MOUSE_BUTTON_4 3
#define JE_MOUSE_BUTTON_5 4
#define JE_MOUSE_BUTTON_6 5
#define JE_MOUSE_BUTTON_7 6
#define JE_MOUSE_BUTTON_8 7
#define JE_MOUSE_BUTTON_LAST JE_MOUSE_BUTTON_8
#define JE_MOUSE_BUTTON_LEFT JE_MOUSE_BUTTON_1
#define JE_MOUSE_BUTTON_RIGHT JE_MOUSE_BUTTON_2
#define JE_MOUSE_BUTTON_MIDDLE JE_MOUSE_BUTTON_3
总结
本篇我们导入了glm数学库,建立了自己的Input系统和KeyCode,这样一来我们就可以获取和监听按键和鼠标的事件,在任何时候去询问某个特定按键的状态,下一章重回Imgui,建立一个基本可用的Docking Imgui,并开始重构整理一些目前为止的代码。