C++ Check for multiple keyboardinputs

Started by
1 comment, last by Gykonik 8 years, 1 month ago

Hello,

In my game I have a input-manager class, that can check for an keyboard input. Sadly at the moment it only works for one input at the time, but I want it to work for multiple (e.g. press STRG + m, then something will happen)

I don't know, what changes I have to do on my existing class, or even if it works...

I would highly appreciate it, if you could say that to me :)

Here is my Code:

InputManager.h:


#pragma once

#include <unordered_map>
#include <glm/glm.hpp>

namespace Bengine {

// Input manager stores a key map that maps SDL_Keys to booleans.
// If the value in the key map is true, then the key is pressed.
// Otherwise, it is released.
class InputManager
{
public:
    InputManager();
    ~InputManager();

    void update();

    void pressKey(unsigned int keyID);
    void releaseKey(unsigned int keyID);

    void setMouseCoords(float x, float y);

    /// Returns true if the key is held down
    bool isKeyDown(unsigned int keyID);

    /// Returns true if the key was just pressed
    bool isKeyPressed(unsigned int keyID);

    //getters
    glm::vec2 getMouseCoords() const { return _mouseCoords; }
private:
    /// Returns true if the key is held down
    bool wasKeyDown(unsigned int keyID);

    std::unordered_map<unsigned int, bool> _keyMap;
    std::unordered_map<unsigned int, bool> _previousKeyMap;
    glm::vec2 _mouseCoords;
};

}

InputManager.cpp:


#include "InputManager.h"

namespace Bengine {

InputManager::InputManager() : _mouseCoords(0.0f)
{
}


InputManager::~InputManager()
{
}

void InputManager::update() {
    // Loop through _keyMap using a for each loop, and copy it over to _previousKeyMap
    for (auto& it : _keyMap) {
        _previousKeyMap[it.first] = it.second;
    }
}

void InputManager::pressKey(unsigned int keyID) {
    // Here we are treating _keyMap as an associative array.
    // if keyID doesn't already exist in _keyMap, it will get added
    _keyMap[keyID] = true;
}

void InputManager::releaseKey(unsigned int keyID) {
    _keyMap[keyID] = false;
}

void InputManager::setMouseCoords(float x, float y) {
    _mouseCoords.x = x;
    _mouseCoords.y = y;
}

bool InputManager::isKeyDown(unsigned int keyID) {
    // We dont want to use the associative array approach here
    // because we don't want to create a key if it doesnt exist.
    // So we do it manually
    auto it = _keyMap.find(keyID);
    if (it != _keyMap.end()) {
        // Found the key
        return it->second;
    } else {
        // Didn't find the key
        return false;
    }
}

bool InputManager::isKeyPressed(unsigned int keyID) {
    // Check if it is pressed this frame, and wasn't pressed last frame
    if (isKeyDown(keyID) == true && wasKeyDown(keyID) == false) {
        return true;
    }
    return false;
}

bool InputManager::wasKeyDown(unsigned int keyID) {
    // We dont want to use the associative array approach here
    // because we don't want to create a key if it doesnt exist.
    // So we do it manually
    auto it = _previousKeyMap.find(keyID);
    if (it != _previousKeyMap.end()) {
        // Found the key
        return it->second;
    } else {
        // Didn't find the key
        return false;
    }
}

}
Advertisement

You shouldn't have to make any changes.

A keyboard is a stateful thing. The operating system will only generate events when the state of a key has actually changed. So it's boolean in nature.

That being said. You simply check for each key's state individually.

Assuming that Cntrl and K are predefined constants....


InputManager KeyboardState = new InputManager;

if (KeyboardState.isKeyDown(Cntrl) && KeyboardState.isKeyDown(K)) {
    //Do some code
}

This type of checking is done via polling. Now... to reduce the amount of times you need to make calls to the OS. You can request data for all the important keys ahead of time for game controls. SDL does something like this.

Keep in mind that if checks are procedural. When it comes to controls, you'll need your combinations to be checked for success FIRST, and if one is filled, ignore the rest. There could also be better ways.

If you wish for the user to be able to custom assign keys and combinations. You'll probably end up using a completely different method. How I'd approach this is a dynamically adapting if schema.

So:

First lets define the class


// This is very basic, but it theoretically works. Edits are needed for real world use.
Class KeyCombination {
    private:
    int length;
    KeyCheck* StrokesArray;

    public:
    bool PushKeyStroke(const KeyCheck Stroke); //Push stroke to the end of the array
    bool CheckKeys();                          //Checks the Keys to make sure this can be triggered.
    int getLength();                           //What ever completed KeyCombination has the longest length should be triggered.
    
  }

Your structure will look something like so.


const enum KeyState {

KEY_UP
KEY_DOWN

}

struct KeyCheck {
KeyState NeededState
int Key

}

As you can see, you run some simple checks on each struct in the array to make sure that the combination requirements are met. On an unrelated note, you'll need some additional logic to ensure that the correct thing is activtated.

On it's own, a keyed combo that uses Ctrl+k+f will activate:

Cntrl
k
f
Cntrl + k
Cntrl + f
Cuntrl + k + f
f+k

InputManager KeyboardState = new InputManager;

if (KeyboardState.isKeyDown(Cntrl) && KeyboardState.isKeyDown(K)) {
//Do some code
}

This one I tried and I don't know why, but it didn't work...

EDIT:

Now it works, I had a bad mistake in my code :D

Thanks ^^

This topic is closed to new replies.

Advertisement