【问题标题】:How can I fire a very specifc KeyPressed event?如何触发一个非常具体的 Key Pressed 事件?
【发布时间】:2012-02-23 02:26:20
【问题描述】:

我有以下伪代码:

public void Update()
{
    if (pressed)
        OnKeyPressed(key);
    if (held)
        OnKeyHeld(key);
    if (released)
        OnKeyReleased(key)
}

因此,在每次 Update() 期间,如果按下、按住或释放某个键,则可以引发相应的事件。这是 OnKeyPressed 方法的实际代码:

public void OnKeyPressed(Keys key)
{
    EventHandler<InputEventArgs> handler = m_keyPressed;

    if (handler != null)
    {
        handler(this, new InputEventArgs(key));
    }
}

但是,这并不是我真正想要的,因为我不一定关心是否按下了某个键。我关心的是是否按下了特定的键。我如何编写代码来实现这个目标,而不会创建大量的事件(我要绑定的每个键一个)?


回复:尼克-

好的,我将这些类组合起来,这是生成的伪代码:

public void OnKeyPressed(Keys key)
{
    if(m_boundKeys.ContainsKey(key))
    {
        //Fire event
        keyPressed(this, EventArgs.Empty);
    }
}

现在,上面的问题是被触发的事件仍然只是一个 keyPressed 事件。这不是 A_keyPressed 或 B_keyPressed 事件。我可以将内容注册到 keyPressed 事件,但这意味着每当按下任何注册的键时,每个订阅者都会收到一个 keyPressed 事件。

我正在寻找:

public void OnKeyPressed(Keys key)
{
    if(m_boundKeys.ContainsKey(key))
    {
        //Specific key event based on key
    }
}

【问题讨论】:

  • 您能添加一个方法或属性来设置所需的键吗?在 Update() 中,您可以根据所需的 key/keys 进行过滤。
  • 我会考虑这篇文章中描述的方法-stackoverflow.com/questions/1707040/handling-function-key-press
  • 嗯...我可以过滤它,但它仍然会在任何时候按下任何这些键时触发 KeyPressed 事件。我想要的是更多类似于 UpKeyPressed、DownKeyPressed 等的东西。
  • 正如您所说的,当单击任何键并且您无法停止它时,事件会触发,您可以过滤它(至少据我所知)-请将此添加到您的原始 Q(如何引发事件仅当单击特定按钮时)

标签: c# events xna


【解决方案1】:

这是我为QuickStart Game Engine 编写的KeyboardHandler 类。它保留前一帧的关键状态列表,然后将其与当前帧的关键状态列表进行比较。根据最后一帧的变化,它会为按下、按住或释放的每个键发送一个事件。它只发送这 3 种类型的事件,但在每个事件中,它都包含与该事件相关的键。

现在,如果您只关心特定的键,您可以使用我编写的另一个类,称为 InputPollingHandler,在其中您只注册您关心的键,您将收到那些注册键的调用。这也将允许您随时轮询任何已注册密钥的值。如果您想人为地创建输入事件,InputPollingHandler 还允许您触发自己的事件,这在您想从记录的事件中复制输入的系统测试等事情中很常见。 InputPollingHandler 的代码在KeyboardHandler 的代码下方。

没有任何方法可以只让 XNA 通知您可能关心的键,因此您使用帮助程序类来监听所有键并只向您发送有关您想要的键的信息。

/// <summary>
/// This class handles keyboard input
/// </summary>
public class KeyboardHandler : InputHandler
{
    /// <summary>
    /// A list of keys that were down during the last update
    /// </summary>
    private List<KeyInfo> previousDownKeys;

    /// <summary>
    /// Holds the current keyboard state
    /// </summary>
    private KeyboardState currentKeyboardState;

    /// <summary>
    /// Creates a keyboard handler.
    /// </summary>
    /// <param name="game"></param>
    public KeyboardHandler(QSGame game)
        : base(game)
    {
        this.previousDownKeys = new List<KeyInfo>();
    }

    /// <summary>
    /// Reads the current keyboard state and processes all key messages required.
    /// </summary>
    /// <param name="gameTime"></param>
    /// <remarks>This process may seem complicated and unefficient, but honestly most keyboards can
    /// only process 4-6 keys at any given time, so the lists we're iterating through are relatively small.
    /// So at the most we're doing 42 comparisons if 6 keys can be held at time. 42 comparisons only if the
    /// 6 keys pressed during one frame are different than the 6 keys pressed on the next frame, which is
    /// extremely unlikely.</remarks>
    protected override void UpdateCore(GameTime gameTime)
    {
        this.currentKeyboardState = Keyboard.GetState();
        Keys[] currentlyPressed = this.currentKeyboardState.GetPressedKeys();
        bool[] isHeld = new bool[currentlyPressed.Length];

        for (int i = currentlyPressed.Length - 1; i >= 0; i--)
        {
            Keys key = currentlyPressed[i];

            // There were no keys down last frame, no need to loop through the last frame's state
            if (this.previousDownKeys.Count == 0)
            {
                // Because no keys were down last frame, every key that is down this frame is newly pressed.
                SendKeyMessage(MessageType.KeyDown, key, gameTime);
            }
            else
            {
                bool processed = false;

                // Loop through all the keys that were pressed last frame, for comparison
                for (int j = this.previousDownKeys.Count - 1; j >= 0; j--)
                {
                    // If this key was used at all last frame then it is being held
                    if (key == this.previousDownKeys[j].key)
                    {
                        // We should keep track of the timer for each index in an array large enough for all keys
                        // so we can have a timer for how long each key has been held. This can come later. Until
                        // then keys are marked as held after one frame. - LordIkon

                        if (this.previousDownKeys[j].heldLastFrame == false)
                        {
                            // Send held message
                            isHeld[i] = true;
                            SendKeyMessage(MessageType.KeyHeld, key, gameTime);
                        }
                        else
                        {
                            isHeld[i] = true;
                        }

                        previousDownKeys.Remove(this.previousDownKeys[j]);   // Remove this key from the previousDownKeys list
                        processed = true;
                        break;
                    }
                }

                // If key was un-processed throughout the loop, process message here as a new key press
                if (processed == false)
                {
                    SendKeyMessage(MessageType.KeyDown, key, gameTime);
                }
            }
        }

        // If there any keys left in the previous state after comparisons, it means they were released
        if (this.previousDownKeys.Count > 0)
        {
            // Go through all keys and send 'key up' message for each one
            for (int i = this.previousDownKeys.Count - 1; i >= 0; i--)
            {
                // Send released message
                SendKeyMessage(MessageType.KeyUp, this.previousDownKeys[i].key, gameTime);
            }
        }

        this.previousDownKeys.Clear();      // Clear the previous list of keys down

        // Update the list of previous keys that are down for next loop
        for (int i = currentlyPressed.Length - 1; i >= 0; i--)
        {
            Keys key = currentlyPressed[i];

            KeyInfo newKeyInfo;
            newKeyInfo.key = key;
            newKeyInfo.heldLastFrame = isHeld[i];

            this.previousDownKeys.Add(newKeyInfo);
        }
    }

    /// <summary>
    /// Sends a message containing information about a specific key
    /// </summary>
    /// <param name="keyState">The state of the key Down/Pressed/Up</param>
    /// <param name="key">The <see cref="Keys"/> that changed it's state</param>
    private void SendKeyMessage(MessageType keyState, Keys key, GameTime gameTime)
    {
        switch (keyState)
        {
            case MessageType.KeyDown:
                {
                    MsgKeyPressed keyMessage = ObjectPool.Aquire<MsgKeyPressed>();
                    keyMessage.Key = key;
                    keyMessage.Time = gameTime;
                    this.Game.SendMessage(keyMessage);
                }
                break;
            case MessageType.KeyHeld:
                {
                    MsgKeyHeld keyMessage = ObjectPool.Aquire<MsgKeyHeld>();
                    keyMessage.Key = key;
                    keyMessage.Time = gameTime;
                    this.Game.SendMessage(keyMessage);
                }
                break;
            case MessageType.KeyUp:
                {
                    MsgKeyReleased keyMessage = ObjectPool.Aquire<MsgKeyReleased>();
                    keyMessage.Key = key;
                    keyMessage.Time = gameTime;
                    this.Game.SendMessage(keyMessage);
                }
                break;
            default:
                break;
        }


    }
}

这里是 InputPollingHandler 类。完整引擎的版本更大,因为它还支持鼠标和 Xbox360 游戏手柄。

public class InputPollingHandler
{
    private QSGame game;

    /// <summary>
    /// Stores all <see cref="Keys"/> that are specifically listened for.
    /// </summary>
    private Dictionary<Keys, InputButton> keys;

    /// <summary>
    /// Create an instance of an input polling handler
    /// </summary>
    /// <param name="Game"></param>
    public InputPollingHandler(QSGame Game)
    {
        this.game = Game;

        this.keys = new Dictionary<Keys, InputButton>();           

        this.game.GameMessage += this.Game_GameMessage;
    }

    /// <summary>
    /// Add an input listener for a keyboard key.
    /// </summary>
    /// <param name="keyType">Key to listen for</param>
    public void AddInputListener(Keys keyType)
    {
        InputButton newButton = new InputButton();
        this.keys.Add(keyType, newButton);
    }

    /// <summary>
    /// Acquire a keyboard key
    /// </summary>
    /// <param name="keyType">Key to acquire</param>
    /// <param name="buttonRequested">Returns the <see cref="InputButton"/> requested</param>
    /// <returns>True if that button was registered for listening</returns>
    private bool ButtonFromType(Keys keyType, out InputButton buttonRequested)
    {
        return this.keys.TryGetValue(keyType, out buttonRequested);
    }

    /// <summary>
    /// Check if a keyboard key is currently being held
    /// </summary>
    /// <param name="keyType">Key to check</param>
    /// <returns>True if button is being held</returns>
    public bool IsHeld(Keys keyType)
    {
        InputButton buttonRequested;
        if (ButtonFromType(keyType, out buttonRequested))
        {
            return buttonRequested.IsHeld;
        }
        else
        {
            // This should be converted to an error that doesn't break like an exception does.
            throw new Exception("This key does not have a listener. It must have a listener before it can be used.");
            //return false;
        }
    }

    /// <summary>
    /// Check if a keyboard key is in the down state (was just pressed down).
    /// </summary>
    /// <param name="keyType">Keyboard key to check</param>
    /// <returns>True if key has just been pressed down</returns>
    public bool IsDown(Keys keyType)
    {
        InputButton buttonRequested;
        if (ButtonFromType(keyType, out buttonRequested))
        {
            return buttonRequested.IsDown;
        }
        else
        {
            // This should be converted to an error that doesn't break like an exception does.
            throw new Exception("This key does not have a listener. It must have a listener before it can be used.");
        }
    }

    /// <summary>
    /// Check if a keyboard key is in the up state (not pressed down or held).
    /// </summary>
    /// <param name="keyType">Keyboard key to check</param>
    /// <returns>True if button is up</returns>
    public bool IsUp(Keys keyType)
    {
        InputButton buttonRequested;
        if (ButtonFromType(keyType, out buttonRequested))
        {
            return buttonRequested.IsUp;
        }
        else
        {
            // This should be converted to an error that doesn't break like an exception does.
            throw new Exception("This key does not have a listener. It must have a listener before it can be used.");
            //return false;
        }
    }

    /// <summary>
    /// Press down a keyboard key in the polling handler (not the actual key).
    /// </summary>
    /// <param name="keyType">Key to press</param>
    /// <returns>True if key has been registered with a listener</returns>
    /// <remarks>Private because only the message system should use this function</remarks>
    private bool Press(Keys keyType)
    {
        InputButton buttonRequested;
        if (ButtonFromType(keyType, out buttonRequested))
        {
            buttonRequested.Press();
            return true;
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    /// Release a keyboard key in the polling handler (not the actual gamepad button).
    /// </summary>
    /// <param name="keyType">Keyboard key to release</param>
    /// <returns>True if key has been registered with a listener.</returns>
    /// <remarks>Private because only the message system should use this function</remarks>
    private bool Release(Keys keyType)
    {
        InputButton buttonRequested;
        if (ButtonFromType(keyType, out buttonRequested))
        {
            buttonRequested.Release();
            buttonRequested.SetHeld(false);
            return true;
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    /// Set the held state of this keyboard key in the polling handler. This occurs whenever a key is being held.
    /// </summary>
    /// <param name="keyType">Keyboard key to hold</param>
    /// <param name="heldState">True for 'held', false to 'unhold'</param>
    /// <returns>True if key has been registered with a listener</returns>
    private bool SetHeld(Keys keyType, bool heldState)
    {
        InputButton buttonRequested;
        if (ButtonFromType(keyType, out buttonRequested))
        {
            buttonRequested.SetHeld(heldState);
            return true;
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    /// Set the lockable state of this keyboard key in the polling handler. Locked keys do not repeat or report as 'held'.
    /// </summary>
    /// <param name="keyType">Keyboard key for which to set lockable state</param>
    /// <param name="lockableState">'true' will set this key to 'lockable'</param>
    /// <returns>True if this key has been registered with a listener</returns>
    public bool SetLockable(Keys keyType, bool lockableState)
    {
        InputButton buttonRequested;
        if (ButtonFromType(keyType, out buttonRequested))
        {
            buttonRequested.SetLockable(lockableState);
            return true;
        }
        else
        {
            // This should be converted to an error that doesn't break like an exception does.
            throw new Exception("This key does not have a listener. It must have a listener before it can be used.");
            //return false;
        }
    }

    /// <summary>
    /// Message handler for the input polling handler.
    /// </summary>
    /// <param name="message">Incoming message</param>
    private void Game_GameMessage(IMessage message)
    {
        switch (message.Type)
        {
            case MessageType.KeyDown:
                MsgKeyPressed keyDownMessage = message as MsgKeyPressed;
                message.TypeCheck(keyDownMessage);

                Press(keyDownMessage.Key);
                break;

            case MessageType.KeyUp:
                MsgKeyReleased keyUpMessage = message as MsgKeyReleased;
                message.TypeCheck(keyUpMessage);

                Release(keyUpMessage.Key);
                break;

            case MessageType.KeyHeld:
                MsgKeyHeld keyPressMessage = message as MsgKeyHeld;
                message.TypeCheck(keyPressMessage);

                SetHeld(keyPressMessage.Key, true);
                break;
        }
    }
}

这个InputPollingHandler 监听特定于游戏引擎的关键消息。在您的版本中,您只需让KeyboardHandler 以您想要的任何形式将事件发送到InputPollingHandler,而不是本示例中显示的消息格式。请注意Press 函数,如下所示,因此我们检查密钥是否已注册。您可以将自己的代码放在该部分中以触发您正在侦听的事件,因此现在您只会收到您关心的键的此事件。

private bool Press(Keys keyType)
{
    InputButton buttonRequested;
    if (ButtonFromType(keyType, out buttonRequested))
    {
        // Your event sender could go here
        return true;
    }
    else
    {
        return false;
    }
}

希望您能了解它背后的一般概念。

【讨论】:

  • 我已经有这样的东西了。我只是想更进一步,能够触发/订阅 A_KeyDown、B_KeyDown 等,而无需创建十亿个事件。创建这么多事件简直是愚蠢的,更不用说维护问题了,因为我希望玩家可以根据自己的选择绑定东西。
  • InputPollingHandler 允许您在按下您订阅的某些键时注册通知。您可以有效地组合上述类,并且当您比较从最后一帧到当前帧的所有键事件时,您可以只发送已注册键的事件。
  • 是的,但我将如何创建这些事件?因为从技术上讲,该事件在用户实际注册之前并不存在。我可以有一个 KeyPressed 事件来启动轮询器,然后让轮询器过滤出绑定键,但是如何为每个绑定键创建/引发事件?
  • 您使用密钥枚举进行注册。在我的InputPollingHandler 样本中查找AddInputListener。他们使用该方法注册他们想要的键,然后每当调用 Press 函数时,它会检查该键是否已注册,如果是,您可以在那里触发事件。
  • 啊,我明白你的意思了。我得考虑一下。不过,我从未见过这样做过,因为您现在必须拥有许多特定的事件处理程序,每个键一个。通常我见过一个内部有一个开关的处理程序,开关指向关心事件的适当函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-16
  • 2014-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-13
相关资源
最近更新 更多