image

Введение


Для печати на клавиатуре необходимо неподвижно сидеть или стоять. Геймпады, в отличие от них, портативные и компактные. Управляя ими, можно ходить по комнате или прилечь на диван.

Из-за малого количества кнопок на геймпаде никто не рассматривал их как средство ввода объёмных текстов, например, в программировании.

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

Для геймпадов существует множество способов ввода текста. Если вы когда-нибудь играли в консольные игры, то, скорее всего, использовали какой-то из них.


Экранный ввод текста в Legend of Zelda

В Legend of Zelda игрок должен по очереди выбирать буквы при помощи крестовины со стрелками и каждый раз нажимать кнопку подтверждения для добавления буквы в поле ввода текста.

С тех пор были разработаны более эффективные способы ввода. Если вас это заинтересовало, то прочитайте статью на Gamasutra.

К сожалению, все найденные мной способы ввода имеют два серьёзных изъяна, из-за которых они не подходят для серьёзной работы:

  • Они недостаточно быстры
  • Они требуют визуальной обратной связи

Потребность в скорости очевидна. Визуальная обратная связь неприемлема, потому что занимает драгоценное место на экране и отвлекает пользователя, а значит, мешает состоянию потока и замедляет работу.

Однако при отсутствии обратной связи все жесты необходимо запоминать и практиковать, пока вы не сможете вводить их с достаточной точностью. Видеоигры вряд ли смогут заставить пользователя потратить пару недель на обучение способу ввода текста, но для способа ввода текста, который можно применять в любой программе, такая цена является приемлемой, а само обучение напоминает освоение сенсорного ввода.

В этом посте я вкратце расскажу об этапах создания системы ввода текста для геймпадов, пригодной в качестве замены объёмного клавиатурного ввода.

Определяемся с жестами


Для начала я создал инструмент визуализации движения аналоговых стиков геймпада на основе pygame языка Python. Для наглядности я дополнил инструмент так, чтобы он не только показывал текущие позиции стиков, но и предыдущие позиции всё более светлых оттенков серого, чтобы вы видели траектории, по которым движутся стики.

На рисунке ниже показаны одновременные движения обоих аналоговых стиков внутрь, вверх, наружу, вниз, снова внутрь и обратно в центр.


Визуализация паттернов движения аналоговых стиков

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

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


Левый стик переместился вверх и обратно в центр

Сколько направлений и какие именно направления можно точно выбрать вслепую? Рассмотрим следующий пример.


Левый стик переместился вверх, вниз, в центр, влево и вправо, а правый стик двигался по диагонали

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

Следующими найденными простейшими способами ввода стали одноэтапные и двухэтапные круговые движения.


Левый стик переместился вверх, влево и обратно к центру


Левый стик переместился вверх, влево, вниз и обратно в центр

Учитывая все придуманные пока жесты, мы получили 4 + 8 + 8 = 20 вариантов ввода на каждом стике.

Разумеется, оба стика можно двигать одновременно, создавая комбинированные жесты ввода.


Оба стика одновременно движутся вверх и обратно к центру

При комбинировании жестов в сумме получается 20 * 20 + 20 + 20 = 440 вариантов ввода, чего, по-моему, более чем достаточно.

Кодирование жестов


Я разделил пространство ввода каждого стика на 4 сектора и присвоил каждому сектору число.

Input spaces divided into sectors

Пространства ввода, разделённые на сектора

Затем я задал пороговую область вокруг центра, помогающую определить, находится ли стик в нейтральном положении, или же в одном из секторов.


Круговая пороговая область вокруг центра

Как видите, радиус пороговой области довольно велик. При экспериментах я определил, что это наилучший радиус, обеспечивающий наименьшее количество ошибок.

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

Stick movements for input ((0,), (2, 3))

Перемещения стиков для ввода ((0,), (2, 3))

Привязка жестов к действиям


В данном случае действиями являются просто клавиши клавиатуры. Кнопки-триггеры геймпада можно привязать к клавишам Shift, Ctrl, Alt и Super, что будет удобно, потому что эти клавиши используются в сочетаниях (например, Ctrl-C).

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

Самые часто нажимаемые клавиши должны быть привязаны к простейшим (а значит, самым быстрым) жестам. Я оценивал сложность жеста сложением длин вводов каждого стика. Например, показанный выше ввод ((0,), (2, 3)) имеет сложность 1 + 2 = 3.

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

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

Моя цель заключалась в разделении этих клавиш на две группы, одна для ввода левым стиком, вторая — правым. Для нахождения идеальных групп я создал граф, в котором узлы были клавишами, а взвешенные рёбра обозначали частоты комбинаций клавиш.

Я циклически удалял ребро с наименьшим весом, пока граф не стал двудольным. Если граф становился несвязанным, я рекурсивно применял к связанным компонентам алгоритм разбиения, а в конце объединял группы в независимые множества.

Рассмотрим следующий пример. Первая группа сложности состоит из всех вводов со сложностью 1, то есть ((0,), ()), ((1,), ()), ((2,), ()), ((3,), ()), ((), (0,)), ((), (1,)), ((), (2,)), ((), (3,)).

В этой группе 8 вводов, поэтому мы берём 8 самых частых клавиш из отсортированного списка. Это 'e', 'o', 't', 'a', 'i', 's', 'j', 'r'. Создаём граф с этими клавишами в качестве узлов и назначаем веса рёбрам между этими узлами, соответствующие частоте каждого сочетания клавиш.

Клавиши e и r сочетаются чаще всего, поэтому должны быть привязаны к разным стикам

При удалении слабых рёбер из графа он рано или поздно превращался в несвязанный.
The key j is frequent but isolated.

Клавиша j встречается часто, но она изолирована.

Возможно, вы недоумеваете, почему клавиша j является одной из 8 самых частых клавиш, но имеет такие слабые связи с остальными частыми клавишами. Причина заключается в том, что j активно используется при работе с VIM plus, в моей системе это часть комбинации горячих клавиш для переключения между окнами. Поэтому она чаще используется изолированно, чем в тексте.

Так как граф несвязанный, я продолжаю применять алгоритм к связанным компонентам. Подграф, состоящий только из узла j, уже является двудольным (j + пустое множество). Я рекурсивно применяю алгоритм к другому компоненту.
Component is bipartite after removing the weakest edges

После удаления самых слабых рёбер компонент становится двудольным

Затем компонент можно без проблем разделить на две группы без рёбер между узлами в группе.
Bipartite drawing of the component

Двудольная схема компонента

В конце я объединяю двудольные множества.
Final grouping for the first 8 keys

Группировка, получившаяся для первых 8 клавиш

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

Я повторил этот процесс для других групп сложности (только для ввода одним стиком.) Затем я сгенерировал все возможные комбинированные вводы, снова сгруппировал их по сложности и назначил оставшиеся клавиши этим вариантам ввода. Так как для комбинированных вводов требуется использование обоих стиков, проблема разделения клавиш на две группы здесь не возникает.

Я применил пакет pyautogui языка Python для генерации клавиатурных событий при срабатывании действий.

Практика


Я использовал тот же тренажёр сенсорного ввода ktouch, которым уже пользовался для обучения печати на клавиатуре почти два десятка лет назад. Для этой цели я создал специализированные уроки.

Practising gamepad touch typing in ktouch

Практикуем аналоговый ввод на геймпаде в ktouch

Наблюдения


  • Хотя процесс Python, в котором запущена эта система ввода, обычно потреблял не больше 10% ресурсов процессора, если бы он должен был постоянно выполняться в фоновом режиме, то я бы реализовал его заново и оптимизировал на языке более низкого уровня, чтобы процессор мог заниматься более затратными задачаи.
  • После покупки геймпада DualShock4 я понял, то довольно точно могу выполнять диагональный ввод. Интеграция диагонального ввода уменьшит количество более сложных вариантов ввода, а значит, увеличит скорость.
  • После недели практики я могу печатать на геймпаде всего в два раза медленнее, чем на клавиатуре. Учитывая мою безумную скорость печати на клавиатуре, это совсем неплохо. Для дополнительной выгоды я использую оставшиеся варианты ввода, привязав их к специальным действиям, например, к горячим клавишам или операциям конкретных программ.
  • Похоже, сильнее всего напрягают большие пальцы круговые движения. Вероятно, стоит заменить их более долгими, но более прямыми вариантами ввода.
  • Напряжение больших пальцев снижается после практики. Однако в начале стоит практиковаться всего по несколько минут за раз.

Заключание


Всего за пару дней я создал эффективную систему ввода для геймпадов. Можно внести множество усовершенствований, однако эта проверка концепции демонстрирует, что эффективный ввод текста на геймпаде возможен. Код проекта выложен на github.