小程序模板網(wǎng)

微信小程序跳一跳的游戲輔助實(shí)現(xiàn)

發(fā)布時(shí)間:2018-05-02 15:49 所屬欄目:小程序開發(fā)教程

0.前言

微信小程序跳一跳是個(gè)挺不錯(cuò)的游戲,但身為一個(gè)天生愛折騰的geek,還是忍不住挑戰(zhàn)這游戲的上限。

效果如下動(dòng)圖,游戲開始,程序會(huì)自動(dòng)識(shí)別小人的坐標(biāo),你只需點(diǎn)擊要跳到的那一個(gè)方塊,程序?qū)⒆詣?dòng)算出并幫你按下屏幕若干秒,小人即完成一次跳躍。

效果圖

1.相關(guān)技術(shù)

實(shí)現(xiàn)起來(lái)其實(shí)相當(dāng)簡(jiǎn)單,主要用到幾個(gè)技術(shù)點(diǎn):

懸浮窗
在Android代碼中執(zhí)行Shell命令實(shí)現(xiàn)模擬觸屏,截取屏幕圖片
opencv進(jìn)行圖片定位識(shí)別
注意:Android程序要執(zhí)行shell命令,得有root權(quán)限,所以要運(yùn)行這個(gè)程序,你需要有個(gè)已經(jīng)root的手機(jī)。

2.實(shí)現(xiàn)思路

2.1 如何知道要按多久屏幕

很顯而易見地:小人與目標(biāo)方塊離得越遠(yuǎn),需要按下屏幕的時(shí)間就越長(zhǎng),兩者成正相關(guān)。我們可以有個(gè)大膽的假設(shè):兩者能否用簡(jiǎn)單的線性關(guān)系去擬合,那么就有以下的公式:

按下時(shí)間 = 距離 * 常量系數(shù)

這個(gè)常數(shù)怎么確定呢?其實(shí)就是猜,多調(diào)試幾次,就能拿到比較準(zhǔn)確的數(shù)字。

如果距離過(guò)近或過(guò)遠(yuǎn),落點(diǎn)產(chǎn)生誤差,我們可以根據(jù)不同距離范圍動(dòng)態(tài)調(diào)整系數(shù)。

2.2 小人與目標(biāo)方塊坐標(biāo)與距離的獲取###

要算距離,首先要得到坐標(biāo),筆者想到了幾種方式:

點(diǎn)擊小人底部,然后點(diǎn)擊目標(biāo)方塊頂部,兩次點(diǎn)擊事件回調(diào),就能得到兩個(gè)坐標(biāo)。
用圖像處理得到小人的坐標(biāo),目標(biāo)方塊坐標(biāo)由點(diǎn)擊屏幕產(chǎn)生。
小人與目標(biāo)方塊坐標(biāo)都用圖像識(shí)別得到。
可見第三種最理想,甚至能讓程序自己在玩游戲,但目前本程序采用了第二種方式。

距離公式.png
得到坐標(biāo)后,根據(jù)兩點(diǎn)間距離公式,算出小人與目標(biāo)方塊的距離。

2.3 懸浮窗

有上一小節(jié)可知,目標(biāo)方塊的坐標(biāo)需要我們點(diǎn)擊屏幕產(chǎn)生,此時(shí)就有個(gè)問題:我們要獲取目標(biāo)方塊坐標(biāo),但不能直接點(diǎn)在小程序上,否則會(huì)觸發(fā)小人跳動(dòng)。因此,我們可以創(chuàng)建一個(gè)透明的懸浮窗來(lái)解決這個(gè)問題。

使用懸浮窗,捕抓目標(biāo)方塊坐標(biāo)
當(dāng)懸浮窗覆蓋在小程序上方,點(diǎn)擊小程序上的目標(biāo)方塊,實(shí)際上是點(diǎn)擊透明的懸浮窗,因此對(duì)應(yīng)位置的坐標(biāo)就能被我們捕獲,并不會(huì)觸發(fā)小程序。

2.4 openCV的使用

判斷小人在屏幕的位置,實(shí)質(zhì)上是一種“查找B圖中在A圖中的位置”的需求,其中A圖就是手機(jī)屏幕截圖。這需求我們可以使用openCV的Imgproc.matchTemplate方法完成。

在游戲開始時(shí),執(zhí)行shell指令截取屏幕圖像,然后用Imgproc.matchTemplate方法查找截圖中小人的位置,記錄作為起跳坐標(biāo)。

等一輪跳躍結(jié)束后,再次執(zhí)行shell命令截取屏幕圖像,分析小人跳躍后的位置,做好下一次跳躍的準(zhǔn)備。

match.png
2.5 在程序中執(zhí)行shell指令

本程序使用到shell指令的地方有兩處:

模擬手指在屏幕按下。
截取手機(jī)屏幕圖片。
對(duì)應(yīng)的adb指令如下:


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

要注意的是,在執(zhí)行swipe指令前,需要將懸浮窗remove掉,否則swipe指令會(huì)作用在懸浮窗上,而非小程序。

最后推薦一個(gè)好用的Shell工具類:

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

3.部分關(guān)鍵代碼

3.1 懸浮窗

懸浮窗的實(shí)現(xiàn)很簡(jiǎn)單,網(wǎng)上也有很多參考資料。


//設(shè)置懸浮窗參數(shù)并顯示
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添加了一個(gè)繼承于LinearLayout的控件,實(shí)現(xiàn)該控件主要是便于重寫onDraw方法,繪制小人位置區(qū)域,關(guān)鍵代碼如下。


@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識(shí)別小人坐標(biāo)

openCV識(shí)別小人的關(guān)鍵代碼如下:


private void try2MatchLittleBoy() {
    Mat source = new Mat();   //Mat相當(dāng)于Android的Bitmap
    Mat template = new Mat();
    //由于筆者開了root與文件讀寫權(quán)限,若在Android M或更高級(jí)的系統(tǒng)上,可能需要按照官方的文件讀寫實(shí)現(xiàn),否則返回的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);
    //創(chuàng)建于原圖相同的大小,儲(chǔ)存匹配度
    Mat result = Mat.zeros(source.rows() - template.rows() + 1, source.cols() - template.cols() + 1, CvType.CV_32FC1);
    //調(diào)用模板匹配方法
    Imgproc.matchTemplate(source, template, result, Imgproc.TM_SQDIFF_NORMED);
    //規(guī)格化
    Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1);
    //獲得最可能點(diǎn),MinMaxLocResult是其數(shù)據(jù)格式,包括了最大、最小點(diǎn)的位置x、y
    Core.MinMaxLocResult mlr = Core.minMaxLoc(result);
    org.opencv.core.Point matchLoc = mlr.minLoc;
    //通知成功匹配的坐標(biāo)
    notifyDrawLittleBoyRect(matchLoc, template);
}

3.3 算出按下屏幕時(shí)間

得到兩點(diǎn)距離后,根據(jù)不同的距離范圍有不同系數(shù),算出需要按下屏幕時(shí)間。


//兩點(diǎn)之間的距離
double distance = Math.sqrt(Math.pow(firstPoint.x - secondPoint.x, 2) + Math.pow(firstPoint.y - secondPoint.y, 2));
//根據(jù)兩點(diǎn)距離判斷起跳系數(shù)
float ratio = distance > 600 ? JumpUtils.JUMP_SPEED_SLOW : distance < 300 ? JumpUtils.JUMP_SPEED_FAST : JumpUtils.JUMP_SPEED;
//生成按下屏幕的時(shí)間
final double holdTime = distance * ratio;

3.4 執(zhí)行Shell 指令

模擬按下屏幕:


//執(zhí)行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();
//延時(shí)800ms,確保截圖完成后,進(jìn)行圖片匹配
mHandler.sendEmptyMessageDelayed(MSG_SCREENSHOT_COMPLETE, 800);


易優(yōu)小程序(企業(yè)版)+靈活api+前后代碼開源 碼云倉(cāng)庫(kù):starfork
本文地址:http://m.u-renovate.com/wxmini/doc/course/24178.html 復(fù)制鏈接 如需定制請(qǐng)聯(lián)系易優(yōu)客服咨詢:800182392 點(diǎn)擊咨詢
QQ在線咨詢
AI智能客服 ×