Input Polling

引言

上一节我们为引擎完善了 ImGUI 事件,对预生成文件进行了修改。今天我们来看看事件系统对输入的轮询。

输入类

首先我们在 Infinite/ 下新建 Input.h 头文件,与 Windows 设计类似作为一个接口:

#pragma once

#include "Core.h"

namespace Infinite {

class INFINITE_API Input
{
public:
inline static bool IsKeyPressed(int keycode) { return s_Instance->IsKeyPressedImpl(keycode); }

inline static bool IsMouseButtonPressed(int button) { return s_Instance->IsMouseButtonPressedImpl(button); }
inline static std::pair<float, float> GetMousePosition() { return s_Instance->GetMousePositionImpl(); }
inline static float GetMouseX() { return s_Instance->GetMouseXImpl(); }
inline static float GetMouseY() { return s_Instance->GetMouseYImpl(); }

protected:
virtual bool IsKeyPressedImpl(int keycode) = 0;

virtual bool IsMouseButtonPressedImpl(int button) = 0;
virtual std::pair<float, float> GetMousePositionImpl() = 0;
virtual float GetMouseXImpl() = 0;
virtual float GetMouseYImpl() = 0;

private:
static Input* s_Instance;
};
}

我们设置了一个输入单例,并且配置好了函数实现接口。

Windows输入类

接着我们来到 src/Platform/Windows 文件夹,新建 WindowsWindow.cppWindowsWindow.h

头文件 WindowsInput.h 没什么可说的:

#pragma once

#include "Infinite/Input.h"

namespace Infinite {

class WindowsInput : public Input
{
protected:
virtual bool IsKeyPressedImpl(int keycode) override;
virtual bool IsMouseButtonPressedImpl(int button) override;
virtual std::pair<float, float> GetMousePositionImpl() override;
virtual float GetMouseXImpl() override;
virtual float GetMouseYImpl() override;
};

}

WindowsInput.cpp 我们先

#include "ifnpch.h"
#include "WindowsInput.h"
#include "./Infinite/Application.h"

#include <GLFW/glfw3.h>

namespace Infinite {

Input* Input::s_Instance = new WindowsInput();

bool WindowsInput::IsKeyPressedImpl(int keycode)
{

}

bool WindowsInput::IsMouseButtonPressedImpl(int button)
{

}

std::pair<float, float> WindowsInput::GetMousePositionImpl()
{

}

float WindowsInput::GetMouseXImpl()
{

}

float WindowsInput::GetMouseYImpl()
{

}

}

然而现在我们无法直接获取窗口 GLFWwindow* m_Window(在 WindowsWindow.h 中),一个可行的解决方案是考虑友元访问,但考虑到封装性最好还是不公开窗口。

因此我们在 Window.h 中加入一个函数 GetNativeWindow 返回一个虚拟的空指针,获取本地窗口:

#pragma once

#include "ifnpch.h"

#include "Infinite/Core.h"
#include "Infinite/Events/Event.h"

namespace Infinite {

struct WindowProps
{
std::string Title;
unsigned int Width;
unsigned int Height;

WindowProps(const std::string& title = "Infinite Engine",
unsigned int width = 1280,
unsigned int height = 720)
: Title(title), Width(width), Height(height)
{
}
};

// Interface representing a desktop system based Window
class INFINITE_API Window
{
public:
using EventCallbackFn = std::function<void(Event&)>;

virtual ~Window() {}

virtual void OnUpdate() = 0;

virtual unsigned int GetWidth() const = 0;
virtual unsigned int GetHeight() const = 0;

// Window attributes
virtual void SetEventCallback(const EventCallbackFn& callback) = 0;
virtual void SetVSync(bool enabled) = 0;
virtual bool IsVSync() const = 0;

+ virtual void* GetNativeWindow() const = 0;

static Window* Create(const WindowProps& props = WindowProps());
};

}

声明之后我们就可以在 WindowsWindow.h 返回 m_Window

#pragma once

#include "Window.h"

#include <GLFW/glfw3.h>


namespace Infinite {

class WindowsWindow : public Window
{
public:
WindowsWindow(const WindowProps& props);
virtual ~WindowsWindow();

void OnUpdate() override;

inline unsigned int GetWidth() const override { return m_Data.Width; }
inline unsigned int GetHeight() const override { return m_Data.Height; }

// Window attributes
inline void SetEventCallback(const EventCallbackFn& callback) override { m_Data.EventCallback = callback; }
void SetVSync(bool enabled) override;
bool IsVSync() const override;

+ inline virtual void* GetNativeWindow() const { return m_Window; }
private:
virtual void Init(const WindowProps& props);
virtual void Shutdown();
private:
GLFWwindow* m_Window;

struct WindowData
{
std::string Title;
unsigned int Width, Height;
bool VSync;

EventCallbackFn EventCallback;
};

WindowData m_Data;
};

}

KeyPressed

bool WindowsInput::IsKeyPressedImpl(int keycode)
{
auto window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow());
auto state = glfwGetKey(window, keycode);

return state == GLFW_PRESS
|| state == GLFW_REPEAT;
}

我们从应用程序 Application 获取窗口 window,调用 glfw 内置函数 glfwGetKey() 即可。

MouseButtonPressed

bool WindowsInput::IsMouseButtonPressedImpl(int button)
{
auto window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow());
auto state = glfwGetMouseButton(window, button);

return state == GLFW_PRESS;
}

获取鼠标按压事件同理,调用 glfwGetMouseButton()

MousePos

std::pair<float, float> WindowsInput::GetMousePositionImpl()
{
auto window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow());
double xpos, ypos;
glfwGetCursorPos(window, &xpos, &ypos);

return { (float)xpos, (float)ypos };
}

float WindowsInput::GetMouseXImpl()
{
// auto window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow());
// double xpos, ypos;
// glfwGetCursorPos(window, &xpos, &ypos);

// return (float)xpos;

auto [x, y] = GetMousePositionImpl();
return (float)x;
}

float WindowsInput::GetMouseYImpl()
{
// auto window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow());
// double xpos, ypos;
// glfwGetCursorPos(window, &xpos, &ypos);

// return (float)ypos;

auto [x, y] = GetMousePositionImpl();
return (float)y;
}

如果项目不是 C++17 标准,则使用注释中的实现方法。

调试

Application.cpp 加入鼠标坐标调试:

#include "ifnpch.h"
#include "Application.h"

#include "Events/ApplicationEvent.h"
#include "Log.h"

#include <glad/glad.h>

+#include "Input.h"

namespace Infinite {

#define BIND_EVENT_FN(x) std::bind(&Application::x, this, std::placeholders::_1)

Application* Application::s_Instance = nullptr;

Application::Application()
{
IFN_CORE_ASSERT(!s_Instance, "Application already exists!");
s_Instance = this;

m_Window = std::unique_ptr<Window>(Window::Create());
m_Window->SetEventCallback(BIND_EVENT_FN(OnEvent));
}

Application::~Application() {}

void Application::PushLayer(Layer* layer)
{
m_LayerStack.PushLayer(layer);
layer->OnAttach();
}

void Application::PushOverlay(Layer* layer)
{
m_LayerStack.PushOverlay(layer);
layer->OnAttach();
}


void Application::OnEvent(Event& e)
{
EventDispatcher dispatcher(e);
dispatcher.Dispatch<WindowCloseEvent>(BIND_EVENT_FN(OnWindowClose));

for (auto it = m_LayerStack.end(); it != m_LayerStack.begin();)
{
(*--it)->OnEvent(e);
if (e.Handled)
break;
}
}

void Application::Run()
{
while (m_Running)
{
glClearColor(1, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);

for (Layer* layer : m_LayerStack)
layer->OnUpdate();

+ auto [x, y] = Input::GetMousePosition();
+ IFN_CORE_TRACE("{0}, {1}", x, y);
m_Window->OnUpdate();
}
}

bool Application::OnWindowClose(WindowCloseEvent& e)
{
m_Running = false;
return true;
}

}