Как мне создать правильный класс ввода в GLFW для игрового движка

#c #opengl #input #game-engine #glfw

#c #opengl #ввод #игровой движок #glfw

Вопрос:

Я занимаюсь своим небольшим проектом, в котором я в основном создаю свой собственный игровой движок / фреймворк на c для создания графики и / или простых игр. Я использую OpenGL с GLFW. Моя цель — создать что-то похожее на различные графические фреймворки, такие как raylib или openFrameworks (но, конечно, урезанное)

На самом деле пока все работает нормально, но я не могу понять, как правильно отделить ввод от класса window, поскольку ввод дескриптора окна кажется мне довольно неуклюжим и просто загромождает класс window.

Это быстрое упрощенное воссоздание моего класса window. (Я не включил класс enum с кодами ключей.)

 #pragma once
#include "../extern/GLFW/glfw3.h"
#include <string>
class Window {
private:
    GLFWwindow* mWindow;
    int mWidth;
    int mHeight;
    std::string mTitle;

public:
    Window();
    ~Window();

    void createWindow(std::string title, int width, int height);
    void mainLoop();

    GLFWwindow* getWindow() const { return mWindow; }


// Input
private:

    bool Window::getKeyStatus(KEY key) {
    static void keyCallback(GLFWwindow* mWindow, int key, int scancode, int action, int mods);
    bool isKeyDown(KEY key);
};
  

И это плюс реализации

 #include "Window.h"
#include <iostream>

Window::Window() {}
Window::~Window() {}

void Window::createWindow(std::string title, int width, int height) {
    if (!glfwInit());
    mWindow = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr);

    if (!getWindow()) {
        glfwTerminate();
    }


    glfwSetWindowUserPointer(getWindow(), this);
    glfwMakeContextCurrent(getWindow());


    glfwSetKeyCallback(mWindow, keyCallback);
}

void Window::mainLoop() {
    while (!glfwWindowShouldClose(getWindow())) {
        /* Render here */
        glClear(GL_COLOR_BUFFER_BIT);

        /* Swap front and back buffers */
        glfwSwapBuffers(getWindow());

        /* Poll for and process events */
        glfwPollEvents();


        if (isKeyDown(KEY::A)) {
            std::cout << "A down" << std::endl;
        }
        if (isKeyDown(KEY::B)) {
            std::cout << "B down" << std::endl;
        }

    }

    glfwTerminate();
}

void Window::keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
    Window* win = (Window*)glfwGetWindowUserPointer(window);

    if (key == (int)KEY::ESCAPE amp;amp; action == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, GL_TRUE);
    } else {
        win->currentKeyState[key] = action;
    }
}

bool Window::getKeyStatus(KEY key) {
    return glfwGetKey(mWindow, (int)key);
}
bool Window::isKeyDown(KEY key) {
    bool down = false;
    if (getKeyStatus(key) == 1) {
        down = true;
    }
    return down;
}
  

Как мне из этого исходить? Моя главная проблема в том, что я, похоже, не могу подключить свое окно и класс ввода. Должен ли я использовать классы наследования или друзей. Должен ли я иметь обратные вызовы glfw в классе window (который я предполагаю) или я должен переместить их в класс ввода? Как я могу соединить эти два класса, чтобы мне не приходилось всегда использовать указатели на окна, такие как «IsKeyDown (GLFWwindow * window, Key keycode)», а вместо этого только «IsKeyDown (Key keycode)». Если это не слишком много, спросите, может ли кто-нибудь написать упрощенный класс ввода?

Заранее спасибо

Ответ №1:

Следует отметить, что GLFW обеспечивает обработку ввода как на основе опроса, так и на основе обратного вызова. В вашем примере вы используете оба: опрос для обработки клавиш A и B и обратный вызов для обработки клавиши escape. Я полагаю, что вы обнаружите, что с обратным вызовом будет проще работать при создании отдельного класса ввода (как для клавиатуры, так и для мыши).

Существует много способов создания класса KeyInput с использованием метода обратного вызова GLFW. Вот мой предпочтительный метод (написанный в соответствии с вашим кодом). Это позволяет использовать несколько экземпляров KeyInput (в отличие от singleton), что означает, что у вас может быть экземпляр KeyInput для ключей пользовательского интерфейса, экземпляр для игровых ключей и т. Д.

Вкратце: каждый экземпляр KeyInput отслеживает нажатое состояние списка клавиш, определенного пользователем при создании. У него есть средство получения и установки для доступа к нажатому состоянию для любой клавиши (хотя средство получения и установки работают только с отслеживаемыми клавишами). Всякий раз, когда вызывается GLFW key-input-callback (статический), он вызывает этот установщик для всех экземпляров KeyInput . Экземпляры хранятся в статическом векторе, который добавляется при создании и удаляется при уничтожении.

Input.h

 #include "../extern/GLFW/glfw3.h"
#include <map>
#include <vector>

class KeyInput {
  // Main KeyInput functionality
  public:
    // Takes a list of which keys to keep state for
    KeyInput(std::vector<int> keysToMonitor);
    ~KeyInput();
    // If this KeyInput is enabled and the given key is monitored,
    // returns pressed state.  Else returns false.
    bool getIsKeyDown(int key);
    // See _isEnabled for details
    bool getIsEnabled() { return _isEnabled; }
    void setIsEnabled(bool value) { _isEnabled = value; }
  private:
    // Used internally to update key states.  Called by the GLFW callback.
    void setIsKeyDown(int key, bool isDown);
    // Map from monitored keyes to their pressed states
    std::map<int, bool> _keys;
    // If disabled, KeyInput.getIsKeyDown always returns false
    bool _isEnabled;

  // Workaround for C   class using a c-style-callback
  public:
    // Must be called before any KeyInput instances will work
    static void setupKeyInputs(Windowamp; window);
  private:
    // The GLFW callback for key events.  Sends events to all KeyInput instances
    static void callback(
      GLFWwindow* window, int key, int scancode, int action, int mods);
    // Keep a list of all KeyInput instances and notify them all of key events
    static std::vector<KeyInput*> _instances;
};
  

Input.cpp

 #include "KeyInput.h"
#include <algorithm>

std::vector<KeyInput*> KeyInput::_instances;

KeyInput::KeyInput(std::vector<int> keysToMonitor) : _isEnabled(true) {
  for (int key : keysToMonitor) {
    _keys[key] = false;
  }
  // Add this instance to the list of instances
  KeyInput::_instances.push_back(this);
}

KeyInput::~KeyInput() {
  // Remove this instance from the list of instances
  _instances.erase(std::remove(_instances.begin(), _instances.end(), this), _instances.end());
}

bool KeyInput::getIsKeyDown(int key) {
  bool result = false;
  if (_isEnabled) {
    std::map<int,bool>::iterator it = _keys.find(key);
    if (it != _keys.end()) {
      result = _keys[key];
    }
  }
  return resu<
}

void KeyInput::setIsKeyDown(int key, bool isDown) {
  std::map<int,bool>::iterator it = _keys.find(key);
  if (it != _keys.end()) {
    _keys[key] = isDown;
  }
}

void KeyInput::setupKeyInputs(Windowamp; window) {
  glfwSetKeyCallback(window.getWindow(), KeyInput::callback);
}

void KeyInput::callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
  // Send key event to all KeyInput instances
  for (KeyInput* keyInput : _instances) {
    keyInput->setIsKeyDown(key, action != GLFW_RELEASE);
  }
}