GetRawInputData vs GetAsyncKeyState()

Ну, я пытаюсь избежать использования устаревшего DirectInput.

Но мне нужно на каждом "кадре" или "итерации" игры выхватывать ВСЕ КЛЮЧЕВЫЕ СОСТОЯНИЯ, чтобы я мог действовать соответственно. Например, если игрок нажал на кнопку VK_RIGHT, то он просто передвинет чуть-чуть вправо на этот кадр.

Проблема с сообщениями WM_INPUT заключается в том, что они могут появляться непредсказуемым числом раз за кадр из-за способа написания игрового цикла:

    Сообщение MSG;
    пока ( 1)
    {
        if( PeekMessage( &message, NULL, 0, 0, PM_REMOVE))
        {
            if( message.message == WM_QUIT)
            {
                перерыв;  // залог, когда WM_QUIT
            }

            TranslateMessage( &message);
            DispatchMessage( &message);
        }
        еще
        {
            // Нет сообщений, поэтому запускаем игру.
            Обновить();
            Рисовать();
        }
    }

Таким образом, если в стеке находится более одного сообщения WM_INPUT, все они будут обработаны перед Update()/Draw().

Я решил эту проблему, используя массив BOOL, чтобы запомнить, какие ключи были недоступны:

    bool array_of_keys_that_are_down [256];

    case WM_INPUT:
        if(его ввод с клавиатуры)
        {
            array_of_keys_that_are_down[ VK_CODE ] = TRUE;
        }

Это работает нормально, потому что функция Update () проверяет

    void Update()
    {
        if( array_of_keys_that_are_down[ VK_RIGHT ])
        {
            // немного переместить игрока вправо
        }
    }

НО проблема в том, что сообщения WM_INPUT генерируются недостаточно часто. Между первым нажатием VK_RIGHT и последующими сообщениями VK_RIGHT существует задержка около 1 секунды, даже если игрок все время держал на нем палец. Это не похоже на DirectInput, где вы можете keyboard->GetDeviceState( 256, (void*)array_of_keys_that_are_down ); (выхватить все состояния клавиш в каждом кадре одним вызовом)

Так что я потерян. Кроме обращения к вызовам функций GetAsyncKeystate() для каждой клавиши, которую мне нужно отслеживать, я не вижу способа избежать использования DirectInput, если вы не можете надежно выхватить все состояния клавиш в каждом кадре.

Мне кажется, что DirectInput был очень хорошим решением этой проблемы, но если он устарел, то действительно должен быть какой-то способ сделать это удобно, используя только Win32 API.

В настоящее время array_of_keys_that_are_down сбрасывается обратно на каждый кадр FALSE.

    memset (array_of_keys_that_are_down, 0, sizeof (array_of_keys_that_are_down));

*РЕДАКТИРОВАТЬ

Я работал над этой проблемой, и одним из решений является сброс состояния ключа только после его освобождения.

    case WM_INPUT:
        if(его ввод с клавиатуры)
        {
            если (это нажатие вниз)
                array_of_keys_that_are_down[ VK_CODE ] = TRUE;
            еще
                array_of_keys_that_are_down[ VK_CODE ] = FALSE;
        }

Мне не нравится это решение, потому что оно кажется надуманным. Если пользователь отключается от приложения, нажимая клавишу, эта клавиша будет "зависать" до тех пор, пока он не переключится назад и снова не нажмет эту же клавишу, потому что мы никогда не получим сообщение WM_INPUT об ускорении. Это делает для странных ошибок "липкий ключ".

5 ответов

Решение

Ты можешь использовать GetKeyboardState вместо. То, что вы обычно хотите, это два массива; один хранит состояние ввода предыдущих кадров, а другой - текущий. Это позволяет такие вещи, как различие между удержанием и срабатыванием.

// note, cannot use bool because of specialization
std::vector<unsigned char> previous(256);
std::vector<unsigned char> current(256);

// in update_keys or similar:
current.swap(previous); // constant time, yay
GetKeyboardState(&current[0]); // normally do error checking

И вы сделали.

Представленное решение является правильным способом сделать это - игнорировать автоповтор и просто записывать состояния "вниз / вверх".

Чтобы решить проблему переключения задач, посмотрите на WM_ACTIVATE сообщение - это позволяет определить, когда окно теряет фокус. Когда это происходит с соответствующим окном, предположим, что все ключи отпущены. (Это похоже на то, что нужно делать с DirectInput при использовании неэксклюзивного кооперативного уровня.)

Лучшее (с точки зрения согласованности результатов и эффективности) решение вашей проблемы — использовать исходные данные. Основанный на событиях, быстрый, эффективный, никаких шансов пропустить ввод.

Вы можете пропустить входные данные с помощью вызовов GetAsyncKeyState. Например, предположим, что в начале итерации игры, работающей на частоте 60 Гц, вы вызываете GetAsyncKeyState, а клавиша не нажата. Все идет нормально. Затем через 5 мс вы нажимаете клавишу, скажем, VK_TAB, и удерживаете ее в течение 5 мс. Затем в начале следующей итерации игры (примерно через 6,67 мс) вы снова вызываете GetAsyncKeyState. Но к тому времени клавиша снова не нажимается. И с точки зрения игры он никогда не был нажат! Это может показаться слишком далеко идущим, но это не так. Я играл в игры, которые используют эту систему и пропускают ввод со скоростью 60 кадров в секунду. Разочарование и ненужность.

Я работаю над этой проблемой, и одно из решений - сбросить состояние ключа только после его выпуска.

       case WM_INPUT :
    if( its keyboard input )
    {
        if( its a down press )
            array_of_keys_that_are_down[ VK_CODE ] = TRUE ;
        else
            array_of_keys_that_are_down[ VK_CODE ] = FALSE ;
    }

Мне не нравится это решение, потому что оно кажется надуманным. Если пользователь отключается от приложения, когда нажата клавиша, то эта клавиша будет «зависать» до тех пор, пока он не переключится обратно и снова не нажмет ту же клавишу, потому что мы никогда не получим сообщение WM_INPUT при нажатии вверх. Это приводит к странным ошибкам «липких клавиш».

Это исправлено с помощью флага RIDEV_INPUTSINK в структуре RAWINPUTDEVICE, когда вы регистрируете свое необработанное устройство ввода : вы получаете свои сообщения, даже когда ваше окно не находится на переднем плане.

Похоже, ваша проблема не в используемых вами API. Вам нужен определенный способ взаимодействия с системой:

Но мне нужно на каждом «кадре» или «итерации» игры выхватывать ВСЕ КЛЮЧЕВЫЕ СОСТОЯНИЯ, чтобы я мог действовать соответственно. Например, если игрок нажал клавишу VK_RIGHT, то он сдвинется чуть-чуть вправо в этом кадре.

Проблема с сообщениями WM_INPUT заключается в том, что они могут появляться непредсказуемое количество раз за кадр из-за того, как записан игровой цикл.

Я решил эту проблему, используя массив BOOL, чтобы запомнить, какие клавиши были нажаты.

Вы хотите знать ключевые состояния, когда проверяете их, поэтому напишите свой собственный слой обработки ввода. Вам нужна система на основе опроса, поэтому создайте класс KeyboardStateHandler, заставьте его отвечать на все необработанные события ввода и отпускания клавиш, а затем в своем игровом цикле вы вызываете keyboardStateHandler.GetKeys() и получаете состояния всех клавиш.

Я думаю, что вы действительно хотите сделать, это создать соответствующий надежный уровень обработки ввода, который будет решением всех ваших проблем, которые вы подняли выше.

Здесь только несколько:

https://bell0bytes.eu/inputsystem/ Ссылка на веб-архив

https://www.gamedev.net/blog/355/entry-2250186-designing-a-robust-input-handling-system-for-games/ Ссылка на веб-архив

https://blog.gemserk.com/2012/08/23/decoupling-game-logic-from-input-handling-logic/ Ссылка на веб-архив

Неважно, что вы используете для фактического получения ввода: GetKeyAsyncState, DirectInput, Raw Input или другие методы, вы хотите отделить обработку ввода от игровой логики.

  1. используйте оконные сообщения для обнаружения нажатий клавиш и отслеживайте массив нажатых клавиш самостоятельно.
  2. следите за сообщением WM_ACTIVATE и используйте GetAsyncKeyboardState в этой точке, чтобы "исправить" массив в соответствии с фактическим состоянием.

Эта комбинация должна поддерживать последовательность.

Как вы сказали, что есть задержка, у меня сложилось впечатление, что вы хотите уменьшить задержку, почему бы не вызвать ' SystemParametersInfo ', чтобы установить типичную задержку и скорость, вам нужно взглянуть на задержку клавиатуры.'SPI_GETKEYBOARDDELAY' и скорость клавиатуры 'SPI_GETKEYBOARDSPEED'. Функция будет выглядеть так:

int SetKeyboardSpeed ​​(int nDelay){
   /* самый быстрый nDelay = 31, самый медленный nDelay = 0 */
   return (SystemParametersInfo(SPI_SETKEYBOARDSPEED, nDelay, NULL, SPIF_SENDCHANGE) > 0);
}
int SetKeyboardDelay(int nDelay){
   /* 0 = самый короткий (около 250 мс) до 3 самый длинный (около 1 с) */
   return (SystemParametersInfo(SPI_SETKEYBOARDDELAY, nDelay, NULL, SPIIF_SENDCHANGE) > 0);
}

Изменить: В ответ на комментарий Blindy для downvote - Вы на 100% правы - Никогда не осознавал, что, когда я ввел код в это... и да, вы указали на это пальцем, Никогда не меняйте global/system настройки без участия пользователя! Я исправлен комментарием Блинди. Пожалуйста, не обращайте внимания на мой ответ, так как он на 100% неверен!

Надеюсь, это поможет, С наилучшими пожеланиями, Том.

Другие вопросы по тегам