Android 手機模擬游戲手柄(USB,C#,winio)


Android 手機模擬游戲手柄(USB,C#,winio)

使用的知識點:Android服務器通過USB連接PC端,winio發送鍵盤消息,Socket編程,線程,Android多點觸控

先說下思路,首先在Android端開啟服務器程序,然后在PC端開啟一個服務器程序模擬發送鍵盤信息(C#編寫)。手機和PC用USB連接,Android和PC的通信通過Socket完成。

 

PC客戶端程序:

雖然有很多方法可以模擬發送鍵盤信息如:PostMessage,keybd_event等。這些都是將按鍵信息發送給系統的消息隊列,然后再響應。但是很多游戲使用了DirectX技術繞過了系統的消息隊列。

 

我用了一個開源的項目,winio。可以將鍵盤的信息直接發給主板,這樣一些游戲也可以接收了按鍵消息了。Winio的相關資料可以在網上搜到。由於我的系統是64位的,在使用過程中遇到了一些問題,主要是winio驅動簽名的問題。具體解決方法:http://www.cnblogs.com/wangqian0realmagic/archive/2012/03/26/2418671.html

我用VS2010進行客戶端的開發,這時動態載入winio64.dll時,會出現如下錯誤“System.DllNotFoundException……無法加載 DLL“WinIo64.dll”: 找不到指定的模塊。 (異常來自 HRESULT:0x8007007E)“。是因為VS2010內部平台默認是X86的,所以要改一下,生成->配置管理器->平台,設為X64即可。

 

PC端和Android端的USB通信要經過端口轉換,要在C#中動態使用adb.exe的forward命令。

代碼:

MsgTcpClient:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;

namespace GameHandles
{
    class MsgTcpClient
    {
        //數據定義
        Socket msgClient;
        static int serverport = 60001;
        string ip;

        public MsgTcpClient()
        {
            msgClient = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
        }

        //嘗試連接如果成功返回true,失敗返回false
        public bool Connect(string ipstring)
        {
            ip = ipstring;
            IPEndPoint ipendpoint = new IPEndPoint(IPAddress.Parse(ip), serverport);
            try
            {
                msgClient.Connect(ipendpoint);
                return true;
            }catch
            {
                return false;
            }
        }

        //接收獲得的命令
        public string getMsg()
        {
            string msgGot = "";
            byte[] tmpmsg = new byte[8];
            int length = 0;
            try
            {
                Console.WriteLine("start to recieve");
                length = msgClient.Receive(tmpmsg, tmpmsg.Length, 0);
                msgGot = Encoding.ASCII.GetString(tmpmsg, 0, length);
            }
            catch
            { }
            return msgGot;
        }

        public void Close()
        {
            msgClient.Close();
        }
    }
}

  主程序部分:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Diagnostics;
using System.Threading;

namespace GameHandles
{
    public partial class Form1 : Form
    {
        //數據定義
        winioManpulate winioKey;
        MsgTcpClient msgClient;//接收Android服務器發來的信息
        string serverip = "127.0.0.1";
        Thread winioThread;
        Keys[] keycode = {Keys.A,Keys.W,Keys.D,Keys.S,Keys.U,Keys.J,Keys.K,Keys.I};
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            winioKey = new winioManpulate();
            msgClient = new MsgTcpClient();
        }

        //控制winio
        private void changeKeys()
        {
            while(true)
            {
                string msgGot = "";
                msgGot = msgClient.getMsg();
                Console.WriteLine(msgGot);
                //Thread.Sleep(3000);
                if (msgGot.Equals("") == false)
                {
                    for (int i = 0; i < msgGot.Length; ++i)
                    {
                        if (msgGot[i] == '1')
                        {
                            winioKey.KeyDown(keycode[i]);
                            //label1.Text = "keydown";
                        }
                        else
                        {
                            winioKey.KeyUp(keycode[i]);
                        }
                    }
                    
                }
            }
        }

        //開始,設置adb,進行Tcp連接
        private void btnConnect_Click(object sender, EventArgs e)
        {
            //設置adb
            Process adbprocess = new Process();
            adbprocess.StartInfo.FileName = @"adb.exe";
            adbprocess.StartInfo.Arguments = @"forward tcp:60001 tcp:60001";
            adbprocess.Start();
            Thread.Sleep(100);
            //連接server
            if (msgClient.Connect(serverip) == true)//如果連接成功
            {
                winioKey.Initialize();
                Thread.Sleep(100);
                btnStop.Enabled = true;
                btnConnect.Enabled = false;
                winioThread = new Thread(new ThreadStart(changeKeys));
                winioThread.Start();
                label1.Text = "begin";
            }
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            winioThread.Abort();
            msgClient.Close();
            winioKey.Shutdown();
        }
    }
}

  

 

Android端:

Android作為socket服務器與普通的java程序差別不大,用ServerSocket類。只是要在AndroidManifest.xml中添加<uses-permission android:name="android.permission.INTERNET" />

還要將屏幕強制設為橫屏:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

但是在實際編碼過程中遇到一些問題,我在onCreate中初始了ServerSocket對象,而setRequestedOrientation會重新執行onCreate(),這樣就重復初始化了ServerSocket對象,會提示地址已占用的錯誤。所以先將ServerSocket對象設為null,初始化之前先判斷是否為null,再初始化。

手柄的按鍵用多點觸控的技術實現。我寫了一個繼承View的類HandlePanel,在onDraw方法中繪制了8個按鍵。然后在Activity類中設置HandlePanel.setOnTouchListener,進行相關操作。這里又遇到一個問題,我第一次用的是Android2.1的系統,這版系統中不能精確的獲得是那個點出發了相應的事件,如:我在屏幕上按了兩只手指,抬起一只時,無法辨別是哪只手指抬起,只能同時獲得兩點的坐標。在網上也沒發現解決的辦法后來看Android的文檔,發現有個event.getActionIndex()的方法可以滿足需求,但是只在2.2以上的版本有,無奈啊。

與其他Socket編程一樣,ServerSocket對象要close掉,所以我重寫了Activity的onStop()方法。但是每次關閉時,都提示意外退出,所以要調用super.onStop();將原先的操作也執行一遍。小錯誤啊,,不過也要注意一下的。

 

代碼:

Socket服務器類:

package com.mhandle;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;


public class MyServerSocket {
	ServerSocket MyServer;
	Socket mySocket;
	OutputStream os;
	Thread getMsg;
	public MyServerSocket() {
		// TODO Auto-generated constructor stub
		//MyServer = new ServerSocket(60001);
		mySocket = null;
		MyServer = null;
	}
	
	public void connect() throws Exception
	{
		if(MyServer == null)
			MyServer = new ServerSocket(60001);
	}
	
	public void sendMsg(String msg) throws IOException
	{
		if(MyServer != null)
		{
			if(mySocket == null)
				mySocket = MyServer.accept();
			os = mySocket.getOutputStream();
			PrintWriter pWriter = new PrintWriter(os);
			pWriter.write(msg);
			pWriter.flush();
		}
		else
		{
			System.out.println("Out Error");
		}
	}
	
	public void stop() throws IOException
	{
		if(mySocket != null)
			mySocket.close();
		if(MyServer != null)
			MyServer.close();
	}
}

  

 

HandlePanel類:

 

package com.mhandle;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.View;
import android.widget.RelativeLayout;

public class HandlePanel extends View{

	public HandlePanel(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
		//init();
	}

	public HandlePanel(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		//init();
	}
	
	public HandlePanel(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
		//init();
	}
	
	Rect hRects[];
	int rectwidth = 120;
	int VHeight;
	int VWidth;
	
	public void init(int h,int w)
	{
		VHeight = h;
		VWidth = w;
		System.out.println(VHeight+" "+VWidth);
		hRects = new Rect[8];
		
		//方向左上右下,功能鍵的順序也是如此
		int tops[] = {(VHeight-rectwidth)/2,
				(VHeight-rectwidth)/2-(rectwidth+10),
				(VHeight-rectwidth)/2,
				(VHeight-rectwidth)/2+(rectwidth+10),
				(VHeight-rectwidth)/2,
				(VHeight-rectwidth)/2-(rectwidth+10),
				(VHeight-rectwidth)/2,
				(VHeight-rectwidth)/2+(rectwidth+10)
				};
		int lefts[] = { (VWidth/2-3*rectwidth-20)/2,
				(VWidth/2-3*rectwidth-20)/2+rectwidth+10,
				(VWidth/2-3*rectwidth-20)/2+2*rectwidth+20,
				(VWidth/2-3*rectwidth-20)/2+rectwidth+10,
				VWidth/2 + (VWidth/2-3*rectwidth-20)/2,
				VWidth/2 + (VWidth/2-3*rectwidth-20)/2 +rectwidth+10,
				VWidth/2 + (VWidth/2-3*rectwidth-20)/2 + 2*rectwidth+20,
				VWidth/2 + (VWidth/2-3*rectwidth-20)/2 + rectwidth+10,
				};
		//System.out.println("left:" + (VWidth/2-3*rectwidth-20)/2);
		//System.out.println(h+" "+w);
		//for(int i=0;i<8;++i)
		//	lefts[i] += 60;
		
		for(int i=0;i<8;++i)
		{
			hRects[i] = new Rect();
			hRects[i].set(lefts[i],tops[i],lefts[i]+rectwidth, tops[i]+rectwidth);
		}
	}
	
	//判斷是否點擊到按鍵
	public int inRect(int x, int y)
	{
		for(int i=0;i<8;++i)
		{
			if( x<hRects[i].right && x>hRects[i].left 
					&& y>hRects[i].top && y<hRects[i].bottom)
				return i;
		}
		return -1;
	}
	
	@Override
	public void onDraw(Canvas canvas)
	{
		//int width = 50;
		Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		mPaint.setColor(0xFFCBD2D8);
		//繪制按鍵
		for(int i=0;i<8;++i)
		{
			canvas.drawRect(hRects[i], mPaint);
		}		
	}
}

  

 

 

 

教訓:要記錄錯誤的名稱(ClassNotFound之類),輸出catch中內容,讀技術文檔,將網上的demo或代碼測試一下,不能全信。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM