views:

1573

answers:

9

Hi,

I've begun writing a game using XNA Framework and have hit some simple problem I do not know how to solve correctly.

I'm displaying a menu using Texture2D and using the keyboard (or gamepad) I change the menu item that is selected. My problem is that the current function used to toggle between menu items is way too fast. I might click the down button and it will go down 5 or 6 menu items (due to the fact that Update() is called many time thus updating the selected item).

ex.
(> indicate selected)
> MenuItem1
MenuItem2
MenuItem3
MenuItem4
MenuItem5

I press the down key for just a second), then I have this state:

MenuItem1
MenuItem2
MenuItem3
> MenuItem4
MenuItem5

What I want is (until I press the key again)
MenuItem1
> MenuItem2
MenuItem3
MenuItem4
MenuItem5

What I am looking for is a way to either have the player click the up/down key many time in order to go from one menu item to the other, or to have some sort of minimum wait time before going to the next menu item.

A: 

You could store in integer value time from last key pressed (left,right...) and if this time is bigger than some limit you could poll for new key pressed. However this could be done only for menu, because in-game you would need that information immediately.

mnn
A: 

Ok, I've figured it out. First, I added a

private Keys keyPressed = Keys.None;

and in my Update() method, I do the following:

 KeyboardState keyboardState = Keyboard.GetState();

if (keyboardState.IsKeyUp(keyPressed))
{
    keyPressed = Keys.None;
}

if (keyboardState.IsKeyDown(keyPressed))
{
    return;
}

// Some additionnal stuff is done according to direction
if (keyboardState.IsKeyDown(Keys.Up))
{
    keyPressed = Keys.Up;
}
else if (keyboardState.IsKeyDown(Keys.Down))
{
    keyPressed = Keys.Down;
}

It seems to be working correctly.

tomzx
Seems a bit flaky, from the code here, keyPressed is set to the same value (i.e. Keys.None). Is something else missing?
Spoike
@Spoike: You are right, I forgot to mention that the keyPressed is defined in the // check for new gamepad press. Code is added.
tomzx
I must be missing something, but it looks to me like you are overwriting your state in the last "if". If the user presses both "up" and "down" you will only remember "up".You do not want to be able to deal with more than one key at the same time? That sounds like a serious limitation to have in a game.
Ranieri
@Ranieri: I do not remember both (up/down) since this code is only used for in-menu where the player either wants to select an option up or down from the actual one, the goal here was to limit up/down selection of menu item. Since the update() function is called many times during a second, there was a problem, and this is a small fix to this problem.
tomzx
A: 

What you can also do is make yourself a functions combining KyeUp and KeyDown that tells you when the key has been pressed once, only in 1 loop of the update, so that it only works everytime you press the key again.

A: 

I save the GamePadState and KeyboardState from the previous update run. At the next update run i check for buttons that were not pressed last run, but are pressed now. Then I save the current state.

I have all of this wrapped up in a static class that I can use to query for specific buttons and/or get a list of pressed buttons since the last update. This makes it really easy to work with multiple keys simultaneously (something you certainly want in games) and it would be trivially extensible to chords.

Ranieri
+2  A: 

A nice way of dealing with this kind of thing is to store a counter for each key you're interested in, which you increment every frame if the key is down, and reset to 0 if it's up.

The advantage of this is that you can then test for both the absolute state of the key (if the counter is non-zero the key is down) and also easily check if it's just been pressed this frame for menus and the like (counter is 1). Plus key repeat becomes easy (counter % repeat delay is zero).

James Sutherland
This is a genuinely good idea. Thanks!
Ranieri
You really want to monitor TIME here, not a simple counter. At least, that is if you want it to perform the same way on different computers with different rendering speeds.
TM
Unless you use fixed rate logic updates, which I'd pretty much always recommend.
James Sutherland
+4  A: 

the best way to implement this is to cache the keyboard/gamepad state from the update statement that just passed.

KeyboardState oldState;
...

var newState = Keyboard.GetState();

if (newState.IsKeyDown(Keys.Down) && !oldState.IsKeyDown(Keys.Down))
{
    // the player just pressed down
}
else if (newState.IsKeyDown(Keys.Down) && oldState.IsKeyDown(Keys.Down))
{
    // the player is holding the key down
}
else if (!newState.IsKeyDown(Keys.Down) && oldState.IsKeyDown(Keys.Down))
{
    // the player was holding the key down, but has just let it go
}

oldState = newState;

In your case, you probably only want to move "down" in the first case above, when the key was just pressed.

Joel Martinez
+1  A: 

If your application is for a Windows machine, I've had great success using this event driven class that I found here: gamedev.net forum post

It handles typical key presses and the short pauses before the repeating starts, just like normal text entry in a Windows application. Also included is mouse move/wheel events.

You can subscribe to the events, for example, using the following code:

InputSystem.KeyDown += new KeyEventHandler(KeyDownFunction);
InputSystem.KeyUp += new KeyEventHandler(KeyUpFunction);

Then in the methods themselves:

void KeyDownFunction(object sender, KeyEventArgs e)
{
   if(e.KeyCode == Keys.F)
      facepalm();
}

void KeyUpFunction(object sender, KeyEventArgs e)
{
   if(e.KeyCode == Keys.F)
      release();
}

...and so forth. It really is a great class. I've found it's flexibility much improved over XNA's default keyboard handling. Good Luck!

bufferz
+3  A: 

I've built a (large) class that helps a lot with any and all XNA input related tasks, it makes what you're asking for easy.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace YourNamespaceHere
{
    /// <summary>
    /// an enum of all available mouse buttons.
    /// </summary>
    public enum MouseButtons
    {
        LeftButton,
        MiddleButton,
        RightButton,
        ExtraButton1,
        ExtraButton2
    }

    public class InputHelper
    {
        private GamePadState _lastGamepadState;
        private GamePadState _currentGamepadState;
#if (!XBOX)
        private KeyboardState _lastKeyboardState;
        private KeyboardState _currentKeyboardState;
        private MouseState _lastMouseState;
        private MouseState _currentMouseState;
#endif
        private PlayerIndex _index = PlayerIndex.One;
        private bool refreshData = false;

        /// <summary>
        /// Fetches the latest input states.
        /// </summary>
        public void Update()
        {
            if (!refreshData)
                refreshData = true;
            if (_lastGamepadState == null && _currentGamepadState == null)
            {
                _lastGamepadState = _currentGamepadState = GamePad.GetState(_index);
            }
            else
            {
                _lastGamepadState = _currentGamepadState;
                _currentGamepadState = GamePad.GetState(_index);
            }
#if (!XBOX)
            if (_lastKeyboardState == null && _currentKeyboardState == null)
            {
                _lastKeyboardState = _currentKeyboardState = Keyboard.GetState();
            }
            else
            {
                _lastKeyboardState = _currentKeyboardState;
                _currentKeyboardState = Keyboard.GetState();
            }
            if (_lastMouseState == null && _currentMouseState == null)
            {
                _lastMouseState = _currentMouseState = Mouse.GetState();
            }
            else
            {
                _lastMouseState = _currentMouseState;
                _currentMouseState = Mouse.GetState();
            }
#endif
        }

        /// <summary>
        /// The previous state of the gamepad. 
        /// Exposed only for convenience.
        /// </summary>
        public GamePadState LastGamepadState
        {
            get { return _lastGamepadState; }
        }
        /// <summary>
        /// the current state of the gamepad.
        /// Exposed only for convenience.
        /// </summary>
        public GamePadState CurrentGamepadState
        {
            get { return _currentGamepadState; }
        }
        /// <summary>
        /// the index that is used to poll the gamepad. 
        /// </summary>
        public PlayerIndex Index
        {
            get { return _index; }
            set { 
                _index = value;
                if (refreshData)
                {
                    Update();
                    Update();
                }
            }
        }
#if (!XBOX)
        /// <summary>
        /// The previous keyboard state.
        /// Exposed only for convenience.
        /// </summary>
        public KeyboardState LastKeyboardState
        {
            get { return _lastKeyboardState; }
        }
        /// <summary>
        /// The current state of the keyboard.
        /// Exposed only for convenience.
        /// </summary>
        public KeyboardState CurrentKeyboardState
        {
            get { return _currentKeyboardState; }
        }
        /// <summary>
        /// The previous mouse state.
        /// Exposed only for convenience.
        /// </summary>
        public MouseState LastMouseState
        {
            get { return _lastMouseState; }
        }
        /// <summary>
        /// The current state of the mouse.
        /// Exposed only for convenience.
        /// </summary>
        public MouseState CurrentMouseState
        {
            get { return _currentMouseState; }
        }
#endif
        /// <summary>
        /// The current position of the left stick. 
        /// Y is automatically reversed for you.
        /// </summary>
        public Vector2 LeftStickPosition
        {
            get 
            { 
                return new Vector2(
                    _currentGamepadState.ThumbSticks.Left.X, 
                    -CurrentGamepadState.ThumbSticks.Left.Y); 
            }
        }
        /// <summary>
        /// The current position of the right stick.
        /// Y is automatically reversed for you.
        /// </summary>
        public Vector2 RightStickPosition
        {
            get 
            { 
                return new Vector2(
                    _currentGamepadState.ThumbSticks.Right.X, 
                    -_currentGamepadState.ThumbSticks.Right.Y); 
            }
        }
        /// <summary>
        /// The current velocity of the left stick.
        /// Y is automatically reversed for you.
        /// expressed as: 
        /// current stick position - last stick position.
        /// </summary>
        public Vector2 LeftStickVelocity
        {
            get 
            {
                Vector2 temp =
                    _currentGamepadState.ThumbSticks.Left - 
                    _lastGamepadState.ThumbSticks.Left;
                return new Vector2(temp.X, -temp.Y); 
            }
        }
        /// <summary>
        /// The current velocity of the right stick.
        /// Y is automatically reversed for you.
        /// expressed as: 
        /// current stick position - last stick position.
        /// </summary>
        public Vector2 RightStickVelocity
        {
            get
            {
                Vector2 temp =
                    _currentGamepadState.ThumbSticks.Right -
                    _lastGamepadState.ThumbSticks.Right;
                return new Vector2(temp.X, -temp.Y);
            }
        }
        /// <summary>
        /// the current position of the left trigger.
        /// </summary>
        public float LeftTriggerPosition
        {
            get { return _currentGamepadState.Triggers.Left; }
        }
        /// <summary>
        /// the current position of the right trigger.
        /// </summary>
        public float RightTriggerPosition
        {
            get { return _currentGamepadState.Triggers.Right; }
        }
        /// <summary>
        /// the velocity of the left trigger.
        /// expressed as: 
        /// current trigger position - last trigger position.
        /// </summary>
        public float LeftTriggerVelocity
        {
            get 
            { 
                return 
                    _currentGamepadState.Triggers.Left - 
                    _lastGamepadState.Triggers.Left; 
            }
        }
        /// <summary>
        /// the velocity of the right trigger.
        /// expressed as: 
        /// current trigger position - last trigger position.
        /// </summary>
        public float RightTriggerVelocity
        {
            get 
            { 
                return _currentGamepadState.Triggers.Right - 
                    _lastGamepadState.Triggers.Right; 
            }
        }
#if (!XBOX)
        /// <summary>
        /// the current mouse position.
        /// </summary>
        public Vector2 MousePosition
        {
            get { return new Vector2(_currentMouseState.X, _currentMouseState.Y); }
        }
        /// <summary>
        /// the current mouse velocity.
        /// Expressed as: 
        /// current mouse position - last mouse position.
        /// </summary>
        public Vector2 MouseVelocity
        {
            get
            {
                return (
                    new Vector2(_currentMouseState.X, _currentMouseState.Y) - 
                    new Vector2(_lastMouseState.X, _lastMouseState.Y)
                    );
            }
        }
        /// <summary>
        /// the current mouse scroll wheel position.
        /// See the Mouse's ScrollWheel property for details.
        /// </summary>
        public float MouseScrollWheelPosition
        {
            get 
            {
                return _currentMouseState.ScrollWheelValue;
            }
        }
        /// <summary>
        /// the mouse scroll wheel velocity.
        /// Expressed as:
        /// current scroll wheel position - 
        /// the last scroll wheel position.
        /// </summary>
        public float MouseScrollWheelVelocity
        {
            get 
            {
                return (_currentMouseState.ScrollWheelValue - _lastMouseState.ScrollWheelValue);
            }
        }
#endif
        /// <summary>
        /// Used for debug purposes.
        /// Indicates if the user wants to exit immediately.
        /// </summary>
        public bool ExitRequested
        {
#if (!XBOX)
            get
            {
                return (
                    (IsCurPress(Buttons.Start) && 
                    IsCurPress(Buttons.Back)) ||
                    IsCurPress(Keys.Escape));
            }
#else
            get { return (IsCurPress(Buttons.Start) && IsCurPress(Buttons.Back)); }
#endif
        }
        /// <summary>
        /// Checks if the requested button is a new press.
        /// </summary>
        /// <param name="button">
        /// The button to check.
        /// </param>
        /// <returns>
        /// a bool indicating whether the selected button is being 
        /// pressed in the current state but not the last state.
        /// </returns>
        public bool IsNewPress(Buttons button)
        {
            return (
                _lastGamepadState.IsButtonUp(button) && 
                _currentGamepadState.IsButtonDown(button));
        }
        /// <summary>
        /// Checks if the requested button is a current press.
        /// </summary>
        /// <param name="button">
        /// the button to check.
        /// </param>
        /// <returns>
        /// a bool indicating whether the selected button is being 
        /// pressed in the current state and in the last state.
        /// </returns>
        public bool IsCurPress(Buttons button)
        {
            return (
                _lastGamepadState.IsButtonDown(button) && 
                _currentGamepadState.IsButtonDown(button));
        }
        /// <summary>
        /// Checks if the requested button is an old press.
        /// </summary>
        /// <param name="button">
        /// the button to check.
        /// </param>
        /// <returns>
        /// a bool indicating whether the selected button is not being
        /// pressed in the current state and is being pressed in the last state.
        /// </returns>
        public bool IsOldPress(Buttons button)
        {
            return (
                _lastGamepadState.IsButtonDown(button) && 
                _currentGamepadState.IsButtonUp(button));
        }
#if (!XBOX)
        /// <summary>
        /// Checks if the requested key is a new press.
        /// </summary>
        /// <param name="key">
        /// the key to check.
        /// </param>
        /// <returns>
        /// a bool that indicates whether the selected key is being 
        /// pressed in the current state and not in the last state.
        /// </returns>
        public bool IsNewPress(Keys key)
        {
            return (
                _lastKeyboardState.IsKeyUp(key) && 
                _currentKeyboardState.IsKeyDown(key));
        }
        /// <summary>
        /// Checks if the requested key is a current press.
        /// </summary>
        /// <param name="key">
        /// the key to check.
        /// </param>
        /// <returns>
        /// a bool that indicates whether the selected key is being 
        /// pressed in the current state and in the last state.
        /// </returns>
        public bool IsCurPress(Keys key)
        {
            return (
                _lastKeyboardState.IsKeyDown(key) &&
                _currentKeyboardState.IsKeyDown(key));
        }
        /// <summary>
        /// Checks if the requested button is an old press.
        /// </summary>
        /// <param name="key">
        /// the key to check.
        /// </param>
        /// <returns>
        /// a bool indicating whether the selectde button is not being
        /// pressed in the current state and being pressed in the last state.
        /// </returns>
        public bool IsOldPress(Keys key)
        {
            return (
                _lastKeyboardState.IsKeyDown(key) && 
                _currentKeyboardState.IsKeyUp(key));
        }
        /// <summary>
        /// Checks if the requested mosue button is a new press.
        /// </summary>
        /// <param name="button">
        /// teh mouse button to check.
        /// </param>
        /// <returns>
        /// a bool indicating whether the selected mouse button is being
        /// pressed in the current state but not in the last state.
        /// </returns>
        public bool IsNewPress(MouseButtons button)
        {
            switch (button)
            {
                case MouseButtons.LeftButton:
                    return (
                        _lastMouseState.LeftButton == ButtonState.Released &&
                        _currentMouseState.LeftButton == ButtonState.Pressed);
                case MouseButtons.MiddleButton:
                    return (
                        _lastMouseState.MiddleButton == ButtonState.Released &&
                        _currentMouseState.MiddleButton == ButtonState.Pressed);
                case MouseButtons.RightButton:
                    return (
                        _lastMouseState.RightButton == ButtonState.Released &&
                        _currentMouseState.RightButton == ButtonState.Pressed);
                case MouseButtons.ExtraButton1:
                    return (
                        _lastMouseState.XButton1 == ButtonState.Released &&
                        _currentMouseState.XButton1 == ButtonState.Pressed);
                case MouseButtons.ExtraButton2:
                    return (
                        _lastMouseState.XButton2 == ButtonState.Released &&
                        _currentMouseState.XButton2 == ButtonState.Pressed);
                default:
                    return false;
            }
        }
        /// <summary>
        /// Checks if the requested mosue button is a current press.
        /// </summary>
        /// <param name="button">
        /// the mouse button to be checked.
        /// </param>
        /// <returns>
        /// a bool indicating whether the selected mouse button is being 
        /// pressed in the current state and in the last state.
        /// </returns>
        public bool IsCurPress(MouseButtons button)
        {
            switch (button)
            {
                case MouseButtons.LeftButton:
                    return (
                        _lastMouseState.LeftButton == ButtonState.Pressed &&
                        _currentMouseState.LeftButton == ButtonState.Pressed);
                case MouseButtons.MiddleButton:
                    return (
                        _lastMouseState.MiddleButton == ButtonState.Pressed &&
                        _currentMouseState.MiddleButton == ButtonState.Pressed);
                case MouseButtons.RightButton:
                    return (
                        _lastMouseState.RightButton == ButtonState.Pressed &&
                        _currentMouseState.RightButton == ButtonState.Pressed);
                case MouseButtons.ExtraButton1:
                    return (
                        _lastMouseState.XButton1 == ButtonState.Pressed &&
                        _currentMouseState.XButton1 == ButtonState.Pressed);
                case MouseButtons.ExtraButton2:
                    return (
                        _lastMouseState.XButton2 == ButtonState.Pressed &&
                        _currentMouseState.XButton2 == ButtonState.Pressed);
                default:
                    return false;
            }
        }
        /// <summary>
        /// Checks if the requested mosue button is an old press.
        /// </summary>
        /// <param name="button">
        /// the mouse button to check.
        /// </param>
        /// <returns>
        /// a bool indicating whether the selected mouse button is not being 
        /// pressed in the current state and is being pressed in the old state.
        /// </returns>
        public bool IsOldPress(MouseButtons button)
        {
            switch (button)
            {
                case MouseButtons.LeftButton:
                    return (
                        _lastMouseState.LeftButton == ButtonState.Pressed &&
                        _currentMouseState.LeftButton == ButtonState.Released);
                case MouseButtons.MiddleButton:
                    return (
                        _lastMouseState.MiddleButton == ButtonState.Pressed &&
                        _currentMouseState.MiddleButton == ButtonState.Released);
                case MouseButtons.RightButton:
                    return (
                        _lastMouseState.RightButton == ButtonState.Pressed &&
                        _currentMouseState.RightButton == ButtonState.Released);
                case MouseButtons.ExtraButton1:
                    return (
                        _lastMouseState.XButton1 == ButtonState.Pressed &&
                        _currentMouseState.XButton1 == ButtonState.Released);
                case MouseButtons.ExtraButton2:
                    return (
                        _lastMouseState.XButton2 == ButtonState.Pressed &&
                        _currentMouseState.XButton2 == ButtonState.Released);
                default:
                    return false;
            }
        }
#endif
    }
}

Just copy it into a separate class fie and move it to your namespace, then declare one and call Update in your update loop. Then whenever you need something related to input, just use the InputHelper! for instance, in your situation, you would use InputHelper.IsNewPress([type of input button/key here]) to check if you want to move the menu item down or up.

RCIX
A: 

Ranieri, what does that look like? I am having a hard time juggling these update cycles...

Hrmmm...

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

SetStates() is private and it is called in the Update()

private static void SetStates()
{
    lastKeyboardState = keyboardState;

    keyboardState = Keyboard.GetState();
}

Here is the update...

public sealed override void Update(GameTime gameTime)
{
    // Called to set the states of the input devices
    SetStates();
    base.Update(gameTime);
}

I've tried adding extra checks..

if (Xin.CheckKeyPress(Keys.Enter) ||
    Xin.CheckButtonPress(Buttons.A))
{
    if (Xin.LastKeyboardState != Xin.KeyboardState ||
        Xin.LastGamePadState(PlayerIndex.One) != Xin.GamePadState(PlayerIndex.One))
    {

doesn't seem to have any noticeable effects - I can't seem to slow down the menu confirmations,

Bixel