GLFW与引擎事件系统的桥接机制是一个经典的软件架构设计问题,也是构建现代游戏引擎不可或缺的设计理念。可以将其理解为:底层平台依赖(GLFW)与上层引擎逻辑(你的事件系统) 之间的分层、解耦与通信机制。
首先要明白,为什么需要桥接?
- 不让引擎逻辑依赖 GLFW(未来可能换为SDL、Win32 API、Mac Cocoa…)
- 所有事件统一走引擎层的事件系统(不管平台如何变化)
- 能够在引擎逻辑中 自由响应事件(窗口关闭、输入、鼠标等)
- 事件能穿越多个系统(如 UI、游戏逻辑、渲染等)
所以桥接机制是为了解耦系统之间的依赖,是 依赖反转原则(DIP) 的一种典范实现。
因此,整个桥接机制可以分为三个层面:
- 平台层(Platform)GLFW 回调 -> Event 对象
- 桥接层(Bridge)回调函数内:触发引擎事件分发机制
- 引擎层(Engine)Application::OnEvent + 事件系统
平台层:GLFW 层(外部依赖)
GLFW 本身通过回调机制,感知系统级事件(窗口关闭、大小变动、按键等):
glfwSetWindowCloseCallback(window, [](GLFWwindow* w) {
WindowCloseEvent event;
data.EventCallback(event);
});
它只是一个 “事件源”。但是它不负责怎么处理这个事件 —— 它只负责告诉你“事件发生了”。
桥接层:Window 封装 & EventCallback
在 Window 中注册一个回调接口(SetEventCallback),当 GLFW 有事件时,通过它把事件推送进引擎。
class Window {
public:
using EventCallbackFn = std::function<void(Event&)>;
void SetEventCallback(const EventCallbackFn& callback);
};
这一层实现了:
- 隔离平台依赖(GLFW)
- 提供一个标准接口
EventCallback,由引擎注册 - 当 GLFW 回调触发时,构造事件对象,调用
EventCallback(event);
引擎层:Application + Event 系统
定义了事件基类 Event,又有具体类型如 WindowCloseEvent、KeyPressedEvent 等。
然后通过事件派发器 EventDispatcher,在 Application::OnEvent 中完成如下操作:
EventDispatcher dispatcher(e);
dispatcher.Dispatch<WindowCloseEvent>(BIND_EVENT_FN(OnWindowClosed));
这一层实现了:
- 动态识别事件类型(运行时多态)
- 动态调用对应处理逻辑(回调函数)
- 支持事件向下分发(LayerStack)
以关闭窗口为例,完整的流程链为:
第一步:平台层GLFW 检测到用户关闭窗口
在类WindowsWindow中,使用glfwSetWindowSizeCallback()注册了一个 Lambda函数。
在该Lambda表达式中,将 GLFW 的原始信息(window, width, height)转为自定义事件WindowCloseEvent,然后使用 data.EventCallback 触发初始化窗口时设置的回调函数。
glfwSetWindowCloseCallback(m_Window, [](GLFWwindow* window)
{
WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
WindowCloseEvent event;
data.EventCallback(event);
});
这是由GLFW 库提供的一个窗口尺寸变化回调函数注册接口,属于 GLFW 的 API。它会注册一个回调函数,当GLFW 管理的窗口被用户调整大小时,就会调用该回调函数。
具体调用链为:修改窗口->GLFW窗口修改接口->接口中定义的Lambda->引擎层事件函数
它的官方函数签名:
GLFWwindow* window;
GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* window, GLFWwindowsizefun callback);
第二步:桥接层 Window 类内部调用了事件回调
由于此前在类Application的构造函数中,绑定了窗口回调和类Application的函数OnEvent():
m_Window->SetEventCallback(BIND_EVENT_FN(Application::OnEvent));
所以,当调用data.EventCallback(event),就会直接进入:
Application::OnEvent(Event& e)
第三步:引擎层Application类进行事件分发
使用EventDispatcher来判断事件类型,判断到传入的事件类型是WindowCloseEvent,则调用绑定的函数OnWindowClosed()来实现具体引擎层逻辑。
void Application::OnEvent(Event& e)
{
EventDispatcher dispatcher(e);
dispatcher.Dispatch<WindowCloseEvent>(BIND_EVENT_FN(OnWindowClosed));
HZ_CORE_TRACE("{}", e.ToString());
for (auto it = m_LayerStack.end(); it != m_LayerStack.begin();)
{
(*--it)->OnEvent(e);
if (e.Handled) break;
}
}
如果想实现引擎层的事件响应逻辑,则需要在Application类中定义事件函数。
对于窗口关闭事件,它的引擎层函数OnWindowClosed()代码逻辑如下:
bool Application::OnWindowClosed(WindowCloseEvent& e)
{
m_Running = false;
return true;
}
至此,引擎层事件逻辑处理完毕,窗口被关闭。
总结:
GLFW 与引擎之间的桥接机制,是通过“回调注入 + 事件对象转发”的方式实现了解耦和模块间通信,从而形成了一个灵活、可拓展、平台无关的事件响应体系。
它通过平台回调转发为引擎事件对象,实现了从平台层到引擎层的事件传递与解耦。
它的最大优点是让引擎逻辑不依赖底层平台,具备更强的可移植性、可测试性与系统扩展能力。
这么做的优势为:
| 优点 | 描述 |
|---|---|
| 解耦 | GLFW 只产生事件,不处理;引擎只处理事件,不管来源 |
| 可扩展 | 换 SDL、Win32 只需改 Window 封装层 |
| 可测试性强 | 引擎可手动创建事件并测试逻辑 |
| 多系统协同 | 同一个事件可以被 UI 层、游戏逻辑层等多个系统响应 |
| 层级传播机制 | LayerStack 控制传播顺序 + 可中断传播(Handled = true) |
还要许多地方会应用这个机制,比如:
| 桥接类型 | 说明 |
|---|---|
| GLFW → 引擎事件系统 | 当前例子 |
| OpenGL/DirectX → 渲染抽象层(Renderer API) | 隐藏具体图形 API |
| 平台窗口 → 引擎 GUI 系统(如 ImGui) | 输入响应与 GUI 联动 |
| 物理库 → 自定义物理接口 | Bullet/PhysX 解耦封装 |
| 输入系统 → 游戏逻辑 | 键盘/鼠标 → 角色行为 |

Leave a comment