πŸ“œ ⬆️ ⬇️

Android PC Game Controller

Hello! In this article I want to talk about how you can make a game controller (in common people - a joystick) from your Adndroid smartphone for a regular PC, namely the steering wheel .

Task Description


The steering behavior will be emulated with an accelerometer. For this purpose, continuous scanning of spatial coordinates is conducted and boundaries for each direction of movement are selected empirically. Based on this data, real-time game key combinations are generated. For example: W - forward, WA - forward and left, etc.

To deliver this data to a PC, a server must be running that accepts incoming commands and emulates key presses. The server can be made single-threaded to connect only one smartphone. The connection will be via Wi-Fi.
')
And now the most interesting ...

Server


The server is implemented in C ++ under Windows. Its main task is to continuously receive incoming messages and press keys. The following is the basic and simple code for this task:

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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  1. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  2. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  3. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  4. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  5. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  6. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  7. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  8. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  9. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  10. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  11. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  12. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  13. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  14. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  15. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  16. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  17. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  18. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  19. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  20. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }
  21. 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, size of( bool )); setsockopt( server.getSocket( ), SOL_SOCKET, SO_RCVTIMEO, ( char* )&timeout, size of( 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" ; }


Keystrokes:

Copy Source | Copy HTML
  1. void pressKeys ( char key1, char key2 ) {
  2. // press previous keys
  3. for ( std :: map <char, int> :: iterator it = scanCodes.begin ( ); it! = scanCodes.end (); it ++) {
  4. char curKey = it-> first;
  5. if ( curKey! = key1 && curKey! = key2 )
  6. upKey ( curKey );
  7. }
  8. downKey ( key1 );
  9. downKey ( key2 );
  10. }
  11. void downKey ( char key ) {
  12. keybd_event ( VkKeyScan (key ), scanCodes [key], 0 , 0 );
  13. }
  14. void upKey ( char key ) {
  15. keybd_event ( VkKeyScan (key ), scanCodes [key], KEYEVENTF_KEYUP, 0 );
  16. }


Customer


The client's task is to connect to the server and send key combinations to press. To do this, use the accelerometer. Our task is to get the spatial coordinates of the phone. This is done like this:
Copy Source | Copy HTML
  1. public class MainActivity extends Activity implements SensorEventListener {
  2. @Override
  3. public void onCreate (Bundle savedInstanceState) {
  4. // ...
  5. sensorManager = (SensorManager) getSystemService (SENSOR_SERVICE);
  6. accelerometer = sensorManager.getDefaultSensor (Sensor.TYPE_ACCELEROMETER);
  7. // ...
  8. }
  9. @Override
  10. public void onSensorChanged (SensorEvent event ) {
  11. if ( event .sensor.getType () == Sensor.TYPE_ACCELEROMETER) {
  12. long curTime = System.currentTimeMillis ();
  13. // read data every 100 ms, otherwise the phone will be bent from the garbage collector
  14. if (lastUpdate == - 1 || (curTime - lastUpdate)> 100 ) {
  15. lastUpdate = curTime;
  16. x = event. Values ​​[DATA_X];
  17. y = event. Values ​​[DATA_Y];
  18. z = event. Values ​​[DATA_Z];
  19. xLabel.setText (String.format ( "X:% + 2.5f" , x));
  20. yLabel.setText (String.format ( "Y:% + 2.5f" , y));
  21. zLabel.setText (String.format ( "Z:% + 2.5f" , z));
  22. try {
  23. sendKeys (); // analysis of coordinates for sending keys to the server
  24. } catch (Exception e) {
  25. e.printStackTrace ();
  26. }
  27. }
  28. }
  29. }
  30. }


The important point is that data should be read from the accelerometer at a certain interval, otherwise your program will instantly suspend from continuous requests to the sensor. Also, when folding, listeners must be untied from the accelerometer so that system and battery resources are not wasted. To do this, the following is done in the onResume and onPause methods:

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


The key generation code is very simple. All boundaries were determined experimentally.
Copy Source | Copy HTML
  1. private String getKeys () {
  2. String keys = "" ;
  3. if (z> 7. 5 )
  4. keys + = "W" ;
  5. else
  6. keys + = "S" ;
  7. if (y <- 3 )
  8. keys + = "A" ;
  9. else if (y> 3 )
  10. keys + = "D" ;
  11. return keys;
  12. }

How it all works


I tested it all at Need For Speed ​​Most Wanted. Feels like, of course, not like a real steering wheel, but you can play. Unfortunately, the video did not work - there is one camera in the house, and then on the test phone. In the near future I will definitely post it. Here's how it looks on the PC and on the smartphone:

image

image

Conclusion


While the main drawback in the server is a global keystroke, independent of the application. In the future, will be what to do. Another problem I encountered is a permanent disconnect. I did not find a better solution than a constant reconnect when the connection is broken.

What to read


Work with accelerometer:
github.com/eburke/android_game_examples/blob/9d65f96aff5d60a2e765d8db894b7eb3fd02c315/GameExamples/src/com/stuffthathappens/games/Accel.java

Keystroke emulation:
www.codeproject.com/kb/system/keyboard.aspx

And most importantly - the source:
dl.dropbox.com/u/5636452/game_controller.zip

Source: https://habr.com/ru/post/111511/


All Articles