Making a joystick from Windows Phone

A little background.


imageOne evening a son came up to me and said that he wanted to play Mario. In the summer, at his grandmother’s cottage, he liked to “chop off” in rainy weather. And it’s just outside the window. Without hesitation, I downloaded the first emulator of an 8-bit console and the game that came across to him. However, it turned out that the pleasure of playing the keyboard is completely different. Going to buy a joystick was too late. And then I thought that you can do without it. At hand, we had an old Nokia Lumia, its size and shape approximately coincided with our needs. It was decided to write a joystick. My son went to draw a design on a piece of paper in a box, and dad went to brew coffee and think how to implement this idea with the least time.

I decided to follow the path of least (from my point of view) resistance. The prefix emulator in the settings must indicate the pressed buttons, which means our application must press the buttons. Pressing buttons can be emulated using the good old WINAPI.

The ultimate idea was a client-server application. When the client (phone) presses the button, it sends a request to the server, which, in turn, depending on what has arrived, emulates pressing or releasing the keyboard button. Communication is via sockets. It seems that everything is simple. Start to do.

Server side


We put on the form textbox with the same name textBox. In it we will show what comes from the phone.

image

Getting started with sockets.

First we connect them:

using System.Net;
using System.Net.Sockets;

We get a socket and a buffer in which everything will come:


public partial class ServerForm : Form
    {
        private Socket _serverSocket, _clientSocket;
        private byte[] _buffer;

We write a function that launches our server.

private void StartServer()
        {
            try
            {
                _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _serverSocket.Bind(new IPEndPoint(IPAddress.Any, 3333));
                _serverSocket.Listen(0);
                _serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

And accordingly we start it at the very beginning.

 public ServerForm()
        {
            InitializeComponent();
	      StartServer();
        }

We connect the work with the keyboard and write a couple of functions: one presses a key, the other releases it.

[ DllImport("user32.dll")]
        private static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
        private const int KEYEVENTF_EXTENDEDKEY = 1;
        private const int KEYEVENTF_KEYUP = 2;
        public static void KeyDown(Keys vKey)
        {
            keybd_event((byte)vKey, 0, KEYEVENTF_EXTENDEDKEY, 0);
        }
        public static void KeyUp(Keys vKey)
        {
                keybd_event((byte)vKey, 0, KEYEVENTF_KEYUP, 0);     
        }

We try to get something and, if everything is in order, then add to the textBox what we received and press (release) the button.

private void ReceiveCallback(IAsyncResult AR)
        {
            try
            {
                int received = _clientSocket.EndReceive(AR);
                Array.Resize(ref _buffer, received);
                string text = Encoding.ASCII.GetString(_buffer);
                // нажимаем или отпускаем кнопку
		    AppendToTextBox(text);
		    // ------------------
                Array.Resize(ref _buffer, _clientSocket.ReceiveBufferSize);
                _clientSocket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), null);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

Function for emulating pressing and releasing a button, depending on what came:

private  void AppendToTextBox(string text)
        {
            MethodInvoker invoker = new MethodInvoker(delegate
                {
                    string text_before = text;
                    string exitW = text;
         //нажимаем
                    if (text == "a")
                    {
                        KeyUp(Keys.D);
                        KeyDown(Keys.A);
                        textBox.Text += text + " ";
                    }
       //отжимаем          
                    if (text == "a1" )
                    {
                        KeyUp(Keys.A);
                        textBox.Text += text + " ";
                    }                   
                });
            this.Invoke(invoker);
        }

During testing, it was found that the "forward" button may stick for unknown reasons. In this case, the player reflexively presses “back”, trying to slow down. In order to have such an opportunity, when you press "back", just in case, we raise the button "forward".

                    if (text == "a")
                    {
                        KeyUp(Keys.D);
                        KeyDown(Keys.A);
                        textBox.Text += text + " ";
                    }

The reason for the button sticking is incomprehensible.

Client part


I wanted to play Mario more and more, so it was decided to configure the connection and make the connection on the same screen.
At the top, we placed the fields for entering the ip address and port, the connection button and textblock showing the status. The standard AppBarButton were taken as control buttons, which the son himself arranged, in accordance with his own design. On his own idea, it was decided to abandon the standard background. The design is finished. It remains to be done to make it all work.

image

We cut the sockets:

using Windows.Networking.Sockets;
using Windows.Networking;
using Windows.Storage.Streams;

Open a new socket:

 StreamSocket clientSocket = new StreamSocket();

Trying to connect:

private async void btnConnect_Click(object sender, RoutedEventArgs e)
        {
            HostName host = new HostName(textBoxIP.Text);
            string port = textBoxPort.Text;
            if (connected)
            {
                StatusText.Text = "уже подключен";
                return;
            }
            try
            {
                StatusText.Text = "попытка подключения ...";    
                await clientSocket.ConnectAsync(host, port);
                connected = true;
                StatusText.Text = "подключение установлено" + Environment.NewLine;
            }
            catch (Exception exception)
            {               
                if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown)
                {
                    throw;
                }
                StatusText.Text = "не удалось установить подключение: "; 
                closing = true;  
                clientSocket.Dispose();
                clientSocket = null;
            }
        }

We send data to the server:

private async void sendkey(string key)
        {
            if (!connected)
            {
                StatusText.Text = "необходимо подключение";
                return;
            }
             try
            {
                StatusText.Text = "попытка отправки данных ...";              
                DataWriter writer = new DataWriter(clientSocket.OutputStream);
                writer.WriteString(key);
                await writer.StoreAsync();
                StatusText.Text = "отправка успешна" + Environment.NewLine;
                writer.DetachStream();
                writer.Dispose();
            }
            catch (Exception exception)
            {
                if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown)
                {
                    throw;
                }
                StatusText.Text = "Не удалось оправить данные ";
                closing = true;
                clientSocket.Dispose();
                clientSocket = null;
                connected = false;
            }
        }

Suddenly a problem arose. The Click event of a button does not occur at the moment it is pressed, but at the moment it is released after clicking. After digging briefly in msdn, it was decided to use GotFocus as a click, and Click as a release button, subsequently the PointerReleased event was added for the same purpose (this allowed to release one button while holding the other). When you release the button, the focus still remains on it, to avoid this, we pass it on to any other element, in this case it is a button that is not involved in the management with the name btnConnect:

//посылаем значения кнопок
        //вверх
        private void gotFocusUp(object sender, RoutedEventArgs e)
        {
            sendkey("w");
        }
        private void lostFocusUp(object sender, RoutedEventArgs e)
        {
            sendkey("w1");
            btnConnect.Focus(FocusState.Programmatic);
        }
        private void lostFocusUp(object sender, PointerRoutedEventArgs e)
        {
            sendkey("w1");
            btnConnect.Focus(FocusState.Programmatic);
        }

Result


image

Pros: Mario runs and jumps, rescues the princess. The son is pleased.

Further plans:

1. Make a server search on the phone.
2. Deal with sticks (although comrades say that sticks add realism, and not at all a bug, but a feature).
3. Redraw the design.
4. Add the ability to play together
5. Comb the code. (I don’t like the amount of if in the server side and the fact that each button has its own almost identical events in the client)

Link for those who are interested on gitHub.

Also popular now: