Android上實現微信跳一跳外掛

citrus-close-up-drink-1320998.jpg
去年跳一跳很火,外掛也很火,當時自己也做了一個,跳個20來萬分沒問題。但是後來因為遊戲一直更新,自己沒時間去更新軟體,而且之前的程式也沒有寫自動檢測遊戲結束和儲存失敗的情形的功能,所以現在基本上沒法用了;最近遊戲涼了,基本上不會再更新了,自己也是無聊,再徹底重新做一次;目前能跳30多萬分吧
定個目標
要做的是一個扔在一邊就可以自己跳的軟體,只需一個測試手機,不需要任何其他的輔助工具就可以跳出高分,定個小目標就 100 萬分吧
實現步驟
整個流程應該是先計算 棋子 (黑色的那個跳來跳去的就叫它棋子吧)和 方塊 (下一個棋子落點的地方就叫它方塊吧) 中心點 之間的 距離 ,然後通過一個 公式 轉換成需要點選的 時間 ,然後 自動點選 螢幕進行跳躍

411F431097BBBD36973120B02903A57C.jpg
可行性分析
距離計算
移動懸浮窗
最簡單的距離計算可以通過兩個懸浮窗來實現,拖動懸浮窗到指定位置就可以計算出距離;這是可行的,但是效率低,需要手動操作,分數無上限,但是人累
//在service裡面開啟懸浮框程式碼 btnView = new ImageView(getApplicationContext()); btnView.setImageResource(R.drawable.ic_star); windowManager = (WindowManager) getApplicationContext() .getSystemService(Context.WINDOW_SERVICE); params = new WindowManager.LayoutParams(); // 設定Window Type params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; // 設定懸浮框不可觸控 params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | FLAG_LAYOUT_INSET_DECOR; // 懸浮窗不可觸控,不接受任何事件,同時不影響後面的事件響應 params.format = PixelFormat.RGBA_8888; // 設定懸浮框的寬高 params.width = 200; params.height = 200; params.gravity = Gravity.TOP; params.x = 300; params.y = 200; windowManager.addView(btnView, params);
影象識別
影象識別就需要先擷取影象再利影象識別工具,android上最簡單的使用openCV來做,簡單點可以通過openCV的 模板匹配 來實現;這個方法需要很多模板,而且精度不高,識別時間較長,幾秒鐘吧,只是簡單寫寫的話分數上限比較低(幾百)
//模板匹配實現程式碼 Mat template = Highgui.imread(templateFilePath1, Highgui.CV_LOAD_IMAGE_COLOR); Mat source = Highgui.imread(originalFilePath, Highgui.CV_LOAD_IMAGE_COLOR); //創建於原圖相同的大小,儲存匹配度 Mat result = Mat.zeros(source.rows() - template.rows() + 1, source.cols() - template.cols() + 1, CvType.CV_8UC1); //呼叫模板匹配方法 Imgproc.matchTemplate(source, template, result, Imgproc.TM_CCOEFF_NORMED); //獲得最可能點,MinMaxLocResult是其資料格式,包括了最大、最小點的位置x、y Core.MinMaxLocResult mlr = Core.minMaxLoc(result); System.out.println("相似度:" + mlr.maxVal); Point matchLoc = mlr.maxLoc; //在原圖上的對應模板可能位置畫一個綠色矩形 Core.rectangle(source, matchLoc, new Point(matchLoc.x + template.width(), matchLoc.y + template.height()), new Scalar(0, 255, 0));
顏色識別
顏色識別就是利用遊戲色彩簡單的特點,只需要識別顏色並找到規律就可以實現計算兩點之間的距離;需要考慮的情況比較多,精度、時間都取決於寫的演算法;分數上限取決於演算法和手機可執行時間
int color = bitmap.getPixel(x, y)
實現自動點選
自動點選,實現方法基本上就兩種,第一是執行 adb命令 ,可以連線 電腦傳送 adb命令實現,或者獲取手機 root許可權 ,就可以本機實現了;一種是使用 Accessbility 輔助功能,但是其實它做不到觸控一段時間這個功能;
/** * @param cmd需要執行的adb命令 */ public static void execShellCmd(String cmd) { try { if (process == null) { process = Runtime.getRuntime().exec("su"); os = new DataOutputStream(process.getOutputStream()); } os.writeBytes(cmd + "\n"); os.flush(); } catch (IOException e) { e.printStackTrace(); } }
獲取轉換公式
轉換公式可以先通過網上的粗略的公式來多次測試、調整、計算,然後得到一個比較精確的公式
double k = (distence * (-0.00020) + 1.495); if (k > 1.4165) { k = 1.4165; } time = (int) (k * distence);
具體實現
實現顏色識別:heavy_plus_sign:獲取root許可權執行adb命令的方法
開啟懸浮窗
第一步是在 Service 中開啟懸浮窗,因為我們需要先開啟懸浮窗才可以到遊戲執行介面開啟程式,需要先獲取許可權,獲取懸浮窗許可權還比較麻煩
快速整合動態許可權申請: ofollow,noindex">Android動態許可權申請工具(包括懸浮窗)
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> //獲取懸浮窗許可權 FloatWindowManager.getInstance().applyOrShowFloatWindow(MainActivity.this)
擷取螢幕
第一步是擷取螢幕,有了root許可權截圖自然很簡單,但是我執行的時候發現root命令的執行好像比較慢,而且先將截圖儲存到本地再讀取出來比較慢一點,android5.0以後是自帶截圖功能(全域性)的,具體實現其實也不簡單,需要開啟錄屏服務,然後去獲取圖片,類似開啟相機預覽,取出圖片的感覺吧;
快速整合截圖:Android截圖、錄屏工具
//開啟錄屏服務 ScreenRecordUtil.getInstance().screenShot(MainActivity.this, null); //隨時獲取截圖 Bitmap bitmap = ScreenRecordUtil.getInstance().getScreenShot();
顏色識別
顏色識別,其實就是看看兩種顏色是不是一樣或者相似,用LAB顏色空間演算法,可以拿到兩種顏色的差別有多大;
/** * LAB顏色空間計算色差,基於人眼對顏色的感知, * 可以表示人眼所能感受到的所有顏色。 * L表示明度,A表示紅綠色差,B表示藍黃色差 */ public static int labAberration(int color1, int color2) { int r1 = Color.red(color1); // 取高兩位 int g1 = Color.green(color1);// 取中兩位 int b1 = Color.blue(color1);// 取低兩位 int r2 = Color.red(color2); // 取高兩位 int g2 = Color.green(color2);// 取中兩位 int b2 = Color.blue(color2);// 取低兩位 int rmean = (r1 + r2) / 2; int r = r1 - r2; int g = g1 - g2; int b = b1 - b2; return (int) Math.sqrt((2 + rmean / 256) * (Math.pow(r, 2)) + 4 * (Math.pow(g, 2)) + (2 + (255 - rmean) / 256) * (Math.pow(b, 2))); }
找到背景顏色
背景顏色是最重要的,因為大部分都是背景顏色,只要當前獲取到的顏色和背景顏色不一致就可以知道應該是是進入方塊了;可以從第一個點獲取到背景顏色;可以注意到背景顏色基本上是一樣的,稍微有一點點漸變,為了準確可以在每次獲取顏色的時候更新背景顏色,使背景顏色更接近當前位置的背景;
找棋子位置
棋子位置是位於螢幕的下方的,所以為了節省時間只擷取 Bitmap 的相應比例部分進行識別;棋子是永遠不變的,從上面取幾個特徵顏色點;從下邊遍歷畫素點的顏色,和對比特徵顏色就可以找到棋子的起跳點位置

74E79A2856911FD47073BA62AD2560B2.jpg
清除棋子影象
棋子的長寬是不變的,找到棋子的位置後,我們將原bitmap上棋子所在的位置用背景顏色覆蓋,這樣可以避免之後識別的顏色干擾
獲取方塊的中心
假設第二張圖片就是我們獲取到的圖片,我們要找到方塊的中心就應該找到對角線,中心其實就是最左邊的角和最右邊的角的連線的中點;找到第一個頂點也很重要,這樣可以知道方塊的顏色,便於找其他點;而且當左右兩個點有一個不準的時候,可以用頂點的X座標和準的點的Y座標來使用也可以;當兩個點都不準的時候,直接使用頂點的X座標和把Y座標下移一點來使用也可以挽救一下

F516BE5284C7E3A2C27B5BDF12E03A04.png
找頂點
找頂點很簡單,我們知道背景顏色,從上遍歷下來只要顏色和背景顏色不一樣就可以判定這是頂點了;
找第一個的點的時候可能會被一些元素影響,比如音符,這個比較好辦,判斷進入的這個顏色的長寬就可以排除,方塊都是比較大的,影響元素都很小
方塊是不是純色
方塊是不是純色可以在找到頂點後取一個小範圍來判定是不是純色
找左右兩邊的點
這個有很多方法,第一種是繼續遍歷下去,每次進入方塊點的X座標會減小,每次出方塊點的X座標會增加,到最小和最大就是要找的兩邊的點;這種方法可行,但是中間會出現很多特殊情況;
這次選擇另一種方法,方塊左右兩邊和水平線的角度是固定的,斜率 double k = 0.5773
,利用角度和進入座標可以生成一個直線的方程,按照這個方程的點往下找,當獲取到和背景顏色一樣或者和方塊的顏色不一樣的點就認為是左右兩邊要找的點了;
方塊不是純色
方塊的顏色不是純色的,不是純色,第一個點還是能找準,找左右點的時候以顏色和背景顏色不一樣為出去的點就好了,這樣當左右兩邊(一般只會有一邊)有方塊的顏色連在一起的時候就會有一邊的位置找不準,這時候根據兩個點和頂點連線的距離判定,太遠或者太近都捨棄;

0930E2C22F50CED040C345470C21FD52.jpg
特殊的純色
當方塊中只有一點線段或者中間是其他顏色的話,取一定的範圍判斷顏色是判斷不出來不是純色的,這時候可以採取嘗試跳過不一樣的顏色再次回到方塊的顏色繼續計算;

D58FC55A105E31AF2595E4D0185E3895.jpg
一些需要注意的地方
首先是方塊形狀,有的是圓形的,形狀其實不影響上面的做法;

1CEF53BB6002BF95FD2D47333937FA8D.jpg
其實一般都會跳到中心,這時候中心是會出現一個白點的,在我們找到中心點的時候,如果點的顏色不是白色可以找找周圍有沒有白色,有的話再找到白點的中心,作為方塊的中心

507F3D161DA9EF9F881400DCBF167887.jpg
遍歷顏色沒有必要一個一個畫素遍歷,可以先快速遍歷,找到不同點以後再回過頭精確的遍歷
其實識別的時間很短,200ms以內,用 adb 執行點選需要的時間很長,可能就一秒左右吧,很慢了,然後跳一次後會產生一些波紋,需要等到波紋消失後才可以截圖,這些都是需要等待的地方;
自我完善
在此基礎上其實還會遇到很多特殊的情況,難復現,而且也不可能一直盯著手機;所以自動開始、儲存失敗時的截圖、崩潰日誌都是很重要的東西;
快速整合異常捕獲: Android捕獲Crash資訊簡單封裝
CrashHander.getInstance().init(this, new ISaveErro() { @Override public void saveErroMsg(Throwable throwable) { } });
現在的版本大概每個小時可以跳2.4萬,上次優化以後,只跳了一次就跳到24萬分,但是崩潰只儲存了日誌和截圖,沒保留之崩潰前的幾張有用的截圖,所以原因沒找到,這樣優化下去感覺手機不卡可以跳到地老天荒;

G_0211045758.gif