游戏引擎从零开始(6)-窗口事件处理

前言

这节完善事件处理,包括移动鼠标、窗口缩放、鼠标点击等,没有新增文件。另外修复上一节几处代码错误。

GLFW可以设置常见的事件回调,在WindowsWindow中我们将这些回调透传到Application。

即WindowsWindow处理通用的事件,具体的业务逻辑由业务层来处理,达到高内聚低耦合、功能分层的目的。

事件处理&分发

GLFW封装在WindowsWindow中,我们先处理最底层的事件回调

设置GLFW的回调

代码修改位于: Sandbox/Hazel/src/Hazel/Platform/Windows/WindowsWindow.cpp

有几种常见的回调需要处理 异常、窗口缩放、窗口关闭、键盘按键事件、鼠标按键事件、鼠标滚轮事件、鼠标移动

  1. 异常回调

声明异常回调的函数

c++ 复制代码
// glfw函数是全局的,异常处理函数也声明为静态函数,不能声明为类函数
static void GLFWErrorCallback(int error, const char* description) {
    HZ_CORE_ERROR("GLFW Error({0}:{1})", error, description);
}

// 设置到glfw中
glfwSetErrorCallback(GLFWErrorCallback);
  1. 窗口缩放回调

获取窗口新的宽高值,设置到data中,通过一个WindowResizeEvent事件回调回去。

其中,WindowData& data = (WindowData)glfwGetWindowUserPointer(window) 获取的是windowsWindow初始化函数中设置进glfw的变量m_Data.

c++ 复制代码
// set GLFW callback
glfwSetWindowSizeCallback(m_Window, [](GLFWwindow* window, int width, int height) {
    WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
    data.Width = width;
    data.Height = height;

    WindowResizeEvent event(width, height);
    data.EventCallback(event);
});

glfwSetWindowSizeCallback 和 glfwSetWindowUserPointer 成对使用

  1. 窗口关闭回调
ini 复制代码
glfwSetWindowCloseCallback(m_Window, [](GLFWwindow* window){
            WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
            WindowCloseEvent event;
            data.EventCallback(event);
        });
  1. 键盘按键回调

分别处理GLFW_PRESS、GLFW_RELEASE、GLFW_REPEAT三类按键事件

键盘按键按下后,会产生一次GLFW_PRESS事件,接着产生重复的REPEAT事件,松开后产生一次GLFW_RELEASE事件

csharp 复制代码
glfwSetKeyCallback(m_Window, [](GLFWwindow* window, int key, int scancode, int action, int mods){
            WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
            switch (action) {
                case GLFW_PRESS:
                {
                    KeyPressedEvent event(key, 0);
                    data.EventCallback(event);
                    break;
                }

                case GLFW_RELEASE:
                {
                    KeyPressedEvent event(key);
                    data.EventCallback(event);
                    break;
                }
                case GLFW_REPEAT:
                {
                    KeyPressedEvent event(key, 1);
                    data.EventCallback(event);
                    break;
                }
            }
        });
  1. 鼠标按键事件

分别处理GLFW_PRESS、GLFW_RELEASE事件

c++ 复制代码
glfwSetMouseButtonCallback(m_Window, [](GLFWwindow* window, int button, int action, int mods){
            WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
            WindowCloseEvent event;
            data.EventCallback(event);

            switch (action) {
                case GLFW_PRESS:
                {
                    MouseButtonPressedEvent event(button);
                    data.EventCallback(event);
                    break;
                }
                case GLFW_RELEASE:
                {
                    MouseButtonReleasedEvent event(button);
                    data.EventCallback(event);
                    break;
                }

            }
        });
  1. 鼠标滚轮事件
c++ 复制代码
glfwSetScrollCallback(m_Window, [](GLFWwindow* window, double xOffset, double yOffset){
    WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
    MouseScrolledEvent event((float) xOffset, (float)yOffset);
    data.EventCallback(event);
});
  1. 鼠标移动事件
c++ 复制代码
glfwSetCursorPosCallback(m_Window, [](GLFWwindow* window, double xOffset, double yOffset){
            WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
            MouseMovedEvent event((float) xOffset, (float)yOffset);
            data.EventCallback(event);
        });

data的EventCallback是std::function类型的对象(仿函数),类似Java中的Runnable

每个事件,都会通过data回调到Application中去处理,但此时我们还没有对data的EventCallback赋值,现在运行会空指针异常.

Application中的回调

代码修改位于: Sandbox/Hazel/src/Hazel/Application.cpp

在Application的构造函数中,赶紧设置回调到Window中.

m_Window.SetEventCallback(const EventCallbackFn& callback)

入参是std::function类型,需要借助std::bind函数将一个普通的函数转换成function,c++中称之为绑定.

定义OnEvent函数和OnWindowClose函数,包装成function设置到windows中,注意EventDispatcher的使用.(BIND_EVENT_FN是个宏定义,下面会讲到)

c++ 复制代码
Application::Application() {
      ...
      // 绑定类成员函数,需要传this
      Window::EventCallbackFn fn = std::bind(&Application::OnEvent, this, std::placeholders::_1);
      m_Window->SetEventCallback(fn);

}

void Application::OnEvent(Event &e) {
      EventDispatcher dispatcher(e);
      dispatcher.Dispatch<WindowCloseEvent>(BIND_EVENT_FN(OnWindowClose));
      HZ_CORE_TRACE("{0}", e.ToString());
  }

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

std::bind传入三个参数,第一个是需要绑定的函数地址,第二个是类对象本身的指针,第三个是函数入参的占位符,OnEvent只有一个参数就写一个占位符,如果有两个参数还得再加一个std::placeholders::_2,多个参数就以此类推往上加。
注意两点。这里绑定的是类对象,所以才需要传this。类对象的函数需要手动加上'&'取址,非类对象直接写Application::OnEvent编译器会帮你取出地址。

代码优化,std::bind的操作我们会多次用到这里可以抽象成宏定义,降低代码重复.

c++ 复制代码
#define BIND_EVENT_FN(x) std::bind(&Application::x, this, std::placeholders::_1)

设置回调的代码可以改成:

c++ 复制代码
#define BIND_EVENT_FN(x) std::bind(&Application::x, this, std::placeholders::_1)
Application::Application() {
    m_Window = std::unique_ptr<Window>(Window::Create());

    // 绑定类成员函数,需要传this
//        Window::EventCallbackFn fn = std::bind(&Application::OnEvent, this, std::placeholders::_1);dd
//        m_Window->SetEventCallback(fn);
    m_Window->SetEventCallback(BIND_EVENT_FN(OnEvent));
}

运行起来,随意操作,命令行有对应的日志打印

本节完整代码参考:事件处理及部分代码bug修复

相关推荐
StrokeAce1 小时前
linux桌面软件(wps)内嵌到主窗口后的关闭问题
linux·c++·qt·wps·窗口内嵌
家有狸花4 小时前
VSCODE驯服日记(三):配置C++环境
c++·ide·vscode
dengqingrui1234 小时前
【树形DP】AT_dp_p Independent Set 题解
c++·学习·算法·深度优先·图论·dp
C++忠实粉丝4 小时前
前缀和(8)_矩阵区域和
数据结构·c++·线性代数·算法·矩阵
ZZZ_O^O5 小时前
二分查找算法——寻找旋转排序数组中的最小值&点名
数据结构·c++·学习·算法·二叉树
小飞猪Jay7 小时前
C++面试速通宝典——13
jvm·c++·面试
rjszcb8 小时前
一文说完c++全部基础知识,IO流(二)
c++
小字节,大梦想9 小时前
【C++】二叉搜索树
数据结构·c++
吾名招财9 小时前
yolov5-7.0模型DNN加载函数及参数详解(重要)
c++·人工智能·yolo·dnn
我是哈哈hh9 小时前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝