Kittoes0124
Kittoes0124

Reputation: 5080

How can I fire a very specifc KeyPressed event?

I have the following pseudocode:

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

So that during every Update() if a key is pressed, held, or released than the appropriate event can be raised. This is the actual code for the OnKeyPressed method:

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

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

However, this isn't really what I want because I don't necessarily care if a key was pressed. What I care about is if a PARTICULAR key is pressed. How can I code this to accomplish this goal without creating an absurd amount of events (one for each key I want to bind)?


RE: Nic-

Ok so I combine the classes and here's the resulting pseudo-code:

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

Now, the problem with the above is that the event being fired is still nothing more than an keyPressed event. It's not an A_keyPressed or B_keyPressed event. I can register things to the keyPressed event but that means that every Subscriber recieves a keyPressed event whenever ANY registered key is pressed.

I'm looking for:

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

Upvotes: 1

Views: 456

Answers (1)

Nic Foster
Nic Foster

Reputation: 2894

Here is a KeyboardHandler class I wrote for the QuickStart Game Engine. It keeps a list of key states from the previous frame, and then compares it will a list of keys from the current frame. Based on changes over the last frame it will send out an event for each key pressed, held, or released. It only sends out those 3 types of events, but within each event it includes the key that is related to this event.

Now, if you only care about particular keys, you can use another class I wrote, called an InputPollingHandler, in which you register only for the keys that you care about, and you will receive calls for those registered keys. This will also allow you to poll for the value of any registered key at any time. The InputPollingHandler also allows you to fire your own events if you want to artificially create input events, which is common in things like systems tests where you want to duplicate input from a recorded event. The code for the InputPollingHandler is below the code for the KeyboardHandler.

There isn't any way to only have XNA inform you about keys you may care about, so you use a helper class to listen for all keys and only send you information about the ones you want.

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


    }
}

And here is the InputPollingHandler class. The version in the full engine is much larger as it supports mouse and Xbox360 gamepads as well.

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

This InputPollingHandler listens for Key messages specific to the game engine. In your version you would just have the KeyboardHandler send the events to the InputPollingHandler in whatever form you would like, rather than in the messaging format shown in this sample. Notice in the Press function, shown again below, so we check if the key was registered for. You could put your own code within that section to fire an event you're listening for, so now you only receive this event for keys you care about.

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

Hopefully you'll get the general concept behind it.

Upvotes: 1

Related Questions