0
\$\begingroup\$

Determining if a key is currently down is easy. Just return keys[scancode]:

switch (ev.type) {
    case SDL_KEYDOWN:
        keys[ev.key.keysym.scancode] = true;
        break;
    case SDL_KEYUP:
        keys[ev.key.keysym.scancode] = false;
        break;
}

Determining if a key was pressed or released this frame is another thing. I just don't know where to start here. It would be accessed by keyPressed(scancode) / keyReleased(scancode), what's a good way to do this?

Of course it would also be easy to do if I just handled it inside of the event handler itself but I'm just using functions anything can call to check the keys.

\$\endgroup\$

3 Answers 3

4
\$\begingroup\$

Store the keys from the last frame and compare in the current frame.

if(!old_keys[key] && keys[key]) { /* pressed since last frame */ } 
if(old_keys[key] && !keys[key]) { /* released since last frame */ } 
if(old_keys[key] && keys[key]) { /* held since last frame */ }
\$\endgroup\$
5
  • \$\begingroup\$ Wouldn't this give a 1-frame input lag? \$\endgroup\$ Jul 26, 2015 at 3:23
  • \$\begingroup\$ It would, but unless you're running at a very low framerate, it's not going to be noticeable. If you need better than that, you should look into getting input in a separate thread with a higher polling rate, but at that point you should consider if the gains are worth the effort. \$\endgroup\$
    – William
    Jul 26, 2015 at 3:26
  • \$\begingroup\$ Would you perhaps know if the NES/SNES did it like this? I'm trying to create a C++ engine that's as close to NES/SNES as possible. \$\endgroup\$ Jul 26, 2015 at 3:28
  • \$\begingroup\$ I could only guess. But I doubt it'll make a noticeable difference. As I recall from my childhood playing NES/SNES, I rarely tried to press more often than the framerate on screen. \$\endgroup\$
    – William
    Jul 26, 2015 at 3:40
  • 4
    \$\begingroup\$ Actually, there would be no input lag with the above solution. The events are still happening against the current key state. You are just comparing with the previous state to see if the key was released or held. What would be true is that a key press AND release cannot happen in the same frame. So if you are running at 30fps, you can detect 15 key presses, 15 key ups and 30 key holds per second, assuming you need all 3 event types. \$\endgroup\$ Jul 26, 2015 at 5:22
0
\$\begingroup\$

If you have to handle a large set of keys have a look at:

SDL_GetKeyState

which gives you a snapshot of the state of the whole keyboard.

\$\endgroup\$
0
\$\begingroup\$

Instead of true/false I use a value of 2/-2 for a recent change (2 for a new press, -2 for a new release), then at the beginning of the loop, before going through the new events I go through the whole array and change every 2 or -2 to 1 or -1. I use 3 in case of a key repeat, so I can handle all those different things in the rest of my code smoothly. So if I test the key for ==2 it means only new presses, if I test for >=2 it means new presses and repeats, >=1 is if it's down at all and so on.

But if you don't want to directly use numbers (probably best not to if other people need to understand your code) you can use the following enum:

enum
{
    KEYUP_NEW = -2,
    KEYUP,
    KEY_UNINIT,
    KEYDOWN,
    KEYDOWN_NEW,
    KEYDOWN_REPEAT
};

and then you only have to test for if (keys[key]==KEYDOWN_NEW) instead of the less readable if (!old_keys[key] && keys[key]) whose meaning isn't as instantly obvious. You can even go one better by having a function do the test for you so that test_key(key, KEYDOWN) or is_key_down(key) can return positive whether the value is KEYDOWN, KEYDOWN_NEW or KEYDOWN_REPEAT if you don't want to use the less obvious if (keys[key]>=KEYDOWN).

But there's a big problem with your approach, it's that you'll miss keypresses that last shorter than your average loop because in those cases you'll have a down event instantly followed by a up event, and therefore your table as seen from outside of the event polling loop will miss the keypress entirely. I have a solution for this, it's to have another table and when you detect a down event while your entry in your table is 2 then don't change it to -2 but rather set the matching entry in that other table to 1. This way for that one iteration of the loop your key is set to 2, which is what you want. Then when the next loop starts, at the same time as you turn 2s into 1s you check that other table and if it's set to 1 then you turn the 2 into a -2 so that you signal the release.

\$\endgroup\$
4
  • \$\begingroup\$ Why are you downvoting me, I'm right! \$\endgroup\$ Nov 24, 2018 at 9:20
  • 1
    \$\begingroup\$ I'm not the downvoter, but using magic numbers doesn't seem like a very great solution. \$\endgroup\$
    – user35344
    Nov 24, 2018 at 15:52
  • 1
    \$\begingroup\$ As my second paragraph points out the other answers have a flaw (very significant at low FPS and even worse if you use remote desktoping which can send the key release event without delay after the key press event), and having to test for only one thing feels more elegant to me. You can always use an enum and have something like KEYUP_NEW, KEYUP, KEYDOWN, KEYDOWN_NEW, KEYDOWN_REPEAT. I have an old habit of using those numbers for all sorts of flags so it doesn't look weird to me. if (keys[key]==KEYDOWN_NEW) seems more clear than if(!old_keys[key] && keys[key]) to me. \$\endgroup\$ Nov 25, 2018 at 8:07
  • 1
    \$\begingroup\$ This comment is 100% correct - you will run into problems with the accepted solution in certain scenarios that this solution solves. \$\endgroup\$
    – Andy Baird
    Apr 15, 2023 at 4:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .