I like your "has this button been pressed since I last asked?" function, and use something very similar in a general key handling class I use (although that kind of "destructive" function does have to be used with care). However I don't see how that's going to avoid cases where the game will ignore user input.
Taking the same danger case:
Snake is moving upwards, user presses Right then Down really fast such that those events are both received before the next main game loop is called.
Assuming your main loop would have something like:
if (getClick(keyCodeForUp) && (currentDirection != DIR_DOWN))
{
currentDirection = DIR_UP;
}
else if (getClick(keyCodeForDown) && (currentDirection != DIR_UP))
{
currentDirection = DIR_DOWN;
}
else if (getClick(keyCodeForRight) && (currentDirection != DIR_RIGHT))
{
currentDirection = DIR_RIGHT;
}
else if (getClick(keyCodeForLeft) && (currentDirection != DIR_LEFT))
{
currentDirection = DIR_LEFT;
}
// and a switch or something to actually move the player depending on currentDirection
As the inputs came really fast,
getClick(keyCodeForRight) and
getClick(keyCodeForDown) would both return true on this iteration of the main loop.
In this particular instance (and this particular ordering of if/else statements) down is checked first, and denied as the snake's going the opposite direction. Right will then be checked and allowed. So the end result is the snake turns and moves right.
As calling
getClick(keyCodeForDown) causes future calls to it to return false until another key press is detected, that first down key press has been lost. So again we're back to the issue of a valid player input being ignored.
If our if/else statements in the above code happened to be in another order then we'd actually get the correct response from the game. If it checked Right first, then Down it would correctly turn right in one update iteration then down in the next (as it wouldn't call
getClick(keyCodeForDown) on the first iteration, and so wouldn't lose the record of that input). Of course we'd still have the same problem occurring for some other combination of directions and inputs, but it gave me an idea:
If the
getClick function simply returned the value for that key without automatically changing it to false (i.e. make the function call non-destructive) then we can get the behaviour we want without needing to use a proper queue. For simplicity of writing, this code just directly accesses the keyClick array.
if (keyClick[keyCodeForUp] && (currentDirection != DIR_DOWN))
{
currentDirection = DIR_UP;
keyClick[keyCodeForUp] = false;
}
else if (keyClick[keyCodeForDown] && (currentDirection != DIR_UP))
{
currentDirection = DIR_DOWN;
keyClick[keyCodeForDown] = false;
}
else if (keyClick[keyCodeForRight] && (currentDirection != DIR_RIGHT))
{
currentDirection = DIR_RIGHT;
keyClick[keyCodeForRight] = false;
}
else if (keyClick[keyCodeForLeft] && (currentDirection != DIR_LEFT))
{
currentDirection = DIR_LEFT;
keyClick[keyCodeForLeft] = false;
}
// and a switch or something to actually move the player depending on currentDirection
The difference from the previous code is that the record of an input is only removed when that input is acted upon, meaning we don't ever ignore user input. That'll work for our test case, but now that I've typed it out I realise it gives a fresh new error case:
If the player is moving upwards, presses down then presses left with any length of a delay between the two events then they will turn downwards one frame after they turn left, even if the Down event was several seconds earlier. Now we're misinterpreting user input.
What a lot of text, read this bit for the important point:Considering the problem on a slightly higher level,
the order of key presses matters.Our test case is a snake moving up, then the player pressing Right followed by Down with the key events occurring before the next iteration of the main update loop. This should make the snake do a non-suicidal U-turn. However if the player had pressed Down followed by Right, the snake should just turn right. No solution that merely looks at what keys have been pressed since the last update could differentiate between those two cases.
We need a solution that takes the order of key presses into account. A queue seems the clearest solution for that, although others certainly exist.