Как замедлить или остановить нажатие клавиш в XNA
Я начал писать игру с использованием фреймворка XNA и столкнулся с какой-то простой проблемой, которую я не знаю, как правильно решить.
Я показываю меню с помощью Texture2D и с помощью клавиатуры (или геймпада) меняю выбранный пункт меню. Моя проблема заключается в том, что текущая функция, используемая для переключения между пунктами меню, слишком быстра. Я мог бы нажать кнопку вниз, и он будет идти вниз 5 или 6 пунктов меню (из-за того, что Update () вызывается много раз, таким образом обновляя выбранный пункт).
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
То, что я ищу, - это способ либо заставить игрока нажимать клавишу up/down много раз, чтобы перейти от одного пункта меню к другому, либо иметь какое-то минимальное время ожидания перед переходом к следующему пункту меню.12 ответов:
Я построил (большой) класс, который очень помогает с любыми и всеми связанными с XNA входными задачами, это делает то, что вы просите легко.
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 } }
Просто скопируйте его в отдельный класс fie и переместите в пространство имен, затем объявите один (переменная inputHelper), инициализируйте его в части initialiaze и вызовите inputHelper.Update () в цикле обновления перед логикой обновления. Затем, когда вам нужно что-то, связанное с вводом, просто используйте InputHelper! например, в вашей ситуации вы могли бы используйте InputHelper.IsNewPress ([тип кнопки ввода/клавиши здесь]), чтобы проверить, хотите ли вы переместить пункт меню вниз или вверх. Для примера: inputHelper.IsNewPress (Ключи.Вниз)
Лучший способ реализовать это-кэшировать состояние клавиатуры / геймпада из инструкции update, которая только что прошла.
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;
В вашем случае вы, вероятно, хотите двигаться "вниз" только в первом случае выше, когда клавиша была просто нажата.
Хороший способ справиться с такими вещами-хранить счетчик для каждого интересующего вас ключа, который вы увеличиваете каждый кадр, если ключ находится внизу, и сбрасываете до 0, если он поднят.
Преимущество этого заключается в том, что вы можете проверить как абсолютное состояние клавиши (если счетчик ненулевой, то клавиша выключена), так и легко проверить, была ли она только что нажата в этом кадре для меню и тому подобного (счетчик равен 1). Плюс повторение ключа становится легким (счетчик % задержки повторения равен нулю).
Если ваше приложение предназначено для машины Windows, я добился большого успеха, используя этот управляемый событиями класс, который я нашел здесь: gamedev.net сообщение на форуме
Он обрабатывает типичные нажатия клавиш и короткие паузы перед началом повтора, так же, как обычный ввод текста в приложении Windows. Также включены события перемещения мыши / колеса.
Вы можете подписаться на события, например, используя следующий код:
InputSystem.KeyDown += new KeyEventHandler(KeyDownFunction); InputSystem.KeyUp += new KeyEventHandler(KeyUpFunction);
Затем в методах сами:
void KeyDownFunction(object sender, KeyEventArgs e) { if(e.KeyCode == Keys.F) facepalm(); } void KeyUpFunction(object sender, KeyEventArgs e) { if(e.KeyCode == Keys.F) release(); }
...и так далее. Это действительно отличный класс. Я обнаружил, что его гибкость значительно улучшилась по сравнению с обработкой клавиатуры XNA по умолчанию. Удачи Вам!
Я думал, что предыдущие ответы были немного слишком сложными, поэтому я даю этот здесь...
Скопируйте класс KeyPress ниже в новый файл, объявите переменные KeyPress, инициализируйте их в своем методе Initialize (). Оттуда вы можете сделать
if ([yourkey].IsPressed()) ...
Примечание: этот ответ работает только для ввода с клавиатуры, но он должен быть легко перенесен на геймпад или любой другой вход. Я думаю, что держать код для разных типов ввода отдельно-это лучше.
public class KeyPress { public KeyPress(Keys Key) { key = Key; isHeld = false; } public bool IsPressed { get { return isPressed(); } } public static void Update() { state = Keyboard.GetState(); } private Keys key; private bool isHeld; private static KeyboardState state; private bool isPressed() { if (state.IsKeyDown(key)) { if (isHeld) return false; else { isHeld = true; return true; } } else { if (isHeld) isHeld = false; return false; } } }
Использование:
// Declare variable KeyPress escape; // Initialize() escape = new KeyPress(Keys.Escape) // Update() KeyPress.Update(); if (escape.IsPressed()) ...
Я могу ошибаться, но я думаю, что мой ответ проще на ресурсах, чем принятый ответ, а также более читабелен!
Вы можете хранить в целочисленном значении время от последнего нажатия клавиши (слева, справа...) и если это время больше, чем некоторый предел, вы можете опросить для нового нажатия клавиши. Однако это может быть сделано только для меню, потому что в игре вам понадобится эта информация немедленно.
Что вы также можете сделать, так это создать функцию, объединяющую KyeUp и KeyDown, которая сообщает вам, когда клавиша была нажата один раз, только в 1 цикле обновления, так что она работает только каждый раз, когда вы нажимаете клавишу снова.
Ладно, я все понял. Во-первых, я добавил
private Keys keyPressed = Keys.None;
И в моем методе Update() я делаю следующее:
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; }
Похоже, он работает правильно.
Я сохраняю GamePadState и KeyboardState из предыдущего запуска обновления. При следующем запуске обновления я проверяю наличие кнопок, которые не были нажаты в прошлом запуске, но нажаты сейчас. Затем я сохраняю текущее состояние.
У меня все это упаковано в статический класс, который я могу использовать для запроса определенных кнопок и/или получения списка нажатых кнопок с момента последнего обновления. Это делает его действительно легко работать с несколькими клавишами одновременно (то, что вы, безусловно, хотите в играх), и это будет тривиально расширяется до аккордов.
Раньери, на что это похоже? Мне трудно жонглировать этими циклами обновления...
МММ...
public static bool CheckKeyPress(Keys key) { return keyboardState.IsKeyUp(key) && lastKeyboardState.IsKeyDown(key); }
SetStates() является частным и вызывается в Update ()
private static void SetStates() { lastKeyboardState = keyboardState; keyboardState = Keyboard.GetState(); }
Вот обновление...
public sealed override void Update(GameTime gameTime) { // Called to set the states of the input devices SetStates(); base.Update(gameTime); }
Я пытался добавить дополнительные проверки..
if (Xin.CheckKeyPress(Keys.Enter) || Xin.CheckButtonPress(Buttons.A)) { if (Xin.LastKeyboardState != Xin.KeyboardState || Xin.LastGamePadState(PlayerIndex.One) != Xin.GamePadState(PlayerIndex.One)) {
Кажется, не имеет никаких заметных эффектов - я не могу замедлить подтверждения меню,
Ну, что вы могли бы сделать, это что-то вроде этого (также будет отслеживать каждый ключ)
Делая это таким образом, вы можете проверить каждый ключ. Вы также можете сделать это для каждого ключа, создав отдельные значенияint[] keyVals; TimeSpan pressWait = new TimeSpan(0, 0, 1); Dictionary<Keys, bool> keyDowns = new Dictionary<Keys, bool>(); Dictionary<Keys, DateTime> keyTimes = new Dictionary<Keys, DateTime>(); public ConstructorNameHere { keyVals = Enum.GetValues(typeof(Keys)) as int[]; foreach (int k in keyVals) { keyDowns.Add((Keys)k, false); keyTimes.Add((Keys)k, new DateTime()+ new TimeSpan(1,0,0)); } } protected override void Update(GameTime gameTime) { foreach (int i in keyVals) { Keys key = (Keys)i; switch (key) { case Keys.Enter: keyTimes[key] = (Keyboard.GetState().IsKeyUp(key)) ? ((keyDowns[key]) ? DateTime.Now + pressWait : keyTimes[key]) : keyTimes[key]; keyDowns[key] = (keyTimes[key] > DateTime.Now) ? false : Keyboard.GetState().IsKeyDown(key); if (keyTimes[key] < DateTime.Now) { // Code for what happens when Keys.Enter is pressed goes here. } break; } }
DateTimes
и отдельные значенияbool
.
Я знаю, что это старо, но как насчет: Добавьте словарь threadsafe:
private ConcurrentDictionary<Keys, DateTime> _keyBounceDict = new ConcurrentDictionary<Keys, DateTime>();
Затем используйте этот метод для отслеживания нажатых клавиш и определения, есть ли отскок клавиш:
/////////////////////////////////////////////////////////////////////////////////////////// /// IsNotKeyBounce - determines if a key is bouncing and therefore not valid within /// a certain "delay" period /////////////////////////////////////////////////////////////////////////////////////////// private bool IsNotKeyBounce(Keys thekey, double delay) { bool OKtoPress = true; if (_keyBounceDict.ContainsKey(thekey)) { TimeSpan ts = DateTime.Now - _keyBounceDict[thekey]; if (ts.TotalMilliseconds < _tsKeyBounceTiming) { OKtoPress = false; } else { DateTime dummy; _keyBounceDict.TryRemove(thekey, out dummy); } } else { _keyBounceDict.AddOrUpdate(thekey, DateTime.Now, (key, oldValue) => oldValue); } return OKtoPress; }
Вот что я вложил в свой метод обновления:
Я использую 50 мс, но вы можете использовать все, что имеет смысл для вашего приложения или привязать его к GameTime или что-то еще...if (Keyboard.GetState().IsKeyDown(Keys.W)) { if (IsNotKeyBounce(Keys.W, 50.0)) _targetNew.Distance *= 1.1f; }