微信小程序跳一跳的遊戲輔助實現

最近微信在其遊戲模塊中添加了一個跳一跳小遊戲,只要在微信首頁下拉即可打開這個小遊戲菜單開始遊戲,因該小遊戲在朋友圈中可以實現排名,故此引來很多小學生再玩這個遊戲,帶來了一波熱潮,那麼小學生除外針對很多高年級的同學估計會在等車等喫飯等人等無聊時間會去玩這個遊戲湊湊熱鬧吧。

那麼今天將對這個遊戲進行一個輔助實現,助你拿高分!其實拿高分做第一是沒意思的,我想如果你是要比某個人高那麼一分這樣會更有意思,那麼這個輔助外掛當遊戲開始時,程序會自動識別小人的座標,你只需點擊要跳到的那一個方塊,程序將自動算出並幫你按下屏幕若干秒,小人即完成一次跳躍。

1.相關技術

實現起來其實相當簡單,主要用到幾個技術點:

1、WindowManager懸浮窗
2、在Android代碼中執行Shell命令實現模擬觸屏,截取屏幕圖片
3、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指令的地方有兩處:

模擬手指在屏幕按下。
截取手機屏幕圖片。
對應的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工具類:

github.com/Trinea/andro

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);


項目鏈接github.com/AchillesLzg/


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章