[OpenGL]实现Unity中的按键监听方式

文章目录[x]
  1. 1:建立委托
  2. 2:设置OpenGL回调
  3. 3:Input类绑定按键委托
  4. 4:实现鼠标移动和鼠标滚轮的监听

因为OpenGL学习到了摄像机的章节,需要用键盘的WASD来移动摄像机,所以就顺便写了一个Input类,以此来实现类似Unity中监听按键的效果。效果如下

if (Input::GetKey(KeyCode::W))
if (Input::GetKeyDown(KeyCode::W))
if (Input::GetKeyUp(KeyCode::W))

 

 

建立委托


委托是C#的叫法,emmm。。。不知道C++叫什么好(函数指针)?就姑且叫委托吧。

#include <functional>
class Window : public Singleton<Window>
{
    friend class Singleton<Window>;
public:
    // window, key, mode
    std::function<void(GLFWwindow*, int, int)> onKeyDown;
    std::function<void(GLFWwindow*, int, int)> onKeyUp;
    std::function<void(GLFWwindow*, int, int)> onKeyRepeat;

    std::function<void()> onPrcoessInput;
// ... 略
};

 

设置OpenGL回调


void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mod)
{
    switch (action)
    {
        case GLFW_PRESS:   if (Window::GetInstance().onKeyDown) Window::GetInstance().onKeyDown(window, key, mod);     break;
        case GLFW_RELEASE: if (Window::GetInstance().onKeyUp) Window::GetInstance().onKeyUp(window, key, mod);       break;
        case GLFW_REPEAT:  if (Window::GetInstance().onKeyRepeat) Window::GetInstance().onKeyRepeat(window, key, mod);   break;
    }
}
glfwSetKeyCallback(m_GlfwWindow, KeyCallback);

 

这样通过上述代码,当OpenGL检测到按键的时候就会调用 KeyCallback 函数,此时我们判断按键类型,再分别调用对应的委托。

通过这种方式,可以将按键监听和与Input系统很好的解耦。

 

Input类绑定按键委托


View Code
#pragma once

#include "Window/Window.h"
#include "KeyCode.h"
#include "Singleton/Singleton.h"


class Input : public Singleton<Input>
{
    enum KeyState
    {
        KeyUnkown = 0,
        KeyDown = 1,
        KeyUp = 2,
        KeyRepeat = 4
    };

    friend class Singleton<Input>;

public:
    // TODO 现在这样写 在同一帧中,先进行GetKeyXXX判断的会截获掉后面进行GetKeyXXX判断的,所以是不行的
    // 应该在一次游戏循环的最后再处理
    // 按下一次键
    static bool GetKeyDown(KeyCode keyCode);
    // 松开一次键
    static bool GetKeyUp(KeyCode keyCode);
    // 一直按下键
    static bool GetKey(KeyCode keyCode);

private:
    bool m_Initialized = false;
    Window& m_Window;
    KeyState m_KeyState[512];

    Input();

    void OnKeyDown(GLFWwindow* window, int key, int mod);
    void OnKeyUp(GLFWwindow* window, int key, int mod);
    void OnKeyRepeat(GLFWwindow* window, int key, int mod);
    void OnProcessInput();

    inline Input::KeyState GetKeyState(KeyCode keyCode) { return m_KeyState[keyCode]; }
    inline void SetKeyState(int key, KeyState state) { m_KeyState[key] = state; }
};
#include "Input.h"
#include "Window/Window.h"

bool Input::GetKeyDown(KeyCode keyCode)
{
    if (Input::GetInstance().GetKeyState(keyCode) & KeyState::KeyDown)
    {
        Input::GetInstance().SetKeyState(keyCode, KeyState::KeyUnkown);
        return true;
    }

    return false;
}

bool Input::GetKeyUp(KeyCode keyCode)
{
    if (Input::GetInstance().GetKeyState(keyCode) & KeyState::KeyUp)
    {
        Input::GetInstance().SetKeyState(keyCode, KeyState::KeyUnkown);
        return true;
    }

    return false;
}

bool Input::GetKey(KeyCode keyCode)
{
    return Input::GetInstance().GetKeyState(keyCode) & (KeyState::KeyRepeat | KeyState::KeyDown);
}

Input::Input() : m_Window(Window::GetInstance())
{
    using namespace std::placeholders;

    m_Window.onKeyDown = std::bind(&Input::OnKeyDown, this, _1, _2, _3);
    m_Window.onKeyUp = std::bind(&Input::OnKeyUp, this, _1, _2, _3);
    m_Window.onKeyRepeat = std::bind(&Input::OnKeyRepeat, this, _1, _2, _3);

    m_Window.onPrcoessInput = std::bind(&Input::OnProcessInput, this);

    m_Initialized = true;
}

void Input::OnProcessInput()
{
    if (glfwGetKey(m_Window.GetGlfwWindow(), GLFW_KEY_ESCAPE) == GLFW_PRESS)
    {
        glfwSetWindowShouldClose(m_Window.GetGlfwWindow(), GL_TRUE);
    }
}

void Input::OnKeyDown(GLFWwindow* window, int key, int mod)
{
    m_KeyState[key] = KeyState::KeyDown;
}

void Input::OnKeyUp(GLFWwindow* window, int key, int mod)
{
    m_KeyState[key] = KeyState::KeyUp;
}

void Input::OnKeyRepeat(GLFWwindow* window, int key, int mod)
{
    m_KeyState[key] = KeyState::KeyRepeat;
}

这里在Input的构造函数中注册了Window类中的键盘事件委托,然后当Input接受到键盘事件后就把 keystate 数组中对应的键设置为相应的值,当使用 GetKey/GeyKeyDown/GeyKeyUp 的时候再做相应处理即可,如上面代码所示。

可能你已经注意到了,GetKey的几个函数要求传入的是KeyCode类型,它其实是一个自定义的枚举,因为OpenGL中原本的枚举很不好用

KeyCode
#pragma once

#include "GLFW/glfw3.h"

enum KeyCode
{
    A = GLFW_KEY_A,
    B = GLFW_KEY_B,
    C = GLFW_KEY_C,
    D = GLFW_KEY_D,
    E = GLFW_KEY_E,
    F = GLFW_KEY_F,
    G = GLFW_KEY_G,
    H = GLFW_KEY_H,
    I = GLFW_KEY_I,
    J = GLFW_KEY_J,
    K = GLFW_KEY_K,
    L = GLFW_KEY_L,
    M = GLFW_KEY_M,
    N = GLFW_KEY_N,
    O = GLFW_KEY_O,
    P = GLFW_KEY_P,
    Q = GLFW_KEY_Q,
    R = GLFW_KEY_R,
    S = GLFW_KEY_S,
    T = GLFW_KEY_T,
    U = GLFW_KEY_U,
    V = GLFW_KEY_V,
    W = GLFW_KEY_W,
    X = GLFW_KEY_X,
    Y = GLFW_KEY_Y,
    Z = GLFW_KEY_Z,
    BackSpace = GLFW_KEY_BACKSPACE
};

这里我只写了这几个需要用到的键,如果需要更多键,如数字键123,直接到glfw3头文件中拿就行了。

 

实现鼠标移动和鼠标滚轮的监听


上面说了键盘,这里就顺带说一下鼠标的监听吧。

建立委托及设置OpenGL回调
// 委托
std::function<void(GLFWwindow*, double, double)> onMouseMove;
std::function<void(GLFWwindow*, double, double)> onMouseScroll;

// OpenGL回调
void MouseCallback(GLFWwindow* window, double xpos, double ypos)
{
    if (Window::GetInstance().onMouseMove)
        Window::GetInstance().onMouseMove(window, xpos, ypos);
}

void MouseScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
{
    if (Window::GetInstance().onMouseScroll)
        Window::GetInstance().onMouseScroll(window, xoffset, yoffset);
}

glfwSetCursorPosCallback(m_GlfwWindow, MouseCallback);
glfwSetScrollCallback(m_GlfwWindow, MouseScrollCallback);
Input类中实现逻辑
// .h
public:
    std::function<void(GLFWwindow*, float, float)> onMouseMove;
    std::function<void(GLFWwindow*, float, float)> onMouseScroll;
    inline static float GetMouseXPos() { return Input::GetInstance().m_CurMouseXPos; }
    inline static float GetMouseYPos() { return Input::GetInstance().m_CurMouseYPos; }
    inline static float GetMouseXScroll() { return Input::GetInstance().m_CurMouseXScroll; }
    inline static float GetMouseYScroll() { return Input::GetInstance().m_CurMouseYScroll; }
private:
    float m_CurMouseXPos, m_CurMouseYPos;
    float m_CurMouseXScroll, m_CurMouseYScroll;
    void OnMouseMove(GLFWwindow* window, double xpos, double ypos);
    void OnMouseScroll(GLFWwindow* window, double xpos, double ypos);

// .cpp
// 构造函数
Input::Input(...)
{
    m_Window.onMouseMove = std::bind(&Input::OnMouseMove, this, _1, _2, _3);
    m_Window.onMouseScroll = std::bind(&Input::OnMouseScroll, this, _1, _2, _3);
    m_CurMouseXPos = SCREEN_WIDTH / 2;
    m_CurMouseYPos = SCREEN_HEIGHT / 2;
}
void Input::OnMouseMove(GLFWwindow* window, double xpos, double ypos)
{
    m_CurMouseXPos = (float)xpos;
    m_CurMouseYPos = (float)ypos;
    if (onMouseMove)
        onMouseMove(window, (float)xpos, (float)ypos);
}

void Input::OnMouseScroll(GLFWwindow* window, double xoffset, double yoffset)
{
    m_CurMouseXScroll = (float)xoffset;
    m_CurMouseYScroll = (float)yoffset;
    if (onMouseScroll)
        onMouseScroll(window, (float)xoffset, (float)yoffset);
}

 

这样就实现了鼠标和键盘的监听拉,而且使用方式几乎和Unity差不多。

 

 

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像