Если игрок управляет дискретным курсором или маркером, например, в сапёре или пасьянсе (если те играются с клавиатуры, разумеется) — так и надо, нужен системный автоповтор клавиш.
Если игрок управляет непрерывно движущимся персонажем, надо отойти от системных автоповторов и сделать две независимых подсистемы.
• Подсистема реагирования на клавиатуру. При нажатии на кнопку системный код превращается в виртуальную клавишу (например, «Идти вверх», «Выстрел» или «Бомба»). Затем в битовой маске управления в соответствующей позиции ставится 1. При отпускании — соответственно 0.
• Подсистема тактов игры. По таймеру проводится такт игры — акт управления всеми её персонажами, главным и врагами. В зависимости от состояния клавиш мы ведём Главного в ту или иную сторону.
• Третья подсистема — рендерер игры. Она может быть синхронной с тактами игры или нет, в зависимости от того, какое мы хотим поведение на слабых машинах и есть ли желание проработать асинхронщину (не обязательно многопоточную; например, рендерер может поминутно вызывать «прокрути такт игры, если надо»). А также звук и сеть — в нашем вопросе, впрочем, не до них (и вообще рендерингом занимается браузер).
Если тактовая частота игры невелика (скажем, до 16) и приходится подгонять героя «на один шажок», у меня есть маленький лайфхак, оставшийся с J2ME. Создаём две маски: «нажатая» и «удерживаемая».
• При нажатии: поставить 1 в обе маски.
• При отпускании: поставить 0 только в удерживаемую.
• При управлении героем: нажатая := нажатая OR удерживаемая; именно по этой «нажатой» управляем героем; нажатая := 0.
С новым годом, и хороших игр!