GetRawInputData vs GetAsyncKeyState()
Well, I'm trying to avoid using the deprecated DirectInput.
But I need, at each "frame" or "iteration" of the game to snatch ALL KEY STATES so that I can act accordingly. For example, if the player is down on the VK_RIGHT key then he will move just a smidgen right on that frame.
The problem with WM_INPUT messages is they can appear an unpredictable number of times per frame, because of the way the game loop is written:
MSG message ; while( 1 ) { if( PeekMessage( &message, NULL, 0, 0, PM_REMOVE ) ) { if( message.message == WM_QUIT ) { break ; // bail when WM_QUIT } TranslateMessage( &message ) ; DispatchMessage( &message ) ; } else { // No messages, so run the game. Update() ; Draw() ; } }
So if more than one WM_INPUT message is stacked there then they will all get processed before Update()/Draw().
I resolved this issue by using an array of BOOL to remember what keys were down:
bool array_of_keys_that_are_down[ 256 ] ; case WM_INPUT : if( its keyboard input ) { array_of_keys_that_are_down[ VK_CODE ] = TRUE ; }
That works fine because the Update() function checks
void Update() { if( array_of_keys_that_are_down[ VK_RIGHT ] ) { // Move the player right a bit } }
BUT the problem is now that WM_INPUT messages don't get generated often enough. There's a delay of about 1 second between the first press of VK_RIGHT and subsequent VK_RIGHT messages, even if the player had his finger down on it the whole time. Its not like DirectInput where you can keyboard->GetDeviceState( 256, (void*)array_of_keys_that_are_down );
(snatch out all key states each frame with a single call)
So I'm lost. Other than resorting to GetAsyncKeystate() function calls for each key I need to monitor, I see no way to avoid using DirectInput if you can't snatch out all key states each frame reliably.
It seems to me that DirectInput was a very good solution to this problem, but if it was deprecated, then there really must be some way to do this conveniently using Win32 api only.
Currently array_of_keys_that_are_down
gets reset back to all FALSE's every frame.
memset( array_of_keys_that_are_down, 0, sizeof( array_of_keys_that_are_down ) ) ;
*EDIT
I've been working on this problem and one solution is to only reset a key state, once its been released
case WM_INPUT : if( its keyboard input ) { if( its a down press ) array_of_keys_that_are_down[ VK_CODE ] = TRUE ; else array_of_keys_that_are_down[ VK_CODE ] = FALSE ; }开发者_StackOverflow中文版
I don't like this solution though because it seems flimsy. If the user switches away from the application while down on a key, then that key will be "stuck" until he switches back and presses that same key again because we'll never get the upstroke WM_INPUT message. It makes for weird "sticky key" bugs.
You can use GetKeyboardState
instead. What you generally want is two arrays; one stores the previous frames' input state, and one stores the current. This allows things like differentiating between being held and being triggered.
// note, cannot use bool because of specialization
std::vector<unsigned char> previous(256);
std::vector<unsigned char> current(256);
// in update_keys or similar:
current.swap(previous); // constant time, yay
GetKeyboardState(¤t[0]); // normally do error checking
And you're done.
The presented solution is the right way to do it -- ignore autorepeat, and just record down/up states.
To handle the task switching problem, look into the WM_ACTIVATE
message -- it lets one detect when a window loses focus. When this happens to the relevant window, assume all keys become released. (This is similar to what one would have to do with DirectInput when using the nonexclusive cooperative level.)
As you said there's a delay, I am under the impression that you want to decrease the delay, why not call the 'SystemParametersInfo' to set the typematic delay and speed rate, you would need to look at the keyboard delay..'SPI_GETKEYBOARDDELAY' and keyboard speed 'SPI_GETKEYBOARDSPEED'. The function would look like this:
int SetKeyboardSpeed(int nDelay){ /* fastest nDelay = 31, slowest nDelay = 0 */ return (SystemParametersInfo(SPI_SETKEYBOARDSPEED, nDelay, NULL, SPIF_SENDCHANGE) > 0); } int SetKeyboardDelay(int nDelay){ /* 0 = shortest (approx 250ms) to 3 longest (approx 1sec) */ return (SystemParametersInfo(SPI_SETKEYBOARDDELAY, nDelay, NULL, SPIIF_SENDCHANGE) > 0); }
Edit: In response to Blindy's comment for the downvote - You are 100% correct - Never realized that as I typed the code into this...and yes, you've put a finger on it, Never ever change global/system-wide settings without the user ever knowing about! I stand corrected by Blindy's comment. Please disregard my answer as it is 100% wrong!
Hope this helps, Best regards, Tom.
- use Window messages to detect keypresses, and track the array of pressed keys yourself.
- watch for the WM_ACTIVATE message and use GetAsyncKeyboardState at that point to 'fix' the array to match the actual state.
That combination should keep things consistent.
The best(in terms of consistency of results and efficiency) solution to your problem is to use raw input. Event-based, fast, efficient, no chances of missing input.
You can miss inputs with GetAsyncKeyState calls. For example, let's say at the start of a game iteration working at 60 Hz you do a call to GetAsyncKeyState and key is not pressed. So far so good. Then 5 ms later you press a key, let's say VK_TAB, and hold it for 5ms. Then at the start of the next game iteration(about ~6.67ms later) you call GetAsyncKeyState again. But by that time the key is not pressed again. And from the game's perspective it was never pressed! It might look like too far reaching, but it's not. I played games which use this system and miss inputs at 60 FPS. Frustrating and unnecessary.
I've been working on this problem and one solution is to only reset a key state, once its been released
case WM_INPUT : if( its keyboard input ) { if( its a down press ) array_of_keys_that_are_down[ VK_CODE ] = TRUE ; else array_of_keys_that_are_down[ VK_CODE ] = FALSE ; }
I don't like this solution though because it seems flimsy. If the user switches away from the application while down on a key, then that key will be "stuck" until he switches back and presses that same key again because we'll never get the upstroke WM_INPUT message. It makes for weird "sticky key" bugs.
This is fixed with a RIDEV_INPUTSINK flag in the RAWINPUTDEVICE structure when you register your raw input device: you get your messages even when your window is not in foreground.
It looks like your problem isn't in the APIs you're using. You want a specific way of interacting with the system:
But I need, at each "frame" or "iteration" of the game to snatch ALL KEY STATES so that I can act accordingly. For example, if the player is down on the VK_RIGHT key then he will move just a smidgen right on that frame.
The problem with WM_INPUT messages is they can appear an unpredictable number of times per frame, because of the way the game loop is written.
I resolved this issue by using an array of BOOL to remember what keys were down.
You want to know key states when you check for them, so write your own input handling layer. You want polling-based system, so make KeyboardStateHandler class, make it to answer to all the key press and key release raw input events, and then in your game loop, you call keyboardStateHandler.GetKeys() and get states of all keys.
I think what you really want to do is to create an appropriate robust input handling layer, that would be the solution to all of your problems you raised above.
Here are just a few:
https://bell0bytes.eu/inputsystem/ Web archive link
https://www.gamedev.net/blog/355/entry-2250186-designing-a-robust-input-handling-system-for-games/ Web archive link
https://blog.gemserk.com/2012/08/23/decoupling-game-logic-from-input-handling-logic/ Web archive link
Doesn't matter what you use to actually get the input: GetKeyAsyncState, DirectInput, Raw Input or other methods, you want to separate input handling from game logic.
精彩评论