Skip to main content
GameDev.net gamedev.net
🔒 Locked

XNA Keyboard Input For Tetris At 1key/sec?

Started by Intrawebs Mar 10, 2009 at 5:07 PM 10 replies 5.9k views
Original Post
Intrawebs
Intrawebs
I have the following code for my Tetris game within the Update method. I read that this method gets called 60 times/sec as a default. However, it seems I have to actually hold down the key vs. simply pressing it to get my game block to move. Should I be getting input in some other way?

           KeyboardState newState = Keyboard.GetState();

            if (newState.IsKeyDown(Keys.Escape))
            {
                UnloadContent();
                this.Exit();
            }

            if (newState.IsKeyDown(Keys.Down))
                Move(0, 1);
            if (newState.IsKeyDown(Keys.Up))
                RotateBlock();
            if (newState.IsKeyDown(Keys.Left))
                Move(-1, 0);
            if (newState.IsKeyDown(Keys.Right))
                Move(1, 0);
My Games
XNA Quicks
Michael_B
Michael_B
The Update method is the correct place for your input handler. I don't understand your question completely. Holding the key down to move the shape is standard Tetris behavior. What do you mean with simply pressing it?
Intrawebs
Intrawebs
Quote:
Original post by Michael_B
The Update method is the correct place for your input handler. I don't understand your question completely. Holding the key down to move the shape is standard Tetris behavior. What do you mean with simply pressing it?


When I press the left arrow, nothing happens. I have to hold it down for at least 1 second before IsKeyDown(Keys.Left) becomes TRUE. It's as if the Keyboard state isn't changing/updating at the same rate or faster than Update is being called.
My Games
XNA Quicks
BASSOFeeSH
BASSOFeeSH
Can you confirm that the chunk of code you posted is actually being called more than once a second?
-- What would Sweetness do?
Michael_B
Michael_B
You can set and verify the timing settings in the constructor of your Game1.cs like this:

IsFixedTimeStep = true;                                     // This is the default settingConsole.WriteLine(TargetElapsedTime.TotalMilliseconds);     // Should be 1000 / 60


However, the default setting would be much too fast for your Tetris. You should slow it down to say 20 Hz.

// This is somewhere at your class level.private const double RepeatRateHz = 1000 / 20;private double keyRepeatRate = RepeatRateHz;// This is in Update().double elapsedTime = gameTime.ElapsedGameTime.TotalMilliseconds;keyRepeatRate -= elapsedTime;if (elapsedTime < 0){    keyRepeatRate = RepeatRateHz;    // Trigger your action...}
Moe
Moe
What you probably want to be doing is checking the new keyboard state and compare it to the state from the previous frame. That will allow you to determine if a key has been pressed since last time you checked.

Take a look at the XNA Game State Management sample, here.
Intrawebs
Intrawebs
Quote:
Original post by Michael_B
However, the default setting would be much too fast for your Tetris. You should slow it down to say 20 Hz.


20 was still too fast, 10 is much better, but, see below....


So, my original problem was caused by a hidden line of code, my fault "TargetElapsedTime = new TimeSpan(0, 0, 1);" (I'm hidding in a corner now lol). However, the problem Michael_B thought I had I actually do have. See below for whats happening now. Basically, I think XNA is missing my keypress's.


PROBLEM: A .1 second TimeSpan between checks of a keypress does prevent the problem of a single keypress moving a tetris piece at light speed, however, it seems to miss a keypress very eaisly.

Also, Setting the TimeSpan between checks of a keypress to .08 of a second instead of .1 is too fast as UpdateInput() gets called too often for a single key press, making the piece move too fast. No matter what, it just cant come close as the key press causes the piece to move too fast, or, miss too many key press's.

I know it sounds nit picky, but, a tetris piece move should respond instantly without a noticable delay to the user.

        private const double RepeatRateHz = 1000 / 10;        private double _keyRepeatRate = RepeatRateHz;        protected override void Update(GameTime gameTime)        {            double elapsedTime = gameTime.ElapsedGameTime.TotalMilliseconds;            _keyRepeatRate -= elapsedTime;	    //get keypresses for moving a block left,right,down and up for rotate shape            if (_keyRepeatRate < 0)            {                _keyRepeatRate = RepeatRateHz;                UpdateInput();            }	    //setup the game            if (!_gameStarted)            {                NewGame();                _gameStarted = true;                _startTime = gameTime.TotalGameTime;            }	    //standard tetris "move a block down every 1 second"            else if (gameTime.TotalGameTime.TotalSeconds - _startTime.TotalSeconds > 1)            {                Move(0, 1);                _startTime = gameTime.TotalGameTime;            }            base.Update(gameTime);        }
My Games
XNA Quicks
Michael_B
Michael_B
Quote:
Original post by Intrawebs
I know it sounds nit picky, but, a tetris piece move should respond instantly without a noticable delay to the user.

This is not nit picking, you're trying to implement the proper game mechanics of Tetris. Small details like this separate a mediocre from a good game.

To solve the problem you need, like Moe said, a snapshot of the keyboard state from the previous frame. This snapshot is needed to detect freshly pressed keys.

As you probably already know, there is no need to modify the default timing settings to implement special input behaviors for a Tetris like game. The default settings save you from a lot of headaches.

// Somewhere at your class level.private KeyboardState oldKeyboardState = Keyboard.GetState();// In Update().// Poll keyboard state with 60 Hz (default).KeyboardState keyboardState = Keyboard.GetState();// Process left key.bool leftKeyPressed = false;if (keyboardState.IsKeyDown(Keys.Left)){    // Key newly pressed?    if (oldKeyboardState.IsKeyUp(Keys.Left))    {        // Trigger action immediately, preset key repeat rate.        leftKeyPressed = true;        keyRepeatRate = RepeatRateHz;    }    else    {        // Time for next trigger?        keyRepeatRate -= elapsedTime;        if (keyRepeatRate < 0)        {            // Trigger action, reset key repeat rate.            leftKeyPressed = true;            keyRepeatRate = RepeatRateHz;        }    }}if (leftKeyPressed){    // Move shape left.    Move(-1, 0);}// After all keys are processed.oldKeyboardState = keyboardState;


[Edited by - Michael_B on March 11, 2009 6:54:05 AM]
Intrawebs
Intrawebs
Quote:
Original post by Michael_B
To solve the problem you need, like Moe said, a snapshot of the keyboard state from the previous frame. This snapshot is needed to detect freshly pressed keys.


Im really hitting myself now. In the XNA documentation it describes exactly how to get keyboard input and compare to a previous state. When I did that (long before I made this thread) I was setting the gaming timing manually as I described earlier and this prevented any input from happening.

Now, by just comparing the state I don't need to use your RepeatRate to slow down the input checks. The code below works PERFECTLY now. Thanks everyone.

protected override void Update(GameTime gameTime)        {            UpdateInput();            if (!_gameStarted)            {                NewGame();                _gameStarted = true;                _startTime = gameTime.TotalGameTime;            }            else if (gameTime.TotalGameTime.TotalSeconds - _startTime.TotalSeconds > 1)            {                Move(0, 1);                _startTime = gameTime.TotalGameTime;            }            base.Update(gameTime);        }        private void UpdateInput()        {            KeyboardState newState = Keyboard.GetState();            if (newState.IsKeyDown(Keys.Escape) && !oldState.IsKeyDown(Keys.Escape))            {                UnloadContent();                this.Exit();            }            else if (newState.IsKeyDown(Keys.Down) && !oldState.IsKeyDown(Keys.Down))                Move(0, 1);            else if (newState.IsKeyDown(Keys.Up) && !oldState.IsKeyDown(Keys.Up))                RotateBlock();            else if (newState.IsKeyDown(Keys.Left) && !oldState.IsKeyDown(Keys.Left))                Move(-1, 0);            else if (newState.IsKeyDown(Keys.Right) && !oldState.IsKeyDown(Keys.Right))                Move(1, 0);            oldState = newState;        }
My Games
XNA Quicks
Michael_B
Michael_B
Ah yes, that obviously works. I thought you were trying to implement an auto-repeat feature. That's were RepeatRateHz comes in.
Intrawebs
Intrawebs
Quote:
Original post by Michael_B
Ah yes, that obviously works. I thought you were trying to implement an auto-repeat feature. That's were RepeatRateHz comes in.


I might do that for the down key press, so your contribution is still valued. :)

My Games
XNA Quicks

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.