5
\$\begingroup\$

I'm pretty new to programming, and even newer to C# and XNA. I currently call a method that looks for keyboard input in my Update method. This method has if statements for doing what needs to be done when a key has been pressed. The problem is, since it's being called constantly, it's impossible to press a key quickly enough for it to register only once.

One issue this creates is going through a menu. A short tap of a key sends the "cursor" that indicates which menu item is selected wheeling through the menu several times. I'd like to be able to "turn off" keyboard input for perhaps a fourth of a second after any valid key has been pressed.

\$\endgroup\$

3 Answers 3

19
\$\begingroup\$

What you're trying to do is simple to solve without having to resort to doing any timing on the input (in fact I recommend you not to do so in this case - timing is more useful for scenarios such as limiting the firing rate of a spaceship).

If you want for only the initial press of the key to be registered, the way it's usually done is:

  1. Keep track of both the current and the previous frame's keyboard state.
  2. Detect a key press by checking if the key is down in the current frame but was up on the previous frame.

Code example:

private KeyboardState _currentKeyboardState;
private KeyboardState _previousKeyboardState;

public void Update(GameTime elapsed)
{
    // Before handling input
    _currentKeyboardState = Keyboard.GetState();

    if(_currentKeyboardState.IsKeyDown(Keys.Down) &&
       _previousKeyboardState.IsKeyUp(Keys.Down))
    {
        // Handle key press of the down key
    }

    // <Handle other keys here>

    // After handling input
    _previousKeyboardState = _currentKeyboardState;
}

Personally, I like to wrap this up in some separate InputManager class and provide methods such as IsKeyDown(key) versus OnKeyPress(key) to distinguish both scenarios clearly.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Amazing as always, you've beaten me! \$\endgroup\$
    – Martin.
    Jan 2, 2012 at 0:55
  • \$\begingroup\$ @Martin OT I always end up revising my posts a lot though. Never quite like my wording on the first version.. \$\endgroup\$ Jan 2, 2012 at 1:00
  • \$\begingroup\$ Thank you so much. None of the game examples or stuff I could think to Google mentioned this technique. Got it working quickly. \$\endgroup\$
    – Matt
    Jan 2, 2012 at 2:38
  • \$\begingroup\$ @Matt No problem! And welcome to stackexchange! You should mark this question as answered now, by using the accept answer button, (right below the downvote button) on the answer of your choice. :) \$\endgroup\$ Jan 2, 2012 at 2:41
4
\$\begingroup\$

David pretty much summed it up entirely. However, if you are interested I attached at the end a condensed version of what I use. It's just a game component you can add to the game with Components.Add() then call to check if buttons were just pressed, released, or are currently down. You can just throw it directly in your project (you might want to change the namespace though). If you need help/an explanation let me know. Happy coding.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Reflection;


namespace XNAGenericsLibrary
{
    public class InputHandler : Microsoft.Xna.Framework.GameComponent
    {
        #region Field Region

        static KeyboardState keyboardState;
        static KeyboardState lastKeyboardState;
        static GamePadState[] gamePadState;
        static GamePadState[] lastGamePadState;

        #endregion

        #region Property Region

        public static KeyboardState KeyboardState
        {
            get { return keyboardState; }
        }

        public static KeyboardState LastKeyboardState
        {
            get { return lastKeyboardState; }
        }

        public static GamePadState[] GamePadStates
        {
            get { return gamePadState; }
        }

        public static GamePadState[] LastGamePadStates
        {
            get { return lastGamePadState; }
        }

        #endregion

        #region Constructor Region

        public InputHandler(Game game)
            : base(game)
        {
            keyboardState = Keyboard.GetState();

            gamePadState = new GamePadState[EnumMethods.GetValuesArray<PlayerIndex>().Length];
            foreach (PlayerIndex index in EnumMethods.GetValuesArray<PlayerIndex>())
                gamePadState[(int)index] = GamePad.GetState(index);
        }

        #endregion

        #region XNA methods

        public override void Update(GameTime gameTime)
        {
            lastKeyboardState = keyboardState;
            keyboardState = Keyboard.GetState();

            lastGamePadState = (GamePadState[])gamePadState.Clone();
            foreach (PlayerIndex index in EnumMethods.GetValuesArray<PlayerIndex>())
                gamePadState[(int)index] = GamePad.GetState(index);

            base.Update(gameTime);
        }

        #endregion

        #region Keyboard Region

        public static bool KeyboardPressed(Keys key)
        {
            return (keyboardState.IsKeyDown(key) && lastKeyboardState.IsKeyUp(key));
        }

        public static bool KeyboardReleased(Keys key)
        {
            return (keyboardState.IsKeyUp(key) && lastKeyboardState.IsKeyDown(key));
        }

        public static bool KeyboardDown(Keys key)
        {
            return keyboardState.IsKeyDown(key);
        }

        #endregion

        #region GamePad Region

        public static bool GamePadButtonPressed(Buttons button, PlayerIndex index)
        {
            return (gamePadState[(int)index].IsButtonDown(button) && lastGamePadState[(int)index].IsButtonUp(button));
        }

        public static bool GamePadButtonReleased(Buttons button, PlayerIndex index)
        {
            return (gamePadState[(int)index].IsButtonUp(button) && lastGamePadState[(int)index].IsButtonDown(button));
        }

        public static bool GamePadButtonDown(Buttons button, PlayerIndex index)
        {
            return gamePadState[(int)index].IsButtonDown(button);
        }

        #endregion
    }


    class EnumMethods
    {
        public static T[] GetValuesArray<T>()
        {
            return (typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public).Select(x => (T)x.GetValue(null))).ToArray<T>();
        }
    }
}

EDIT- By the way, this works with the XBox 360 which uses the Compact Framework. If targeting the PC you can replace calls to EnumMethods.GetValuesArray() with Enum.GetValues(), which would probably be more efficient.

\$\endgroup\$
2
\$\begingroup\$

To answer your question (for a length of time?), there's a simple way to do this

Just use something like

int framesPassed = 0;
public override void Update(GameTime gameTime) {
     if (framesPassed > 30) {
          framesPassed = 0;
          doYourStuff();
     }
     framesPassed++;
}

which will ensure that you'll call doYourStuff() after 30 frames pass.

If you really need to do something after X (5) seconds, I'd recommend you to do something like this

double secondsPassed = 0.0;
public override void Update(GameTime gameTime) {
     secondsPassed += gameTime.ElapsedGameTime.TotalSeconds;
     if (secondsPassed > 5.0) {
          secondsPassed -= 5.0;
          doYourStuff();
     }
}
\$\endgroup\$
5
  • \$\begingroup\$ A frame is not a unit of time though, as its duration depends on the framerate. For true time you should use the GameTime.ElapsedGameTime property, but the process is similar enough. \$\endgroup\$ Jan 2, 2012 at 18:26
  • \$\begingroup\$ @DavidGouveia: yeah, you're right. \$\endgroup\$
    – Martin.
    Jan 2, 2012 at 19:16
  • \$\begingroup\$ @DavidGouveia: I hope it's all correct now. \$\endgroup\$
    – Martin.
    Jan 2, 2012 at 19:19
  • 1
    \$\begingroup\$ You missed calling the TotalSeconds property of ElapsedGameTime, but I went ahead and fixed it. And I also added a little tweak which is instead of resetting the timer to 0, decreasing it by 5 instead. This is a bit more accurate in the long run because it saves those "extra" milliseconds in each iteration. \$\endgroup\$ Jan 2, 2012 at 19:42
  • 1
    \$\begingroup\$ @DavidGouveia: that's a good idea, thank you a lot! \$\endgroup\$
    – Martin.
    Jan 2, 2012 at 21:03

You must log in to answer this question.

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