• 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);
            }      
        }
    }
  • 相关阅读:
    10年测试专家深度解读接口测试
    测试技术大牛谈成长经历:一个好的软件测试工程师应该做到这些!
    一位测试老鸟的工作经验分享
    又一名程序员倒下,网友:我们只是新时代农民工
    软件测试工程师这样面试,拿到offer的几率是80%
    App测试流程及测试点(个人整理版)
    自动化测试是什么?
    软件测试工程师的职业技能分析
    月薪15k的测试员需要学习什么技术?
    面向对象
  • 原文地址:https://www.cnblogs.com/canphp/p/2833265.html
Copyright © 2020-2023  润新知