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

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

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

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

#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 && 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 в классе окна (что я предполагаю) или я должен переместить их во входной класс? Как я могу связать эти два класса, чтобы мне не приходилось всегда использовать указатели окон, например «isKeyDown (окно GLFWwindow *, код клавиши)», а вместо этого только «isKeyDown (код клавиши)». Если это не слишком много, спросите, может ли кто-нибудь написать упрощенный класс ввода?

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

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
4 742
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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

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

Вход.ч

#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(Window& 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;
};

Ввод.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 result;
}

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(Window& 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);
  }
}

Другие вопросы по теме