Pull to refresh

Игровой контроллер для ПК на Android

Reading time7 min
Views9.8K
Всем привет! В данной статье я хочу рассказать о том, как можно сделать из своего Adndroid смартфона игровой контроллер (в простонародье — джойстик) для обычного ПК, а именно руль.

Описание задачи


Поведение руля будет эмулироваться с помощью акселерометра. Для этого ведется непрерывное сканирование пространственных координат и эмпирическим путем подбираются границы для каждого направления движения. Исходя из этих данных в реальном времени генерируются сочетания игровых клавиш. Например: W — вперед, WA — вперед и влево и т. д.

Для доставки этих данных на ПК должен быть запущен сервер, который принимает входящие команды и эмулирует нажатия соответствующих клавиш. Сервер можно сделать однопоточным, чтобы подключался только один смартфон. Соединение будет осуществляться по Wi-Fi.

А теперь самое интересное…

Сервер


Сервер реализован на C++ под Windows. Основная его задача — непрерывно принимать входящие сообщения и нажимать клавиши. Ниже приведен основной и простой код для данной задачи:

Copy Source | Copy HTML
  1. while (true) {
  2.         std::cout << "Wait for connection...\n";
  3.  
  4.         try {
  5.             socket = server.Accept();
  6.         } catch (const char *error) {
  7.             std::cout << error << std::endl;
  8.             exit( 0);
  9.         }
  10.  
  11.         bool keepAlive = true;
  12.         int timeout = 10000;
  13.  
  14.         setsockopt(server.getSocket(), SOL_SOCKET, SO_KEEPALIVE, (char*)&keepAlive, sizeof(bool));
  15.         setsockopt(server.getSocket(), SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(int));
  16.  
  17.         std::cout << "Connected!\n";
  18.  
  19.         while (true) {
  20.             std::string msg = socket->ReceiveLine();
  21.  
  22.             if (msg.empty())
  23.                 break;
  24.  
  25.             processKeys(msg.c_str());
  26.         }
  27.  
  28.         std::cout << "Disconnected.\n\n\n";
  29.     }


Нажатие клавиш:

Copy Source | Copy HTML
  1. void pressKeys(char key1, char key2) {
  2.     // отжать предыдущие клавиши
  3.  
  4.     for (std::map<char, int>::iterator it = scanCodes.begin() ; it != scanCodes.end(); it++ ) {
  5.         char curKey = it->first;
  6.  
  7.         if (curKey != key1 && curKey != key2)
  8.             upKey(curKey);
  9.     }
  10.  
  11.     downKey(key1);
  12.     downKey(key2);
  13. }
  14.  
  15. void downKey(char key) {
  16.     keybd_event(VkKeyScan(key), scanCodes[key],  0,  0);
  17. }
  18.  
  19. void upKey(char key) {
  20.     keybd_event(VkKeyScan(key), scanCodes[key], KEYEVENTF_KEYUP,  0);
  21. }


Клиент


Задача клиента — подключится к серверу и посылать комбинации клавиш для нажатия. Для этого используется акселерометр. Наша задача — получить пространственные координаты телефона. Это делается так:
Copy Source | Copy HTML
  1. public class MainActivity extends Activity implements SensorEventListener {
  2.  
  3. @Override
  4.     public void onCreate(Bundle savedInstanceState) {
  5.        // ...
  6.  
  7.         sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
  8.         accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
  9.  
  10.        //...
  11.     }
  12.  
  13.    @Override
  14.     public void onSensorChanged(SensorEvent event) {
  15.         if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
  16.             long curTime = System.currentTimeMillis();
  17.  
  18.             // считывание данных раз в 100 мс, иначе телефон загнется от сборщика мусора
  19.             if (lastUpdate == -1 || (curTime - lastUpdate) > 100) {
  20.                 lastUpdate = curTime;
  21.  
  22.                 x = event.values[DATA_X];
  23.                 y = event.values[DATA_Y];
  24.                 z = event.values[DATA_Z];
  25.  
  26.                 xLabel.setText(String.format("X: %+2.5f", x));
  27.                 yLabel.setText(String.format("Y: %+2.5f", y));
  28.                 zLabel.setText(String.format("Z: %+2.5f", z));
  29.  
  30.                 try {
  31.                     sendKeys(); // анализ координат для отправки клавиш на сервер
  32.                 } catch (Exception e) {
  33.                     e.printStackTrace();
  34.                 }
  35.  
  36.             }
  37.  
  38.         }
  39.  
  40.     }
  41. }


Важный момент — считывать данные с акселерометра надо с определенным интервалом, иначе ваша программа моментально подвесится от непрерывных запросов к сенсору. Также при сворачивании надо отвязывать listener от акселерометра, чтобы ресурсы системы и батареи не тратились зря. Для этого в методах onResume и onPause делается следующее:

Copy Source | Copy HTML
  1. @Override
  2.     protected void onResume() {
  3.         super.onResume();
  4.  
  5.         sensorManager.registerListener(this, accelerometer, SENSOR_DELAY_NORMAL);
  6.     }
  7.  
  8.     @Override
  9.     protected void onPause() {
  10.         super.onPause();
  11.         sensorManager.unregisterListener(this);
  12.     }


Код для генерации клавиш очень прост. Все границы определялись экспериментальным путем.
Copy Source | Copy HTML
  1. private String getKeys() {
  2.         String keys = "";
  3.  
  4.         if (z > 7.5)
  5.             keys += "W";
  6.         else
  7.             keys += "S";
  8.  
  9.         if (y < -3)
  10.             keys += "A";
  11.         else if (y > 3)
  12.             keys += "D";
  13.  
  14.         return keys;
  15.     }

Как это все работает


Испытывал я это все на Need For Speed Most Wanted. По ощущениям, конечно, не как настоящий руль, но играть можно.К сожалению, видео снять не получилось — в доме одна камера, и то на испытуемом телефоне. В ближайшее время обязательно выложу. Вот как это выглядит на ПК и на смартфоне:

image

image

Заключение


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

Что почитать


Работа с акселерометром:
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
Tags:
Hubs:
+47
Comments30

Articles

Change theme settings