如何开启非常具体的Key Press活动?

时间:2012-02-23 02:26:21

标签: c# events xna

我有以下伪代码:

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));
    }
}

但是,这并不是我想要的,因为我不一定关心是否按下了按键。我关心的是如果按下一个特殊的键。如何在不创建荒谬的事件数量的情况下对此进行编码以实现此目标(每个键我想绑定一个)?


RE:Nic -

好的,所以我将这些类组合在一起,这里是生成的伪代码:

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
    }
}

1 个答案:

答案 0 :(得分: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;
    }
}

希望你能得到它背后的一般概念。