Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Initialize a Container of sf::Keyboard:Key and bool pairs  (Read 2872 times)

0 Members and 1 Guest are viewing this topic.

MasterYi

  • Newbie
  • *
  • Posts: 5
    • View Profile
Initialize a Container of sf::Keyboard:Key and bool pairs
« on: April 11, 2021, 07:08:18 pm »
Hello, this is my first post here, so I hope I am able to format everything correctly.

I am looking to initialize a container that holds pairs of sf::Keyboard:Key and bool, which is intended to track the state of each key pressed using the event loop.  I am doing this in order to allow for checking if multiple key combinations are pressed (for example, Ctrl-F is pressed), in an InputManager class.

I saw this post: https://stackoverflow.com/questions/17888993/key-repetition-in-sfml-2-0
which has a great idea to initialize an std::array<bool, sf::Keyboard::KeyCount> and then fill it with false.

However, I can not determine how to do this within a class initialization / method.  I want the InputManager to have a private member variable like this, that I fill with false.  Ideally, this class would have only static members to make it easier to use throughout the application.

I tried the following:

class InputManager {
private:
        static std::array<bool, sf::Keyboard::KeyCount> keyState;
public:
        static void initializeKeyStates();
};
 

However, when I try to use "keyState", it complains about incomplete type.  For example, in InputManager.cpp, I have:

void InputManager::initializeKeyStates()
{
        keyState.fill(false);   // Error here, incomplete type is not allowed
}
 

I realize this may be more of a "std::array" question.  But, I also considered using an std::map<sf::Keyboard::Key, bool>, but then actually inserting every sf::Keyboard::Key becomes very tedious -- I can't determine how to insert the pairs via a loop.

Any help on this is greatly appreciated -- having this datastructure in my InputManager will make everything much easier for my game!

Please let me know if I'm missing anything important in this post  :)

EDIT:  I want to also clarify a few things in the above post --
1.  Using sf::Keyboard::isKeyPressed() is not appropriate for my goal, as I have items that I want to check for using events so that it only happens once (I have window.setKeyRepeatEnabled(false)).
2.  The first usage of this (as a proof of concept) will be toggling an FPS display.  I have that working currently via the standard event loop, like this:
    bool control_pressed = false;
    while (window.isOpen()) {
        window.clear();

        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) {
                window.close();
            }
            if (event.type == sf::Event::KeyPressed) {
                if (event.key.code == sf::Keyboard::F) {
                    if (control_pressed) {
                        fpsDisplayText.shouldToggle = true;
                    }
                } else if (event.key.code == sf::Keyboard::LControl) {
                    control_pressed = true;
                }
            }
            if (event.type == sf::Event::KeyReleased) {
                if (event.key.code == sf::Keyboard::LControl) {
                    control_pressed = false;
                }
            }
        }

        fpsDisplayText.checkToggle(); // this will determine if "shouldToggle" and then toggle
                                       // and set "shouldToggle" to false if it toggled

   .
   .
   ...
 
« Last Edit: April 11, 2021, 07:32:53 pm by MasterYi »

MasterYi

  • Newbie
  • *
  • Posts: 5
    • View Profile
Re: Initialize a Container of sf::Keyboard:Key and bool pairs
« Reply #1 on: April 11, 2021, 08:15:14 pm »
For anyone else wondering about this, I did find a reasonable answer for use cases where you only need to check "ctrl", "alt", "shift", or "system" key presses combined with another key.

Instead of using manual bools at all, one can simply do the following:

        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) {
                window.close();
            }
            if (event.type == sf::Event::KeyPressed) {
                if (event.key.code == sf::Keyboard::F) {
                    if (event.key.control) {
                        fpsDisplayText.shouldToggle = true;
                    }
                }
            }
        }
 

This is straight from the wonderful documentation provided by the SFML team:
https://www.sfml-dev.org/documentation/2.5.1/structsf_1_1Event_1_1KeyEvent.php

My question still stands for other key combinations, though.  Ideas are very much appreciated.

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10914
    • View Profile
    • development blog
    • Email
Re: Initialize a Container of sf::Keyboard:Key and bool pairs
« Reply #2 on: April 12, 2021, 05:59:03 pm »
Personally, I'd probably use an unordered_map on the Key enum and a boolean to represent the key state.
Plus x bools for modifier keys.
Then for each key event (pressed/released) update the map and modifier key.

That way you can later on check the map and find all active keys and modifiers.

Alternatively, you can look at Thor's ActionMap which does a similar thing already as far as I know: https://bromeon.ch/libraries/thor/tutorials/v2.0/actions.html
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

MasterYi

  • Newbie
  • *
  • Posts: 5
    • View Profile
Re: Initialize a Container of sf::Keyboard:Key and bool pairs
« Reply #3 on: April 13, 2021, 12:16:22 pm »
Thank you for the reply eXpl0it3r!  I've lurked without being a member on this forum for a minute, and it's cool to have one of the main SFML contributors reply on your post  8)

That does sound like a good idea.  For others looking at this in the future, this is what the initializeKeys() function looks like, using this idea:

// in InputManager.h the map looks like:
static std::unordered_map<sf::Keyboard::Key, bool> keyStateMap;

// in the .cpp file:

      // required to instantiate the static unordered_map (must put this line in the cpp)
std::unordered_map<sf::Keyboard::Key,bool> InputManager::keyStateMap;

void InputManager::initializeKeyState() {
    for (int keyInt = sf::Keyboard::Key::A; keyInt != sf::Keyboard::Key::KeyCount; keyInt++)  {
        sf::Keyboard::Key key = static_cast<sf::Keyboard::Key>(keyInt);
        keyStateMap[key] = false;
    }
}
 

@expl0it3r, this solution does somewhat rely on the sf::Keyboard::Key enum being stable -- but the items in question probably would not move (sf::Keyboard::Key::A, and sf::Keyboard::Key::KeyCount) -- keyCount for obvious reasons, and A I would think would not move.

Is there any better way off the top, that one could iterate the Key enum?

Thanks again, big help!!  :)
« Last Edit: April 13, 2021, 02:48:45 pm by MasterYi »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10914
    • View Profile
    • development blog
    • Email
Re: Initialize a Container of sf::Keyboard:Key and bool pairs
« Reply #4 on: April 13, 2021, 12:38:52 pm »
@expl0it3r, this solution does somewhat rely on the sf::Keyboard::Key enum being stable -- but the items in question probably would not move (sf::Keyboard::Key::A, and sf::Keyboard::Key::KeyCount) -- keyCount for obvious reasons, and A I would think would not move.
Why?
Are you not accessing the map with the same enum values?
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

MasterYi

  • Newbie
  • *
  • Posts: 5
    • View Profile
Re: Initialize a Container of sf::Keyboard:Key and bool pairs
« Reply #5 on: April 13, 2021, 02:07:52 pm »
Quote
Why?
Are you not accessing the map with the same enum values?

Well, I am -- the idea is that if A was reordered to come after numbers, for example, then the loop above would start iterating at a later "index".

But, that's an easy fix, which is to make the loop like this (just change the initial int to 0 instead of sf::Keyboard::Key::A)

void InputManager::initializeKeyState() {
    for (int keyInt = 0; keyInt != sf::Keyboard::Key::KeyCount; keyInt++)  {
        sf::Keyboard::Key key = static_cast<sf::Keyboard::Key>(keyInt);
        keyStateMap[key] = false;
    }
}
 

And in fact, this function is probably unnecessary with an unordered_map, as iterating the event loop / accessing values in the map will populate it --- so prefilling probably isn't necessary.  But, maybe the code in this thread will help someone just looking to iterate the Key enum, etc.

I can see the light now.

Thanks again eXpl0it3r
« Last Edit: April 13, 2021, 02:24:51 pm by MasterYi »

kojack

  • Sr. Member
  • ****
  • Posts: 331
  • C++/C# game dev teacher.
    • View Profile
Re: Initialize a Container of sf::Keyboard:Key and bool pairs
« Reply #6 on: April 13, 2021, 05:29:19 pm »
Your solution has changed, but I'll have a guess at the original issue in case it comes up elsewhere.
Are you defining the keyState array in the cpp file? You have a declaration in the .h, but static members are a little different to other variables, they also need a matching definition to allocate the memory for them.
For example, if you had this:
// test.h
#include <arrray>
class Test
{
   public:
   Test();
   static std::array<bool,sf::Keyboard::KeyCount> keyState;
};
// test.cpp
#include "test.h"
Test::Test()
{
   keyState.fill(false);
}
The fill line will fail to compile because while keyState is known, it's memory hasn't been defined. You need to do one of two possible things:
1 - Define it in the cpp like this:  std::array<bool,sf::Keyboard::KeyCount> Test::keyState;
2 - If you are using C++17 or above, you can put the word "inline" before the static in the header. Now you don't need the definition in the cpp file.

Or maybe you already had the definition and it's a different issue, in which case never mind. :)

MasterYi

  • Newbie
  • *
  • Posts: 5
    • View Profile
Re: Initialize a Container of sf::Keyboard:Key and bool pairs
« Reply #7 on: April 14, 2021, 01:19:31 am »
Thanks kojack.  This turned out to be a more layered question than I originally thought, but at this point, I think all parts of it have been clarified and answered, thanks to your reply filling the last gap.  If there are any questions about any of the topics here that aren't clear, though, I'd be happy to discuss.


=== The Question Parts We've Answered ===

1.  The wall I hit implementing this code originally, was what you point out.  I had the static variable, without the instantiation in the .cpp file.  For many other static variables throughout the application, I had used inline, but couldn't manage to make it work in this case (although I bet there is syntax that could work).  In the original question, the .cpp instantiation is not there, but by a later time, I had discovered the answer and posted my finding as a reply, albeit with a different data structure -- but I didn't clarify that the same would have worked for the static std::array (so thanks for pointing it out).

2.  I also wanted to inquire about other possible ways to make this work in case I was looking at the problem wrong or reinventing the wheel.  With the discovery of the "event.key.control" and the like, a simple use case became evident, which will probably suffice for most people (ctrl,alt,shift,system).

3.  Third, is what kind of datastructure is ideal to tackle the "every key" goal.  I do think that the unordered map is clean and expressive, and also allows for 'at-reference-instantiation' of values (so that init isn't even necessary -- when you reference a key (as in key-value-pair key, not sfml key) in an unordered map that doesn't exist in that map, a default value will be created at the key).  Shout out to eXpl0it3r

4.  How to iterate the sf::Keyboard::Key enum.  Which, is also solved!


So, I thank everyone for their contribution, and I believe (and hope) that this thread may help others.  Also hidden here is a demonstration of how to implement a toggle in a game loop via the "checkToggle" function which I didn't include all of the code for, but did briefly explain via a comment.


Thanks again everyone  :D
« Last Edit: April 14, 2021, 01:38:23 am by MasterYi »

 

anything