views:

414

answers:

4

I am writing a Tetris clone in WPF. If I hold down the right arrow key, the current piece shifts right. For playability, I want to allow the user to press another key (i.e. F-key) and rotate the moving piece without having to let go of the right arrow key first. Currently when I do this, the piece stops shifting.

My first basic attempt at this was hooking into

Window_PreviewKeyDown(object sender, KeyEventArgs e)

and then sending a message to the controller layer.

How do I structure my input-listening code to allow this?

My current code Here

+1  A: 

Windows will only repeat events for the last key you pressed down. In addition, the delay before the key is repeated first, as well as the rate it is repeated, depends on settings which are usually optimized for typing and not for gaming. Therefore, for games, it is best to ignore them altogether and have your own way to implement "repeated" keys.

Basically you will have to keep the states (pressed or released) of the keys you are interested and change them when you receive a KeyDown or KeyUp event. Then, in some sort of timer, you will have to check the states and fire events for every key that is down at that moment. If that timer happens in the controller layer or in the UI depends on your architecture :-)

You might add extra checks like when both left and right keys are down to ignore both, as otherwise the piece will jump left and right repeatedly.

mihi
+1  A: 

There should exist a KeyUp-event as well and by using both KeyDown and KeyUp you can achieve the effect you're looking for. It can be done is several ways.

One would be to let your KeyDown event initiate a repeating action and let KeyUp cancel it.

Another would be to store whether or not certain keys are being held down. Keep a list of keys and toggle them on when KeyDown and off on KeyUp. In the games main loop, you'll fire events corresponding to all the keys currently down.

An example:

bool[] heldDown = new bool[256]; // One index for the keycode of each key

void Window_PreviewKeyDown(object sender, KeyEventArgs e) {
    heldDown[e.KeyCode] = true;
}    
void Window_PreviewKeyUp(object sender, KeyEventArgs e) {
    heldDown[e.KeyCode] = false;
}    

And then you go through "heldDown" in your main loop and fire events for every meaningful key that's being held down:

if (heldDown[Key.F])
    MainController.TriggerPieceEvent(Constants.PieceEvents.RotateClockwise);
if (heldDown[Key.Left])
    MainController.TriggerPieceEvent(Constants.PieceEvents.ShiftLeft);
// etc...
Jakob
c++-style array :P
Alex
Oh, right :) Once C, always C I suppose.
Jakob
+1  A: 

You need states. Create some variables like 'RightArrowPressed' and 'RotateButtonPressed'

in Key_Down check the key is pressed, if it is, set the corresponding variable to true.

In key_up, do the same but set them to false.

Now at any point you can look at the two variables and see which of the keys are pressed.

SLC
+3  A: 

You are relying on the key auto-repeating to keep the piece moving. That's not appropriate, the user's keyboard repeat delay is variable and will affect the playability of the game. And as you found out, it stops auto-repeating when another key is pressed.

Fix this by giving each game object as motion status. Use the KeyDown event to change it. And the KeyUp event to reset it, if the motion status value still matches the key. In your game loop, update the game object positions based on their motion status. You'd do so at a fixed rate, triggered by a timer.

The motion status should also indicate "falling down". You can now simply make the game more difficult as the levels progress by reducing the timer rate. Pieces fall down quicker. And the keyboard becomes more responsive, giving the user a chance to still win a level.

Hans Passant