C#實現(xiàn)微信跳一跳小游戲的自動跳躍助手開發(fā)實戰(zhàn)
一、前言:
前段時間微信更新了新版本后,帶來的一款H5小游戲“跳一跳”在各朋友圈里又火了起來,類似以前的“打飛機(jī)”游戲,這游戲玩法簡單,但加上了積分排名功能后,卻成了“裝逼”的地方,于是很多人花錢花時間的刷積分搶排名。后來越來越多的聰明的“程序哥們”弄出了不同方式不同花樣的跳一跳助手(外掛?),有用JS實現(xiàn)的、有JAVA實現(xiàn)的、有Python實現(xiàn)的,有直接物理模式的、有機(jī)械化的、有量尺子的等等,簡直是百花齊放啊……
趕一下潮流,剛好有點(diǎn)時間,于是花了一個下午時間,我也弄了一個C#版本的簡單實現(xiàn)。
二、實現(xiàn):
簡單的實現(xiàn)流程: 連接手機(jī) -> 獲取跳一跳游戲界面 -> 獲取位置(棋子位置和要跳躍的落腳點(diǎn)位置) -> 點(diǎn)擊棋子跳躍
1、連接手機(jī)
電腦要連接并操作安卓手機(jī),一般是通過ADB協(xié)議連接手機(jī)并進(jìn)行操作。連接手機(jī)前要求手機(jī)已開啟USB調(diào)試模式,可通過USB線或者TCP方式連接手機(jī)。正常只要電腦安裝了adb sdk tools之類的工具包,就會自帶有adb命令,所以C#要能操作手機(jī),簡單實現(xiàn)就是直接利用現(xiàn)成的adb命令。
手機(jī)通過USB線接入電腦后,在CMD窗口輸入以下adb devices命令,如果顯示有device列表則表示手機(jī)已連接成功可以對手機(jī)進(jìn)行操作了。
C:\Users\k>adb devices List of devices attached e832acb device
2、獲取游戲界面
獲取手機(jī)界面的截圖可通過以下adb命令獲取:
adb shell screencap -p [filename]
參數(shù) :
- p 表示截圖保存格式為PNG圖像格式。
filename: 截圖保存的路徑地址(手機(jī)路徑),如果不輸入則將截圖數(shù)據(jù)直接輸出到當(dāng)前控制臺會話,否則會將截圖保存到相關(guān)路徑地址(必須有寫權(quán)限)
為避免文件保存到手機(jī)后還要再執(zhí)行adb pull(拉文件到本地電腦)的操作,所以選擇不帶filename參數(shù)的命令。在C#代碼里通過Process這個類進(jìn)行adb命令的調(diào)用執(zhí)行,實現(xiàn)代碼如下:
var startInfo = new ProcessStartInfo("adb", "shell screencap -p"); startInfo.CreateNoWindow = true; startInfo.ErrorDialog = true; startInfo.RedirectStandardOutput = true; startInfo.UseShellExecute = false; var process = Process.Start(startInfo); process.Start(); var memoStream = new MemoryStream(); process.StandardOutput.BaseStream.CopyTo(memoStream);
但由于adb client的原因,在它輸出的截圖數(shù)據(jù)流中會對'\n'(0A)這個字符替換為''\r\n'(0D0A)這兩個字符,并且在測試中還發(fā)現(xiàn)不同的手機(jī)替換次數(shù)還不相同的,有可能替換一次,也有可能替換二次!所以為解決這個問題,先計算在最開始的10字節(jié)里的數(shù)據(jù)出現(xiàn)了多少次'\r'(0D)字符后再出現(xiàn)‘\n'(0A)字符,因為正常的PNG文件,在文件頭的第4,第5個字節(jié)位置里會有'\r\n'(0D0A)標(biāo)志,所以檢查出來的出現(xiàn)次數(shù)就表示'\n'(0A)被adb client替換了多少次,之后再對整個接收到的數(shù)據(jù)流進(jìn)行'\n'(0A)還原(刪除無用的'\r'(0D)字符)。
>>統(tǒng)計'\n'被替換了次
private static int Find0DCount(MemoryStream stream) { int count = 0; stream.Position = 0; while(stream.Position < 10 && stream.Position < stream.Length) { int b = stream.ReadByte(); if(b == '\r') { count++; } else if(b == '\n') { return count; }else if(count > 0) { count = 0; } } return 0; }
>>對接受到的截圖數(shù)據(jù)流進(jìn)行'\n'字符還原
var count = Find0DCount(memoStream); var newStream = new MemoryStream(); memoStream.Position = 0; while (memoStream.Position != memoStream.Length) { var b = memoStream.ReadByte(); if (b == '\r') { int c = 1; var b1 = memoStream.ReadByte(); while(b1 == '\r' && memoStream.Position != memoStream.Length) { c++; b1 = memoStream.ReadByte(); } if(b1 == '\n') { if(c == count) { newStream.WriteByte((byte)'\r'); } newStream.WriteByte((byte)b1); } else { for(int i=0; i<c; i++) newStream.WriteByte((byte)'\r'); newStream.WriteByte((byte)b1); } } else { newStream.WriteByte((byte)b); } } return new Bitmap(newStream);
3、獲取棋子與跳躍落腳點(diǎn)位置
將獲取到的手機(jī)界面截圖顯示到軟件窗體上的PictureBox控件上,可用鼠標(biāo)的左右鍵分別點(diǎn)擊圖片位置標(biāo)示棋子位置和需要跳的落腳點(diǎn)位置,鼠標(biāo)點(diǎn)擊的坐標(biāo)位置即表示手機(jī)界面的坐標(biāo)位置。由于手機(jī)界面截圖在PictureBox控件顯示時為了能一屏全圖顯示,對圖片做了縮放處理,且圖片縮放后如果圖片的寬度小于PictureBox控件的寬度,PictureBox會將圖片居中后顯示。所以鼠標(biāo)點(diǎn)擊的坐標(biāo)位置還需要進(jìn)行坐標(biāo)轉(zhuǎn)換才可以映射為手機(jī)界面里的絕對坐標(biāo)位置。
轉(zhuǎn)換計算方法:先計算PictureBox控件的圖片縮放值和圖片顯示的左邊距,然后再對鼠標(biāo)點(diǎn)擊坐標(biāo)進(jìn)行縮放計算。代碼如下:
private Point CalPoint(Point p) { if (this.cbZoom.Checked && this.pictureBox1.Image != null) { var zoom = (double)this.pictureBox1.Height / this.pictureBox1.Image.Height; var width = (int)(this.pictureBox1.Image.Width * zoom); var left = this.pictureBox1.Width / 2 - width / 2; return new Point((int)((p.X - left) / zoom), (int)(p.Y / zoom)); } else { return p; } }
如全靠手動鼠標(biāo)點(diǎn)擊坐標(biāo)位置來玩游戲,這和直接在手機(jī)里手動玩游戲是沒有什么區(qū)別的,區(qū)別只在于能夠跳躍精準(zhǔn)些(跳躍力度能自動計算出,下面會講),所以程序還要能夠?qū)崿F(xiàn)自動化,就是要能夠自動找出棋子與跳躍落腳點(diǎn)的位置。
A、找棋子的坐標(biāo)位置
棋子的位置非常的好找,對游戲界面里的棋子(圖2黃色塊)進(jìn)行放大可以發(fā)現(xiàn)棋子底部有一塊區(qū)域(圖3白色塊)的顏色值是固定的R(54)G(60)B(102)顏色,如下兩圖:
(圖2)
(圖3)
根據(jù)棋子的這一顏色特點(diǎn)在獲取到手機(jī)界面截圖時,對圖片象素進(jìn)行掃描,查找R(54)G(60)B(102)這一顏色,找到的坐標(biāo)位置就是棋子的位置。為了能快速掃描圖片,不采用效率較低下的GetPixel方法獲取顏色值,而采用LockBits方法鎖定圖片數(shù)據(jù)到內(nèi)存,再采用指針移動獲取象素顏色,由于采用了指針,代碼需要開啟unsafe定義。且棋子正常情況下不會在最頂部和最底部出現(xiàn),所以不需要對整張界面圖片掃描,只掃描20%-63%區(qū)域的數(shù)據(jù),并且從底部開始找起。
B、找跳躍的落腳點(diǎn)位置
寫此助手只是無聊時的產(chǎn)出物,所以我只是簡單實現(xiàn)。游戲中如果連續(xù)跳到了目標(biāo)物的中間位置時,新目標(biāo)物的中間部分會出現(xiàn)一個白色圈(如上圖2的紅色塊),如果再跳中此位置,會進(jìn)行加分。根據(jù)這一特點(diǎn),程序找出那一白色圈圈的位置即可做為落腳點(diǎn)位置,白色圈的顏色值為R(254)G(254)B(254),如果沒有此白色圈位置,則手動鼠標(biāo)選擇落腳點(diǎn)位置。實現(xiàn)此功能后,程序基本上也能實現(xiàn)90%左右的自動化跳躍了。
查找代碼實現(xiàn)如下:
private static Point FindPointImpl(Bitmap bitmap, out Point comboPoint) { var standPColor = Color.FromArgb(54, 60, 102); var comboPColor = Color.FromArgb(245, 245, 245); Point standPoint = Point.Empty; comboPoint = Point.Empty; int y1 = (int)(bitmap.Height * 0.2); int y2 = (int)(bitmap.Height * 0.63); PixelFormat pf = PixelFormat.Format24bppRgb; BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, y1, bitmap.Width, y2), ImageLockMode.ReadOnly, pf); try { unsafe { int w = 0; while (y2 > y1) { byte* p = (byte*)bitmapData.Scan0 + (y2 - y1 - 1) * bitmapData.Stride; w = bitmap.Width; int endColorCount = 0; while (w > 40) { ICColor* pc = (ICColor*)(p + w * 3); if (standPoint == Point.Empty && pc->R == standPColor.R && pc->G == standPColor.G && pc->B == standPColor.B) { standPoint = new Point(w - 3, y2); if (comboPoint != Point.Empty) break; } else if (comboPoint == Point.Empty) { if (pc->R == comboPColor.R && pc->G == comboPColor.G && pc->B == comboPColor.B) { endColorCount++; } else { if (endColorCount > 0) { comboPoint = new Point(w + 5, y2 - 1); if (standPoint != Point.Empty) break; } endColorCount = 0; } } w--; } if (comboPoint == Point.Empty) { if (endColorCount > 10) { comboPoint = new Point(w + 5, y2 - 1); } } if (standPoint != Point.Empty && comboPoint != Point.Empty) break; y2--; } } return standPoint; } finally { bitmap.UnlockBits(bitmapData); } }
4、棋子跳躍
要能跳躍,首先需要知道一個蓄力時間,就是按住棋子多久的時間,此蓄力時間的計算公式如下:
蓄力時間 = 距離 * 力度系數(shù)
“距離”就是棋子位置與跳躍落腳點(diǎn)位置的距離,根據(jù)上面的方法得出這兩個位置的坐標(biāo)點(diǎn)后,根據(jù)直角三角形的勾股定理即可求出,代碼如下:
public double Distance { get { if (!this.CanDo) return -1; int w = Math.Abs(this.P2.X - this.P1.X); int h = Math.Abs(this.P2.Y - this.P1.Y); return Math.Sqrt((double)(w * w) + (h * h)); } }
“力度系數(shù)” 是一個常量值,具體怎么定義沒去細(xì)查,我采用的計算公式是: “力度系數(shù) = 1495 / 手機(jī)分辨率的寬度值”, 如我的手機(jī)分辨率是1080*1920,則力度系數(shù)就是 1495 / 1080 = 1.3842....
算出了蓄力時間后通過以下adb命令發(fā)送到手機(jī)即可模擬點(diǎn)擊操作。
adb shell input swipe <x1> <y1> <x2> <y2> [duration(ms)]
x1, y1 就是棋子的坐標(biāo)位置
x2, y2 還是棋子的坐標(biāo)位置
duration 蓄力時間值,由距離*力度系數(shù)得出。
代碼如下:
public bool Do() { if (!this.CanDo) return false; var startInfo = new ProcessStartInfo("adb", string.Format("shell input swipe {0} {1} {0} {1} {2}", this.P1.X, this.P1.Y, this.Time)); startInfo.CreateNoWindow = true; startInfo.ErrorDialog = true; startInfo.UseShellExecute = false; var process = Process.Start(startInfo); return process.Start(); }
三、結(jié)束語
程序?qū)崿F(xiàn)很簡單,都是通過adb命令與手機(jī)進(jìn)行交互操作。如果你認(rèn)為對你有幫助麻煩贊下即可:)積分別玩太過哦。
可執(zhí)行文件下載地址:http://xiazai.jb51.net/201801/yuanma/JumperHelper.rar
代碼倉庫:https://github.com/kingthy/JumperHelper
總結(jié)
以上所述是小編給大家介紹的C#實現(xiàn)微信跳一跳小游戲的自動跳躍助手開發(fā)實戰(zhàn),希望對大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會及時回復(fù)大家的!
上一篇:c#快速寫本地日志方法
欄 目:C#教程
本文標(biāo)題:C#實現(xiàn)微信跳一跳小游戲的自動跳躍助手開發(fā)實戰(zhàn)
本文地址:http://mengdiqiu.com.cn/a1/C_jiaocheng/5289.html
您可能感興趣的文章
- 01-10C#實現(xiàn)txt定位指定行完整實例
- 01-10WinForm實現(xiàn)仿視頻 器左下角滾動新聞效果的方法
- 01-10C#實現(xiàn)清空回收站的方法
- 01-10C#實現(xiàn)讀取注冊表監(jiān)控當(dāng)前操作系統(tǒng)已安裝軟件變化的方法
- 01-10C#實現(xiàn)多線程下載文件的方法
- 01-10C#實現(xiàn)Winform中打開網(wǎng)頁頁面的方法
- 01-10C#實現(xiàn)遠(yuǎn)程關(guān)閉計算機(jī)或重啟計算機(jī)的方法
- 01-10C#自定義簽名章實現(xiàn)方法
- 01-10C#文件斷點(diǎn)續(xù)傳實現(xiàn)方法
- 01-10winform實現(xiàn)創(chuàng)建最前端窗體的方法


閱讀排行
本欄相關(guān)
- 01-10C#通過反射獲取當(dāng)前工程中所有窗體并
- 01-10關(guān)于ASP網(wǎng)頁無法打開的解決方案
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10WinForm繪制圓角的方法
- 01-10C#實現(xiàn)txt定位指定行完整實例
- 01-10WinForm實現(xiàn)仿視頻 器左下角滾動新
- 01-10C#停止線程的方法
- 01-10C#實現(xiàn)清空回收站的方法
- 01-10C#通過重寫Panel改變邊框顏色與寬度的
- 01-10C#實現(xiàn)讀取注冊表監(jiān)控當(dāng)前操作系統(tǒng)已
隨機(jī)閱讀
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-11ajax實現(xiàn)頁面的局部加載
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 01-10delphi制作wav文件的方法
- 01-10C#中split用法實例總結(jié)
- 04-02jquery與jsp,用jquery
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05DEDE織夢data目錄下的sessions文件夾有什