2
\$\begingroup\$

I am designing a keyboard class that can detect the keyboard key press only one time but I still cannot figure out the way to do it. My goal is just check and perform the action only once when the same key is keep pressing or keep holding and no action performed when 2 action keys are pressed at the same time. For example, when I keep pressing or holding key A, action 1 is only perform once. Then I keep pressing or holding another key B, action 2 is also perform once. I cannot peform any action if I press key A and B at the same time.

There is two class inside KeyboardClass header and cpp file i.e. KeyboardClientClass and KeyboardServerClass.

////////////////////////////////////////////////////////////////////////////////
// Filename: KeyboardClass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _KEYBOARDCLASS_H_
#define _KEYBOARDCLASS_H_

////////////////////////////////////////////////////////////////////////////////
// Class prototype
////////////////////////////////////////////////////////////////////////////////
class KeyboardServerClass;


////////////////////////////////////////////////////////////////////////////////
// Class name: KeyboardClientClass
////////////////////////////////////////////////////////////////////////////////
class KeyboardClientClass
{
public:
    KeyboardClientClass( const KeyboardServerClass& KeyboardServer );
    ~KeyboardClientClass();
    bool KeyIsPressed( unsigned char keycode ) const;
    bool KeyIsPressedOnce( unsigned char keycode );

private:
    unsigned char tempKeyCode;
    const KeyboardServerClass& server;
};

class KeyboardServerClass
{
    friend KeyboardClientClass;

public:
    KeyboardServerClass();
    ~KeyboardServerClass();
    void OnKeyPressed( unsigned char keycode );
    void OnKeyReleased( unsigned char keycode );

   private:
        static const int nKeys = 256;
        bool keystates[ nKeys ];
        bool isKeyDown;
    };

    #endif


////////////////////////////////////////////////////////////////////////////////
// Filename: KeyboardClass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "KeyboardClass.h"

KeyboardClientClass::KeyboardClientClass( const KeyboardServerClass& KeyboardServer ) 
: server ( KeyboardServer )
{
    tempKeyCode = 257;
}


KeyboardClientClass::~KeyboardClientClass()
{}


bool KeyboardClientClass::KeyIsPressed( unsigned char keycode ) const
{
    return server.keystates[ keycode ];
}



bool KeyboardClientClass::KeyIsPressedOnce( unsigned char keycode )
{
    if ( tempKeyCode != keycode )
    {
        tempKeyCode = keycode;
        return server.keystates[ keycode ];
    }
    else
    {
        return false;
    }
}


KeyboardServerClass::KeyboardServerClass()
{
    for ( int x = 0; x < nKeys; x++ )
    {
        keystates[ x ] = false;
    }
}


KeyboardServerClass::~KeyboardServerClass()
{
    isKeyDown = true;
}


void KeyboardServerClass::OnKeyPressed( unsigned char keycode )
{
    keystates [ keycode ] = true;
    isKeyDown = false;
}


void KeyboardServerClass::OnKeyReleased( unsigned char keycode )
{
    keystates [ keycode ] = false;
    isKeyDown = true;
}

Firstly, I create the a KeyboardServer object to keep track of the keyboard messgae from Windows Procedure.

LRESULT CALLBACK SystemClass::MessageHandler( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch ( msg )
    {
        //************ KEYBOARD MESSAGES ************ //

        // Check if a key has been pressed on the keyboard
        case WM_KEYDOWN:
        {
            if ( wParam == VK_ESCAPE )
                PostQuitMessage( 0 );

            // If a key is pressed send it to the KeyboardServer object so it can record the state
            m_KeyboardServer.OnKeyPressed( wParam );
            break;
        }

        // Check if a key has been released on the keyboard
        case WM_KEYUP:
        {
            // If a key is released then send it to the KeyboardServer object so it can unset the state for that key
            m_KeyboardServer.OnKeyReleased( wParam );
            break;
        }

        // ************ END KEYBOARD MESSAGES ************ //
}

Then, I create a KeyboardClient object at Game class to check whether a key has been pressed or not and perform the action based on key pressed.

if ( m_Keyboard.KeyIsPressed( KEY_B ) )
    // Do action A
else if ( m_Keyboard.KeyIsPressed( KEY_N ) )
    // Do action B
\$\endgroup\$
1
  • \$\begingroup\$ I'm not familiar to DirectX, and I don't understand all your code, but in XNA, I'd just create booleans for each key and store wheather the key is pressed or not. If you now detect "a key is down", just check "Was it down last update" and fire the event just if it wasn't. \$\endgroup\$
    – jalgames
    Commented Sep 29, 2013 at 9:31

3 Answers 3

1
\$\begingroup\$

Pseudo code:

currentKeyboardState
previousKeyboardState

void UpdateInput()
    store currentstate to previousstate
    get new state to currentstate

Now that we have stored the last state and we know the current state, we can check if key was NOT pressed in last frame and is now pressed. So we know if key was pressed down just now.

if(currentKeyboardState.KeyIsPressed( KEY_B ) && !previousKeyboardState.KeyIsPressed( KEY_B ))

We can also detect if key is being held down, by checking if it was down last frame and now.

if(currentKeyboardState.KeyIsPressed( KEY_B ) && previousKeyboardState.KeyIsPressed( KEY_B ))

Checking if key was down last frame, but is not anymore, we can say that it was released.

if(!currentKeyboardState.KeyIsPressed( KEY_B ) && previousKeyboardState.KeyIsPressed( KEY_B ))

With these, i think you can create what you need.

TL;DR;

Store previous keyboard state, so you can compare current state to previous state and determine if something fishy is going on.

\$\endgroup\$
2
  • \$\begingroup\$ You can also detect if the key was released by checking if the key was down but isn't anymore. \$\endgroup\$
    – Casey
    Commented Sep 29, 2013 at 10:08
  • \$\begingroup\$ Right, somehow forgot that. I edited answer and added that as an example too. \$\endgroup\$
    – Katu
    Commented Sep 29, 2013 at 11:00
0
\$\begingroup\$

When you detect the WM_KEYDOWN message, you can use the bits in the LPARAM argument in your message handler to determine if the key stroke is repeated and also what the key's previous state was.

Store if the key is repeated as well as pressed/released. When you keep pressing or holding key A, only perform action 1 if the repeat count is 0.

if ( m_Keyboard.KeyIsPressed( KEY_A ) && !m_Keyboard.KeyIsRepeated( KEY_A ) )
    // Do action A

Note: If the keystroke is held long enough, multiple messages are sent which might change the repeat count.

To cancel out keys A and B, you can add up a count of the number of keys pressed that you want to be exclusive of each other in your keyboard client and only perform the action if there is a total of 1 key pressed.

\$\endgroup\$
0
\$\begingroup\$

What you can do is store boolean values for if the key was down or released and create some simple getters, a setter, and a "resetter" like so:

struct Key
{
    bool down;
    bool released;
    bool pressed;
};
bool KeyInput::IsKeyDown(uint32 keyCode)
{
    return s_keys[keyCode].down;
}

bool KeyInput::WasKeyPressed(uint32 keyCode)
{
    return s_keys[keyCode].pressed;
}
bool KeyInput::WasKeyReleased(uint32 keyCode)
{
    return s_keys[keyCode].released;
}

void KeyInput::SetKey(uint32 keyCode, bool isDown, bool isReleased, bool isPressed)
{
    s_keys[keyCode].down = isDown;
    s_keys[keyCode].released = isReleased;
    s_keys[keyCode].pressed = isPressed;
}

void KeyInput::ResetKeys()
{
    for (int i = 0; i < NUM_KEYS; i++)
    {
        s_keys[i].released = false;
        s_keys[i].pressed = false;
    }
}

Then in your WM_KEYDOWN and KEYUP handles:

case WM_KEYUP:
{
    bool wasDown = ((lParam & (1 << 30)) != 0);
    KeyInput::SetKey(wParam, false, wasDown, false);
}
break;
case WM_KEYDOWN:
{
    bool wasDown = ((lParam & (1 << 30)) != 0);
    bool isDown = ((lParam & (1 << 31)) == 0);
    KeyInput::SetKey(wParam, isDown, false, !wasDown && isDown);
}
break;

And then finally before you handle the messages you call the reset function, and everything after your message handling would be your game loop, so something like:

        while (g_running)
        {
            KeyInput::ResetKeyReleased();

            MSG message;
            while (PeekMessage(&message, 0, 0, 0, PM_REMOVE))
            {
                if (message.message == WM_QUIT)
                    g_running = false;
                TranslateMessage(&message);
                DispatchMessageA(&message);
            }

            if (KeyInput::IsKeyDown('W'))
                OutputDebugStringA("W is down\n");
            if (KeyInput::WasKeyReleased(VK_SPACE))
                OutputDebugStringA("SPACE was just released\n");
            if (KeyInput::WasKeyPressed('G'))
                OutputDebugStringA("G was just pressed\n");
        }

Hopefully this helped someone!

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .