微信小游戲「跳一跳」外掛(java版)

image

其實(shí)也不能說(shuō)算是外掛吧,算是個(gè)游戲小助手吧,畢竟不能抓包,也不能直接修改分?jǐn)?shù)(據(jù)說(shuō)之前可以直接抓包修改分?jǐn)?shù),不過(guò)這漏洞已經(jīng)被微信官方修復(fù)),今天這個(gè)是 Android 同學(xué)可以非常容易看懂的一篇文章,是從 Android 的角度實(shí)現(xiàn)的,附帶著技術(shù)原理分析和代碼分析。

這個(gè)開(kāi)源庫(kù)已經(jīng)被我同學(xué)分享到 GitHub 上,他自己很無(wú)聊,就寫(xiě)了這個(gè)東西和這篇文章,自己通過(guò)寫(xiě)代碼實(shí)現(xiàn)高分也是玩的不亦樂(lè)乎,這就是程序員和普通玩家的區(qū)別吧。

開(kāi)源庫(kù)地址:https://github.com/xushanmeng/WechatJumpHelper

功能簡(jiǎn)介

用JAVA自動(dòng)控制手機(jī)玩跳一跳

  • 自動(dòng)識(shí)別圖像計(jì)算距離

  • 自動(dòng)幫你點(diǎn)擊屏幕

  • 自動(dòng)緩存圖片,并在圖片上標(biāo)記一些識(shí)別結(jié)果,如下圖:

    image

運(yùn)行環(huán)境

  1. JAVA,最低版本為7.0,官網(wǎng)下載

  2. adb驅(qū)動(dòng),官網(wǎng)下載(需要翻墻),或者到這里下載SDK-tools,其中就包含adb

  3. 安卓手機(jī),目前已適配分辨率

  • 1600x2560

  • 1440x2560

  • 1080x1920

  • 720x1080

使用方法

有JAVA開(kāi)發(fā)工具的同學(xué)可以直接運(yùn)行java代碼,便于代碼調(diào)試,下面主要介紹運(yùn)行已經(jīng)打包好的jar包的方法

  1. 手機(jī)打開(kāi)USB調(diào)試,并連接電腦
  • 打開(kāi)USB調(diào)試方法,進(jìn)入設(shè)置,找到開(kāi)發(fā)者選項(xiàng),打開(kāi)并勾選USB調(diào)試

  • 如果沒(méi)有開(kāi)發(fā)者選項(xiàng),進(jìn)入關(guān)于手機(jī),連續(xù)點(diǎn)擊版本號(hào)7次,即可開(kāi)啟開(kāi)發(fā)者選項(xiàng)

  1. 通過(guò)下面的命令,運(yùn)行Android.jar

    java -jar Android.jar

  2. 根據(jù)手機(jī)分辨率選擇跳躍系數(shù),目前已適配機(jī)型:

    其他分辨率請(qǐng)自己微調(diào)。

  • 1600x2560機(jī)型推薦0.92

  • 1440x2560機(jī)型推薦1.039

  • 1080x1920機(jī)型推薦1.392

  • 720x1080機(jī)型推薦2.078

原理說(shuō)明

  1. 通過(guò)adb命令控制手機(jī)截圖,并取回到本地

    adb shell screencap -p /sdcard/screen.png

    adb pull /sdcard/screen.png .

  2. 圖片分析

  • 有靶點(diǎn),即目標(biāo)物體中心的白色圓點(diǎn),則靶點(diǎn)中心為目標(biāo)落點(diǎn)

  • 無(wú)靶點(diǎn),但是純色平面,或者規(guī)則平面,則平面中心為目標(biāo)落點(diǎn)

  • 無(wú)靶點(diǎn),又無(wú)純色規(guī)則平面,但是左上和右上位置的斜率是固定的,可根據(jù)固定斜率的斜線(xiàn)和目標(biāo)物體中心線(xiàn)的焦點(diǎn)計(jì)算落點(diǎn)

  • 根據(jù)棋子的顏色,取頂部和底部的特征像素點(diǎn),在截圖中進(jìn)行匹配,找到棋子坐標(biāo)

  • 由于目標(biāo)物體不是在左上就是在右上,可以從上往下掃描,根據(jù)色差判斷目標(biāo)物體位置,其中又分為以下幾種類(lèi)型

    image
    image
    image
  • 計(jì)算棋子坐標(biāo)和目標(biāo)落點(diǎn)的距離

  • 距離×跳躍系數(shù)=按壓屏幕的時(shí)間,不同分辨率的手機(jī),跳躍系數(shù)也有所不同

  1. 通過(guò)adb命令,給手機(jī)模擬按壓事件

    adb shell input swipe x y x y time

    其中xy是屏幕坐標(biāo),time是觸摸時(shí)間,單位ms。

工程結(jié)構(gòu)

image

代碼詳解

這里將針對(duì)一些關(guān)鍵算法的代碼進(jìn)行解釋

  1. 尋找棋子位置

    把截圖放大,可以看到棋子頂部像素連成一條橫線(xiàn),那么我們通過(guò)顏色匹配,找到這一條線(xiàn)的始末位置,取中間位置,就得到了棋子的x坐標(biāo)。

    image

    棋子的底部也是一條橫線(xiàn),用顏色匹配,我們檢測(cè)到相似顏色的最大y坐標(biāo),就是棋子底部了,不過(guò)考慮到棋子底部是個(gè)圓盤(pán),我們把棋子的y坐標(biāo)再往上提一些。

    image.gif

    這樣我們就得到了棋子的xy坐標(biāo),下面是相關(guān)代碼:

    /* 計(jì)算棋子位置 */Pixel piece = new Pixel();for (int i = TOP_BORDER; i < screenHeight - BOTTOM_BORDER; i++) {   int startX = 0;   int endX = 0;   for (int j = LEFT_BORDER; j < screenWidth - RIGHT_BORDER; j++) {       int red = Color.red(pixels[i][j].color);       int green = Color.green(pixels[i][j].color);       int blue = Color.blue(pixels[i][j].color);       if (50 < red && red < 55               && 50 < green && green < 55               && 55 < blue && blue < 65) {//棋子頂部顏色           //如果偵測(cè)到棋子相似顏色,記錄下開(kāi)始點(diǎn)           if (startX == 0) {               startX = j;               endX = 0;           }       } else if (endX == 0) {           //記錄下結(jié)束點(diǎn)           endX = j;           if (endX - startX < PIECE_TOP_PIXELS) {               //規(guī)避井蓋的BUG,像素點(diǎn)不夠長(zhǎng),則重新計(jì)算               startX = 0;               endX = 0;           }       }       if (50 < red && red < 60               && 55 < green && green < 65               && 95 < blue && blue < 105) {//棋子底部的顏色           //最后探測(cè)到的顏色就是棋子的底部像素           piece.y = i;       }   }   if (startX != 0 && piece.x == 0) {       piece.x = (startX + endX) / 2;   }}//棋子縱坐標(biāo)從底部邊緣調(diào)整到底部中心piece.y -= PIECE_BOTTOM_CENTER_SHIFT;
    
  2. 尋找靶點(diǎn)

    所謂靶點(diǎn),就是目標(biāo)物體中心的那個(gè)小圓點(diǎn),顏色值為0xf5f5f5

    image

    那么我們只需要尋找顏色值為0xf5f5f5的色塊就可以了,為了規(guī)避其他物體相近顏色干擾,我們可以限制色塊的大小,正確大小的色塊才是靶點(diǎn)。

    但是如何計(jì)算色塊的大小呢,色塊最頂端到最底端y坐標(biāo)的差值我們作為色塊的高度,同理,最左側(cè)到最右側(cè)x坐標(biāo)的差值作為寬度,我們只需要查找這四個(gè)頂點(diǎn)的坐標(biāo)就可以了。

    本來(lái)打算用凸包的Graham掃描算法,后來(lái)發(fā)現(xiàn)色塊已經(jīng)是凸包了,且邊緣像素是連續(xù)的,那么我們按照一定順序,遍歷邊緣像素,就可以在O(n^-2)的時(shí)間復(fù)雜度里,得到色塊的頂點(diǎn)坐標(biāo)了。

    我們從第一個(gè)像素點(diǎn)開(kāi)始,尋找的順序如圖所示:

    image
    /**    * 尋找色塊頂點(diǎn)像素    */   public static final Pixel[] findVertexs(Pixel[][] pixels, Pixel firstPixcel) {       Pixel[] vertexs = new Pixel[4];       Pixel topPixel = firstPixcel;       Pixel leftPixel = firstPixcel;       Pixel rightPixel = firstPixcel;       Pixel bottomPixel = firstPixcel;       Pixel currentPixcel = firstPixcel;       //先把坐標(biāo)置于左上角       while (checkBorder(pixels, currentPixcel)//判斷是否超出圖像邊緣               && Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) {//判斷是否是相同顏色            currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x];       }       while (checkBorder(pixels, currentPixcel)               && Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) {            currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1];       }       //尋找上頂點(diǎn)像素       while (checkBorder(pixels, currentPixcel)) {           if (Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) {                currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x];           } else if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x + 1], firstPixcel)) {                currentPixcel = pixels[currentPixcel.y][currentPixcel.x + 1];           } else {               topPixel = findCenterPixcelHorizontal(pixels, currentPixcel);               break;           }       }       //尋找右頂點(diǎn)像素       while (checkBorder(pixels, currentPixcel)) {           if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x + 1], firstPixcel)) {                currentPixcel = pixels[currentPixcel.y][currentPixcel.x + 1];           } else if (Color.compareColor(pixels[currentPixcel.y + 1][currentPixcel.x], firstPixcel)) {                currentPixcel = pixels[currentPixcel.y + 1][currentPixcel.x];           } else {               rightPixel = findCenterPixcelVertial(pixels, currentPixcel);               break;           }       }       //尋找下頂點(diǎn)像素       while (checkBorder(pixels, currentPixcel)) {           if (Color.compareColor(pixels[currentPixcel.y + 1][currentPixcel.x], firstPixcel)) {                currentPixcel = pixels[currentPixcel.y + 1][currentPixcel.x];           } else if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) {                currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1];           } else {               bottomPixel = findCenterPixcelHorizontal(pixels, currentPixcel);               break;           }       }       //尋找左頂點(diǎn)像素       while (checkBorder(pixels, currentPixcel)) {           if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) {                currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1];           } else if (Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) {                currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x];           } else {               leftPixel = findCenterPixcelVertial(pixels, currentPixcel);               break;           }       }       vertexs[0] = leftPixel;       vertexs[1] = topPixel;       vertexs[2] = rightPixel;       vertexs[3] = bottomPixel;       return vertexs;   }
    

    得到了四個(gè)坐標(biāo)點(diǎn),我們就可以計(jì)算色塊的中點(diǎn)了,也就是目標(biāo)落點(diǎn)。

    對(duì)于沒(méi)有靶點(diǎn),但是落點(diǎn)是規(guī)則平面的,也可以用類(lèi)似算法。

  3. 斜率計(jì)算對(duì)于沒(méi)有靶點(diǎn),又不是規(guī)則平面的,我們?cè)趺从?jì)算落點(diǎn)呢,這時(shí)候就要用到斜率了。

    可以看得出來(lái),每次左上角或右上角出現(xiàn)的物體,針對(duì)當(dāng)前物體的方向都是一樣的,也就是兩個(gè)物體中心的連線(xiàn),斜率是固定的。

    基本所有的目標(biāo)物體,最頂點(diǎn)像素中點(diǎn)的x坐標(biāo),都是在物體中間,我們至少先得到了目標(biāo)物體x坐標(biāo)了,記為des.x ,接下來(lái)要求des.y 。

    image.gif

    如上圖所示,計(jì)算過(guò)程如下:

    斜線(xiàn)的公式為 y=kx+b

    那么,在棋子坐標(biāo)上有 piece.y=k*piece.x+b

    在目標(biāo)落點(diǎn)坐標(biāo)上有 des.y=kdes.x+b

    代入得到 des.y=k*(des.x-piece.x)+piece.y

    然而這種算法還是有偏差的。

    image.gif

    可以看到,同樣的斜率,如果棋子的位置有偏差,計(jì)算出來(lái)最終落點(diǎn)還是會(huì)有偏差的。

    代碼解析就先講這么多,希望有大神可以提出更好的解決方案。

玩游戲小竅門(mén)

  1. 連續(xù)的落到物體中心位置,是有分?jǐn)?shù)加成的,最多跳一次可以得幾十分

  2. 井蓋、商店、唱片、魔方,多停留一會(huì),有音樂(lè)響起后也是有分?jǐn)?shù)加成的

那么看一下程序員的朋友圈有多殘酷吧

image

原文作者:Samon
閱讀原文

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容