WM_KEYUP 重复调用保持键

WM_KEYUP calls repeatedly for held key

我正在使用 DirectX TK 创建飞扬的小鸟克隆。对于输入,我使用 space 栏。接受输入的代码是:

 case WM_KEYUP:
        Keyboard::ProcessMessage(message, wParam, lParam);
        OutputDebugStringA("Up");
        break;

我认为 WM_KEYUP 应该只在释放按键时调用一次,这在您只想接收一次按键输入时非常有用;但是,当我 运行 这段代码时,它会在每次按键时至少打印两次 "Up" 。如果我按住 space 栏,它会重复调用它直到我松开它。我应该如何解决这个问题?谢谢!

回调函数的代码如下:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static bool s_in_sizemove = false;
    static bool s_in_suspend = false;
    static bool s_minimized = false;
    static bool s_fullscreen = false;
    // TODO: Set s_fullscreen to true if defaulting to fullscreen.

    auto game = reinterpret_cast<Game*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));

    switch (message)
    {
    case WM_PAINT:
        if (s_in_sizemove && game)
        {
            game->Tick();
        }
        else
        {
            PAINTSTRUCT ps;
            (void)BeginPaint(hWnd, &ps);
            EndPaint(hWnd, &ps);
        }
        break;

    case WM_SIZE:
        if (wParam == SIZE_MINIMIZED)
        {
            if (!s_minimized)
            {
                s_minimized = true;
                if (!s_in_suspend && game)
                    game->OnSuspending();
                s_in_suspend = true;
            }
        }
        else if (s_minimized)
        {
            s_minimized = false;
            if (s_in_suspend && game)
                game->OnResuming();
            s_in_suspend = false;
        }
        else if (!s_in_sizemove && game)
        {
            game->OnWindowSizeChanged(LOWORD(lParam), HIWORD(lParam));
        }
        break;

    case WM_ENTERSIZEMOVE:
        s_in_sizemove = true;
        break;

    case WM_EXITSIZEMOVE:
        s_in_sizemove = false;
        if (game)
        {
            RECT rc;
            GetClientRect(hWnd, &rc);

            game->OnWindowSizeChanged(rc.right - rc.left, rc.bottom - rc.top);
        }
        break;

    case WM_GETMINMAXINFO:
        if (lParam)
        {
            auto info = reinterpret_cast<MINMAXINFO*>(lParam);
            info->ptMinTrackSize.x = 320;
            info->ptMinTrackSize.y = 200;
        }
        break;

    case WM_ACTIVATEAPP:
        if (game)
        {
            if (wParam)
            {
                game->OnActivated();
            }
            else
            {
                game->OnDeactivated();
            }
        }
        Keyboard::ProcessMessage(message, wParam, lParam);
        Mouse::ProcessMessage(message, wParam, lParam);
        break;

    case WM_POWERBROADCAST:
        switch (wParam)
        {
        case PBT_APMQUERYSUSPEND:
            if (!s_in_suspend && game)
                game->OnSuspending();
            s_in_suspend = true;
            return TRUE;

        case PBT_APMRESUMESUSPEND:
            if (!s_minimized)
            {
                if (s_in_suspend && game)
                    game->OnResuming();
                s_in_suspend = false;
            }
            return TRUE;
        }
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    case WM_SYSKEYDOWN:
        if (wParam == VK_RETURN && (lParam & 0x60000000) == 0x20000000)
        {
            // Implements the classic ALT+ENTER fullscreen toggle
            if (s_fullscreen)
            {
                SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
                SetWindowLongPtr(hWnd, GWL_EXSTYLE, 0);

                int width = 800;
                int height = 600;
                if (game)
                    game->GetDefaultSize(width, height);

                ShowWindow(hWnd, SW_SHOWNORMAL);

                SetWindowPos(hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED);
            }
            else
            {
                SetWindowLongPtr(hWnd, GWL_STYLE, 0);
                SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST);

                SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);

                ShowWindow(hWnd, SW_SHOWMAXIMIZED);
            }

            s_fullscreen = !s_fullscreen;
        }
        break;

    case WM_MENUCHAR:
        // A menu is active and the user presses a key that does not correspond
        // to any mnemonic or accelerator key. Ignore so we don't produce an error beep.
        return MAKELRESULT(0, MNC_CLOSE);
    case WM_INPUT:
    case WM_MOUSEMOVE:
    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
    case WM_MBUTTONDOWN:
    case WM_MBUTTONUP:
        Mouse::ProcessMessage(message, wParam, lParam);
        break;
    case WM_MOUSEWHEEL:
    case WM_XBUTTONDOWN:
    case WM_XBUTTONUP:
    case WM_MOUSEHOVER:
        Mouse::ProcessMessage(message, wParam, lParam);
        break;
    case WM_KEYDOWN: 
    case WM_KEYUP:
        Keyboard::ProcessMessage(message, wParam, lParam);
        break;
    case WM_SYSKEYUP:
        Keyboard::ProcessMessage(message, wParam, lParam);
        break;
    }

    return DefWindowProc(hWnd, message, wParam, lParam);
}

DirectX Tool KitKeyboardclass 用于像 'controller' 一样处理键盘,因此您可以获得即时快照GetState 的状态:所有打开的位都是按下键,所有关闭的位都是向上键。

如果您希望某个事件仅在 transition 时触发,那就是各种 "button tracker" classes 的用武之地:

std::unique_ptr<DirectX::Keyboard>          m_keyboard;
DirectX::Keyboard::KeyboardStateTracker     m_keyboardButtons;
m_keyboard = std::make_unique<Keyboard>();
void Game::Update(DX::StepTimer const&)
{
    float elapsedTime = float(timer.GetElapsedSeconds());

    auto kb = m_keyboard->GetState();
    m_keyboardButtons.Update(kb);

    // These controls are scaled by framerate, so we just want to use them 'raw'
    if (kb.A || kb.D)
    {
        m_yaw += (kb.D ? 0.1f : -0.1f);
    }

    if (kb.W || kb.S)
    {
        m_pitch += (kb.W ? 0.1f : -0.1f);
    }

    if (kb.Home)
    {
        m_yaw = m_pitch = 0.f;
    }

    // Later yaw/pitch are used scaled by elapsedTime 

    // These keys we want 'debounced'
    if (m_keyboardButtons.IsKeyPressed(Keyboard::Q))
    {
        m_usedInstanceCount = std::max(c_minInstanceCount, m_usedInstanceCount - 1000);
    }
    else if (m_keyboardButtons.IsKeyPressed(Keyboard::E))
    {
        m_usedInstanceCount = std::min(c_maxInstances, m_usedInstanceCount + 1000);
    }

    if (m_keyboardButtons.IsKeyPressed(Keyboard::Space))
    {
        Fire();
    }
}

键盘跟踪器为您提供 "keys that were just pressed" 和 "keys that were just released"。原始状态告诉你 "keys down" 和 "keys up".

同样的原则适用于 GamePad::ButtonStateTrackerMouse::ButtonStateTracker。因为按钮比较少,我明确报了四种状态:UPHELDRELEASEDPRESSED。和键盘一样的信息,只是更容易使用。

GitHub wiki