美文网首页Android OtherAndroid游戏Android开发经验谈
微信小程序跳一跳的游戏辅助实现

微信小程序跳一跳的游戏辅助实现

作者: AchillesL | 来源:发表于2017-12-31 16:39 被阅读1060次

    原创作者:AchillesL
    若转载文章,请在明显的位置标明文章出处

    0.前言

    微信小程序跳一跳是个挺不错的游戏,但身为一个天生爱折腾的geek,还是忍不住挑战这游戏的上限。

    效果如下动图,游戏开始,程序会自动识别小人的坐标,你只需点击要跳到的那一个方块,程序将自动算出并帮你按下屏幕若干秒,小人即完成一次跳跃。

    效果图

    1.相关技术

    实现起来其实相当简单,主要用到几个技术点:

    • 悬浮窗
    • 在Android代码中执行Shell命令实现模拟触屏,截取屏幕图片
    • opencv进行图片定位识别

    注意:Android程序要执行shell命令,得有root权限,所以要运行这个程序,你需要有个已经root的手机。

    2.实现思路

    2.1 如何知道要按多久屏幕

    很显而易见地:小人与目标方块离得越远,需要按下屏幕的时间就越长,两者成正相关。我们可以有个大胆的假设:两者能否用简单的线性关系去拟合,那么就有以下的公式:

    按下时间 = 距离 * 常量系数

    这个常数怎么确定呢?其实就是猜,多调试几次,就能拿到比较准确的数字。

    如果距离过近或过远,落点产生误差,我们可以根据不同距离范围动态调整系数。

    2.2 小人与目标方块坐标与距离的获取###

    要算距离,首先要得到坐标,笔者想到了几种方式:

    1. 点击小人底部,然后点击目标方块顶部,两次点击事件回调,就能得到两个坐标。
    2. 用图像处理得到小人的坐标,目标方块坐标由点击屏幕产生。
    3. 小人与目标方块坐标都用图像识别得到。

    可见第三种最理想,甚至能让程序自己在玩游戏,但目前本程序采用了第二种方式。

    距离公式.png

    得到坐标后,根据两点间距离公式,算出小人与目标方块的距离。

    2.3 悬浮窗

    有上一小节可知,目标方块的坐标需要我们点击屏幕产生,此时就有个问题:我们要获取目标方块坐标,但不能直接点在小程序上,否则会触发小人跳动。因此,我们可以创建一个透明的悬浮窗来解决这个问题。

    使用悬浮窗,捕抓目标方块坐标

    当悬浮窗覆盖在小程序上方,点击小程序上的目标方块,实际上是点击透明的悬浮窗,因此对应位置的坐标就能被我们捕获,并不会触发小程序。

    2.4 openCV的使用

    判断小人在屏幕的位置,实质上是一种“查找B图中在A图中的位置”的需求,其中A图就是手机屏幕截图。这需求我们可以使用openCV的Imgproc.matchTemplate方法完成。

    在游戏开始时,执行shell指令截取屏幕图像,然后用Imgproc.matchTemplate方法查找截图中小人的位置,记录作为起跳坐标。

    等一轮跳跃结束后,再次执行shell命令截取屏幕图像,分析小人跳跃后的位置,做好下一次跳跃的准备。

    match.png

    2.5 在程序中执行shell指令

    本程序使用到shell指令的地方有两处:

    1. 模拟手指在屏幕按下。
    2. 截取手机屏幕图片。

    对应的adb指令如下:

    adb shell input touchscreen swipe 1000 1000 1200 1200 time
    adb shell /system/bin/screencap -p /storage/emulated/0/JumpX/screenshot.png

    要注意的是,在执行swipe指令前,需要将悬浮窗remove掉,否则swipe指令会作用在悬浮窗上,而非小程序。

    最后推荐一个好用的Shell工具类:

    https://github.com/Trinea/android-common/blob/master/src/cn/trinea/android/common/util/ShellUtils.java

    3.部分关键代码

    3.1 悬浮窗

    悬浮窗的实现很简单,网上也有很多参考资料。

    //设置悬浮窗参数并显示
    mParams = new WindowManager.LayoutParams();
    mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);
    
    mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
    mParams.format = PixelFormat.RGBA_8888;
    mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
    
    mParams.gravity = Gravity.LEFT | Gravity.TOP;
    mParams.x = 0;
    mParams.y = 0;
    
    mParams.width = JumpUtils.SMALL_SIZE_WIDTH;
    mParams.height = JumpUtils.SMALL_SIZE_HIGH;
    
    mLinearLayout = (MyLinearLayout) LayoutInflater.from(getApplication()).inflate(R.layout.layout, null);
    mButton = mLinearLayout.findViewById(R.id.btn);
    mWindowManager.addView(mLinearLayout, mParams);
    

    WindowManager添加了一个继承于LinearLayout的控件,实现该控件主要是便于重写onDraw方法,绘制小人位置区域,关键代码如下。

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    
        //绘制小人位置方框
        if (mIsNeed2DrawLittleBoyRect && point1 != null && point2 != null) {
            Paint paint = new Paint();
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(6f);
            paint.setAntiAlias(true);
            RectF rectF = new RectF(point1.x, point1.y, point2.x, point2.y);
            canvas.drawRect(rectF, paint);
        }
    
        //清除上一次的绘制
        if (!mIsNeed2DrawLittleBoyRect  && point1 != null && point2 != null ) {
            Paint paint = new Paint();
            paint.setColor(Color.parseColor("#00000000"));
            paint.setStyle(Paint.Style.FILL);
            RectF rectF = new RectF(point1.x, point1.y, point2.x, point2.y);
            canvas.drawRect(rectF, paint);
        }
    }
    

    3.2 openCV识别小人坐标

    openCV识别小人的关键代码如下:

    private void try2MatchLittleBoy() {
        Mat source = new Mat();   //Mat相当于Android的Bitmap
        Mat template = new Mat();
    
        //由于笔者开了root与文件读写权限,若在Android M或更高级的系统上,可能需要按照官方的文件读写实现,否则返回的bitmapSource可能为null
        Bitmap bitmapSource = BitmapFactory.decodeFile(JumpUtils.SCREENSHOT_FILE_NAME);
        Bitmap bitmapTemplate = BitmapFactory.decodeFile(JumpUtils.LITTLE_BOY_FILE_NAME);
    
        Utils.bitmapToMat(bitmapSource, source);
        Utils.bitmapToMat(bitmapTemplate, template);
    
        //创建于原图相同的大小,储存匹配度
        Mat result = Mat.zeros(source.rows() - template.rows() + 1, source.cols() - template.cols() + 1, CvType.CV_32FC1);
        //调用模板匹配方法
        Imgproc.matchTemplate(source, template, result, Imgproc.TM_SQDIFF_NORMED);
        //规格化
        Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1);
        //获得最可能点,MinMaxLocResult是其数据格式,包括了最大、最小点的位置x、y
        Core.MinMaxLocResult mlr = Core.minMaxLoc(result);
        org.opencv.core.Point matchLoc = mlr.minLoc;
    
        //通知成功匹配的坐标
        notifyDrawLittleBoyRect(matchLoc, template);
    }
    

    3.3 算出按下屏幕时间

    得到两点距离后,根据不同的距离范围有不同系数,算出需要按下屏幕时间。

    //两点之间的距离
    double distance = Math.sqrt(Math.pow(firstPoint.x - secondPoint.x, 2) + Math.pow(firstPoint.y - secondPoint.y, 2));
    //根据两点距离判断起跳系数
    float ratio = distance > 600 ? JumpUtils.JUMP_SPEED_SLOW : distance < 300 ? JumpUtils.JUMP_SPEED_FAST : JumpUtils.JUMP_SPEED;
    //生成按下屏幕的时间
    final double holdTime = distance * ratio;
    

    3.4 执行Shell 指令

    模拟按下屏幕:

    //执行swipe命令
    new Thread(new Runnable() {
        @Override
        public void run() {
            String command[] = new String[]{"sh", "-c",
                    "input touchscreen swipe 1000 1000 1000 1000 " + (int)holdTime};
            ShellUtils.CommandResult commandResult = ShellUtils.execCommand(command, true, true);
            Log.d("Achilles:", commandResult.errorMsg);
        }
    }).start();
    

    截取屏幕图片:

    new Thread(new Runnable() {
        @Override
        public void run() {
            String command[] = new String[]{"sh", "-p",
                        "/system/bin/screencap " + JumpUtils.SCREENSHOT_FILE_NAME};
                ShellUtils.CommandResult commandResult = ShellUtils.execCommand(command, true, true);
            Log.d("Achilles:", commandResult.errorMsg);
        }
    }).start();
    //延时800ms,确保截图完成后,进行图片匹配
    mHandler.sendEmptyMessageDelayed(MSG_SCREENSHOT_COMPLETE, 800);
    

    4. 项目链接

    https://github.com/AchillesLzg/jianshu-jumpx

    相关文章

      网友评论

        本文标题:微信小程序跳一跳的游戏辅助实现

        本文链接:https://www.haomeiwen.com/subject/yczogxtx.html