Всем привет! В данной статье я хочу рассказать о том, как можно сделать из своего Adndroid смартфона игровой контроллер (в простонародье — джойстик) для обычного ПК, а именно руль.
Поведение руля будет эмулироваться с помощью акселерометра. Для этого ведется непрерывное сканирование пространственных координат и эмпирическим путем подбираются границы для каждого направления движения. Исходя из этих данных в реальном времени генерируются сочетания игровых клавиш. Например: W — вперед, WA — вперед и влево и т. д.
Для доставки этих данных на ПК должен быть запущен сервер, который принимает входящие команды и эмулирует нажатия соответствующих клавиш. Сервер можно сделать однопоточным, чтобы подключался только один смартфон. Соединение будет осуществляться по Wi-Fi.
А теперь самое интересное…
Сервер реализован на C++ под Windows. Основная его задача — непрерывно принимать входящие сообщения и нажимать клавиши. Ниже приведен основной и простой код для данной задачи:
Нажатие клавиш:
Задача клиента — подключится к серверу и посылать комбинации клавиш для нажатия. Для этого используется акселерометр. Наша задача — получить пространственные координаты телефона. Это делается так:
Важный момент — считывать данные с акселерометра надо с определенным интервалом, иначе ваша программа моментально подвесится от непрерывных запросов к сенсору. Также при сворачивании надо отвязывать listener от акселерометра, чтобы ресурсы системы и батареи не тратились зря. Для этого в методах onResume и onPause делается следующее:
Код для генерации клавиш очень прост. Все границы определялись экспериментальным путем.
Испытывал я это все на Need For Speed Most Wanted. По ощущениям, конечно, не как настоящий руль, но играть можно.К сожалению, видео снять не получилось — в доме одна камера, и то на испытуемом телефоне. В ближайшее время обязательно выложу. Вот как это выглядит на ПК и на смартфоне:


Пока главный недостаток в сервере — это глобальное нажатие клавиш, не зависящее от приложения. В дальнейшем будет чем заняться. Еще одна проблема с которой я столкнулся — постоянный разрыв соединения. Не нашел лучшего решения, чем постоянный реконнект при обрыве связи.
Работа с акселерометром:
github.com/eburke/android_game_examples/blob/9d65f96aff5d60a2e765d8db894b7eb3fd02c315/GameExamples/src/com/stuffthathappens/games/Accel.java
Эмуляция нажатия клавиш:
www.codeproject.com/kb/system/keyboard.aspx
И самое главное — исходники:
dl.dropbox.com/u/5636452/game_controller.zip
Описание задачи
Поведение руля будет эмулироваться с помощью акселерометра. Для этого ведется непрерывное сканирование пространственных координат и эмпирическим путем подбираются границы для каждого направления движения. Исходя из этих данных в реальном времени генерируются сочетания игровых клавиш. Например: W — вперед, WA — вперед и влево и т. д.
Для доставки этих данных на ПК должен быть запущен сервер, который принимает входящие команды и эмулирует нажатия соответствующих клавиш. Сервер можно сделать однопоточным, чтобы подключался только один смартфон. Соединение будет осуществляться по Wi-Fi.
А теперь самое интересное…
Сервер
Сервер реализован на C++ под Windows. Основная его задача — непрерывно принимать входящие сообщения и нажимать клавиши. Ниже приведен основной и простой код для данной задачи:
Copy Source | Copy HTML
- while (true) {
- std::cout << "Wait for connection...\n";
-
- try {
- socket = server.Accept();
- } catch (const char *error) {
- std::cout << error << std::endl;
- exit( 0);
- }
-
- bool keepAlive = true;
- int timeout = 10000;
-
- setsockopt(server.getSocket(), SOL_SOCKET, SO_KEEPALIVE, (char*)&keepAlive, sizeof(bool));
- setsockopt(server.getSocket(), SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(int));
-
- std::cout << "Connected!\n";
-
- while (true) {
- std::string msg = socket->ReceiveLine();
-
- if (msg.empty())
- break;
-
- processKeys(msg.c_str());
- }
-
- std::cout << "Disconnected.\n\n\n";
- }
Нажатие клавиш:
Copy Source | Copy HTML
- void pressKeys(char key1, char key2) {
- // отжать предыдущие клавиши
-
- for (std::map<char, int>::iterator it = scanCodes.begin() ; it != scanCodes.end(); it++ ) {
- char curKey = it->first;
-
- if (curKey != key1 && curKey != key2)
- upKey(curKey);
- }
-
- downKey(key1);
- downKey(key2);
- }
-
- void downKey(char key) {
- keybd_event(VkKeyScan(key), scanCodes[key], 0, 0);
- }
-
- void upKey(char key) {
- keybd_event(VkKeyScan(key), scanCodes[key], KEYEVENTF_KEYUP, 0);
- }
Клиент
Задача клиента — подключится к серверу и посылать комбинации клавиш для нажатия. Для этого используется акселерометр. Наша задача — получить пространственные координаты телефона. Это делается так:
Copy Source | Copy HTML
- public class MainActivity extends Activity implements SensorEventListener {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- // ...
-
- sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
- accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
-
- //...
- }
-
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
- long curTime = System.currentTimeMillis();
-
- // считывание данных раз в 100 мс, иначе телефон загнется от сборщика мусора
- if (lastUpdate == -1 || (curTime - lastUpdate) > 100) {
- lastUpdate = curTime;
-
- x = event.values[DATA_X];
- y = event.values[DATA_Y];
- z = event.values[DATA_Z];
-
- xLabel.setText(String.format("X: %+2.5f", x));
- yLabel.setText(String.format("Y: %+2.5f", y));
- zLabel.setText(String.format("Z: %+2.5f", z));
-
- try {
- sendKeys(); // анализ координат для отправки клавиш на сервер
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- }
-
- }
-
- }
- }
Важный момент — считывать данные с акселерометра надо с определенным интервалом, иначе ваша программа моментально подвесится от непрерывных запросов к сенсору. Также при сворачивании надо отвязывать listener от акселерометра, чтобы ресурсы системы и батареи не тратились зря. Для этого в методах onResume и onPause делается следующее:
Copy Source | Copy HTML
- @Override
- protected void onResume() {
- super.onResume();
-
- sensorManager.registerListener(this, accelerometer, SENSOR_DELAY_NORMAL);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- sensorManager.unregisterListener(this);
- }
Код для генерации клавиш очень прост. Все границы определялись экспериментальным путем.
Copy Source | Copy HTML
- private String getKeys() {
- String keys = "";
-
- if (z > 7.5)
- keys += "W";
- else
- keys += "S";
-
- if (y < -3)
- keys += "A";
- else if (y > 3)
- keys += "D";
-
- return keys;
- }
Как это все работает
Испытывал я это все на Need For Speed Most Wanted. По ощущениям, конечно, не как настоящий руль, но играть можно.К сожалению, видео снять не получилось — в доме одна камера, и то на испытуемом телефоне. В ближайшее время обязательно выложу. Вот как это выглядит на ПК и на смартфоне:


Заключение
Пока главный недостаток в сервере — это глобальное нажатие клавиш, не зависящее от приложения. В дальнейшем будет чем заняться. Еще одна проблема с которой я столкнулся — постоянный разрыв соединения. Не нашел лучшего решения, чем постоянный реконнект при обрыве связи.
Что почитать
Работа с акселерометром:
github.com/eburke/android_game_examples/blob/9d65f96aff5d60a2e765d8db894b7eb3fd02c315/GameExamples/src/com/stuffthathappens/games/Accel.java
Эмуляция нажатия клавиш:
www.codeproject.com/kb/system/keyboard.aspx
И самое главное — исходники:
dl.dropbox.com/u/5636452/game_controller.zip