您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關如何使用C#代碼實現跳一跳自動跳躍,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
效果圖
因為比較急著做出成品,所以細節上沒多細摳。感覺設置的跳躍速度稍快了一點,有興趣的同學可以實測一下。也有一個因素是測試時后臺程序比較多,影響了結果。
原理其實也是跟大家想的一樣很簡單,無非就是三個要素:距離、速度、時間。就是通過當前小藍人腳底所在的像素坐標和目標平臺中心像素的坐標計算距離,除以事先通過測試得出的速度,得出觸摸屏幕時間,由程序發出“觸摸”指令,實現定點跳躍。不過在做自動計算跳躍所需觸摸時間之前還是要做一些準備功夫的。下面直接說一下詳細的過程吧。
準備工作:
1、通過PS等工具獲取①小藍人最底下一行(作為當前位置Y坐標)和最左邊一列(作為當前位置X坐標)的像素RGB,實測在本機基本都是一樣的X(54,63, 102),Y(43, 43, 73)。圖片左上角、右下角坐標分別為[0,0][Xmax,Ymax]。②獲取小藍人的頭的寬度(所占像素點)。③獲取左上角分數最底下一行的像素y坐標。
2、通過指令
adb shell input touchscreen swipe x y x y 延時(ms)
(x、y為觸摸屏幕的坐標),結合photoshop測試出“跳一跳”每一條的速度。本例中測得結果約為17 / 24(pixel/ms),實際游戲中的速度略小于這個速度。大家用代碼可以精確測一下,我已經沒耐心了0.0。
3、電腦準備好調試環境(因為窮所以測試用的是自己的Android機,所以要準備好ADK(platform-tools/adb.exe);另外本次測試語言是C#)
4、手機開啟調試模式,連接電腦,打開“跳一跳”
過程:
一、獲取設備號(獲取序列號,或者直接查看手機信息),指令:
adb devices
二、截取手機當前畫面到sd卡(本機存儲格式為png,實測手機按鍵截屏為jpg(失真)),指令:
adb -s 設備號 shell screencap -p /sdcard/temp.png
三、復制文件到電腦,指令:
adb -s 設備號 pull /sdcard/temp.png 保存路徑
四、刪除文件,指令:
adb -s 設備號 shell rm /sdcard/temp.png
五、獲取小藍人腳底像素坐標和目標平臺中心像素坐標,下面詳細說說里面的步驟
1、通過Bitmap類讀取圖片,再用unsafe代碼利用指針把RGB數據直接從內存拷出來存放到byte數組中(這步其實不用也可以但不知道直接通過Bitmap獲取像素效率會不會很低,大家可以測了分享一下結果)
2、用兩層循環y從max->0,遍歷x軸像素,通過對比找出小藍人位置,本例通過兩個rgb像素的標準差不超過3作為置信偏差判斷兩個像素是否為同一元素。再稍微處理一下就可得出當前坐標。
3、利用上面得到的坐標P以及一開始準備工作中提到的分數底行y坐標(取大于該y作為startY即可)再進行對目標坐標的搜索:用兩層循環y從startY->Py,遍歷x軸像素(利用P的x坐標縮小搜索的x坐標范圍:若x位于左半屏則搜索Px+40->Xmax,反之搜索0->Px-40,注:不縮小范圍會出錯,原因大家想想)。(這個40可取大于小藍人頭寬度一半的值即可)
4、那就用我們的勾三股四弦五定理再開根求出距離。距離除以速度得出時間。
六、發送觸摸指令實現定時跳躍,指令:
adb shell input touchscreen swipe x y x y延時(ms)
這里不得不說一下,當時找半天找不到定時觸摸的指令,網上有個用6個指令組合實現定時觸摸屏幕的方法,但實測無效,而且也怕指令這么多,延時還是分開控制,肯定會對跳躍結果有很大影響。后面看到一條利用swipe指令實現的評論,真是醒目。swipe雖然是滑動指令,但如果設置起止坐標都是同一個坐標不就相當于實現了定點定時觸摸了嗎。
七、七就是一直重復二~六的步驟就是了。
本次測試很東西都是急著做,沒仔細研究,例如獲取跳躍速度這個就是傻瓜式的通過手動發送跳躍指令、截圖用ps手動計算出來的。大家可以用代碼實現一下。希望大家指正可以改進的地方。
C#源碼如下
Cmd類,實現cmd執行命令
class Cmd { private System.Diagnostics.Process process; private bool isExecuted; // 是否執行過命令 private string command; // 上次執行命令 private int result; // 上次執行命令結果 private string resultContent; // 上次執行命令返回結果 public Cmd() { process = new System.Diagnostics.Process(); process.StartInfo.FileName = "cmd.exe"; process.StartInfo.UseShellExecute = false; //是否使用操作系統shell啟動 process.StartInfo.RedirectStandardInput = true;//接受來自調用程序的輸入信息 process.StartInfo.RedirectStandardOutput = true;//由調用程序獲取輸出信息 process.StartInfo.RedirectStandardError = true;//重定向標準錯誤輸出 process.StartInfo.CreateNoWindow = true;//不顯示程序窗口 isExecuted = false; } public int ExecuteCmd(string cmd) { command = cmd; try { process.Start(); process.StandardInput.WriteLine(cmd + "&exit"); process.StandardInput.AutoFlush = true; string content = process.StandardOutput.ReadToEnd(); process.WaitForExit();//等待程序執行完退出進程 process.Close(); result = 0; resultContent = content.Split(new string[] { "&exit" }, StringSplitOptions.None)[1].Replace("\n", ""); } catch (Exception ex) { result = -1; resultContent = ex.Message; } if (!isExecuted) isExecuted = true; return result; } private int ExecuteCmd(string adbPath, string cmd) { command = $"\"{adbPath}\" {cmd}"; try { process.Start(); process.StandardInput.WriteLine(command + "&exit"); process.StandardInput.AutoFlush = true; string content = process.StandardOutput.ReadToEnd(); process.WaitForExit();//等待程序執行完退出進程 process.Close(); result = 0; resultContent = content.Split(new string[] { "&exit" }, StringSplitOptions.None)[1].Replace("\n", ""); } catch (Exception ex) { result = -1; resultContent = ex.Message; } if (!isExecuted) isExecuted = true; return result; } public string GetExcResult() { if (isExecuted) { if (result == 0) { return resultContent; } else { return $"Execute Failed! Command:{command}\n{resultContent}"; } } else { return "從未執行過命令"; } } public void DisposeProcess() { process.Dispose(); } } class Pixel { public byte[] pixel = new byte[3]; public Pixel() { } }
Pixel類,存放RGB字節
class Pixel { public byte[] pixel = new byte[3]; public Pixel() { } }
PlayJumpJump類,實現主要分析計算和跳躍操作
class PlayJumpJump { private static readonly int confidenceItv = 3; // 兩個rgb標準差小于等于3認為是同一元素 private static readonly Pixel manXRgb = new Pixel { pixel = new byte[] { 54, 63, 102 } }; // 小人X坐標rgb private static readonly Pixel manYRgb = new Pixel { pixel = new byte[] { 43, 43, 73 } }; // 小人Y坐標rgb private static readonly double startYPer = 0.15625; // 分數下一行Y為第289,取 300 / 1920 = 0.15625, 從下一行開始搜索目標 private static readonly double Speed = 17.0 / 24; // 速度,最重要的因素,這也是約摸算出來的 private static readonly string[] TouchCoor = new string[] { "800", "1700" }; // 觸屏位置 private static readonly string Format = "png"; // 本人用機子截取為png,也可不設格式(實測bitmap與ps cc打開同一jpg,同一像素點rgb值不一致,懷疑是bitmap打開jpg會有失真) private static readonly string TempDir = "/sdcard/"; private static readonly string SaveDir = "temp/"; private static readonly string CaptureScreen_Command = $"-s {{0}} shell screencap -p {TempDir}{{1}}"; private static readonly string CopyFile_Command = $"-s {{0}} pull {TempDir}{{1}} \"{SaveDir}{{1}}\""; private static readonly string RemoveFile_Command = $"-s {{0}} shell rm {TempDir}{{1}}"; private static readonly string LongPress_Command = "shell input touchscreen swipe {0} {1} {0} {1} {2}"; private Cmd myCmd; private string adbCmdPrefix; private string result; public List<string> devices; public PlayJumpJump(string adbPath) { myCmd = new Cmd(); adbCmdPrefix = $"\"{adbPath}\" "; if (!Directory.Exists(SaveDir)) { Directory.CreateDirectory(SaveDir); } } public void Init() { myCmd = new Cmd(); } public bool GetDevices() { devices = new List<string>(); myCmd.ExecuteCmd(ReturnCommand("devices")); result = myCmd.GetExcResult(); foreach (string line in result.Split(new char[] { '\n'})) { if (line.Contains("device")) { List<string> items = line.Split(new char[] { '\t', '\r' }, StringSplitOptions.None).ToList(); if (items.Count > 1) { devices.Add(items[items.IndexOf("device") - 1]); } } } return devices.Count > 0 ? true : false; } public string CaptureScreen() { string fileName = $"temp{DateTime.Now.ToString("HHmmssfff")}.{Format}"; myCmd.ExecuteCmd(ReturnCommand(CaptureScreen_Command, new string[] { devices[0], fileName })); myCmd.ExecuteCmd(ReturnCommand(CopyFile_Command, new string[] { devices[0], fileName })); myCmd.ExecuteCmd(ReturnCommand(RemoveFile_Command, new string[] { devices[0], fileName })); return AppDomain.CurrentDomain.BaseDirectory + SaveDir + fileName; } public static unsafe Pixel[][] GetPixelArray(string path) { Bitmap bitmap = new Bitmap(path); int depth = Image.GetPixelFormatSize(bitmap.PixelFormat); if (depth == 24) { int width = bitmap.Width; int height = bitmap.Height; Pixel[][] pixelArray = new Pixel[height][]; for (int i = 0; i < pixelArray.Length; i++) pixelArray[i] = new Pixel[width]; Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height); BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); byte* ptr = (byte*)bmpData.Scan0; for (int i = 0; i < pixelArray.Length; i++) { for (int j = 0; j < pixelArray[i].Length; j++) { pixelArray[i][j] = new Pixel { pixel = new byte[] { *(ptr + 2), *(ptr + 1), *ptr } }; ptr += 3; } ptr += bmpData.Stride - 3 * bmpData.Width; // 減去占位字節(可能出于性能或兼容性考慮,Stride為4的倍數) } bitmap.UnlockBits(bmpData); return pixelArray; } else if (depth == 32) { int width = bitmap.Width; int height = bitmap.Height; Pixel[][] pixelArray = new Pixel[height][]; for (int i = 0; i < pixelArray.Length; i++) pixelArray[i] = new Pixel[width]; Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height); BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb); byte* ptr = (byte*)bmpData.Scan0; for (int i = 0; i < pixelArray.Length; i++) { for (int j = 0; j < pixelArray[i].Length; j++) { pixelArray[i][j] = new Pixel { pixel = new byte[] { *(ptr + 2), *(ptr + 1), *ptr } }; ptr += 4; // 每3個字節忽略1個透明度字節 } } bitmap.UnlockBits(bmpData); return pixelArray; } else { return null; } } public void Jump2Happy() { string picture = CaptureScreen(); Pixel[][] pixelArray = GetPixelArray(picture); int[] curCoor = GetCurCoordinates(pixelArray); int[] destCoor = GetDestCoordinates(pixelArray, curCoor); double distance = Math.Round(Math.Sqrt(Math.Pow(Math.Abs(destCoor[0] - curCoor[0]), 2) + Math.Pow(Math.Abs(destCoor[1] - curCoor[1]), 2)), 3); int time = (int)(distance / Speed); Console.WriteLine($"from [{curCoor[0]},{curCoor[1]}]\tto [{destCoor[0]},{destCoor[1]}] distance≈{distance} take≈{time}ms ==>> Jump "); myCmd.ExecuteCmd(ReturnCommand(LongPress_Command, new string[] { TouchCoor[0], TouchCoor[1], time.ToString() })); } public static int[] GetCurCoordinates(Pixel[][] pixelArray) { int[] coordinates = new int[2]; List<int[]> xList = new List<int[]>(); List<int[]> yList = new List<int[]>(); // y從max -> 0,遍歷x軸像素 for (int i = pixelArray.Length - 1; i >= 0; i--) { for (int j = 0; j < pixelArray[i].Length; j++) { if (isSameElement(pixelArray[i][j], manXRgb, confidenceItv)) { xList.Add(new int[] { j, i }); } } if (xList.Count > 0) break; } coordinates[0] = xList.Count > 0 ? (xList[0][0] + xList[xList.Count - 1][0]) / 2 : 0; // x從0 -> max,遍歷y軸像素 for (int i = 0; i < pixelArray[0].Length; i++) { for (int j = pixelArray.Length - 1; j >= 0; j--) { if (isSameElement(pixelArray[j][i], manYRgb, confidenceItv)) { yList.Add(new int[] { i, j }); } } if (yList.Count > 0) break; } coordinates[1] = yList.Count > 0 ? (yList[0][1] + yList[yList.Count - 1][1]) / 2 : 0; return coordinates; } public static int[] GetDestCoordinates(Pixel[][] pixelArray, int[] curCoor) { Pixel enviRgb; // 排除rgb采樣 Pixel destRgb = null; // 采樣 int[] coordinates = new int[2]; List<int[]> xList = new List<int[]>(); List<int[]> yList = new List<int[]>(); int startY = (int)(pixelArray.Length * startYPer); int start, end, inc; if (curCoor[0] < (pixelArray[0].Length / 2)) { start = curCoor[0] + 40; end = pixelArray[0].Length; } else { start = 0; end = curCoor[0] - 40; } // y從0 -> max,遍歷x軸像素 for (int i = startY; i < pixelArray.Length; i++) { enviRgb = pixelArray[i][0]; for (int j = start; j < end; j++) { if (!isSameElement(pixelArray[i][j], enviRgb, confidenceItv)) { xList.Add(new int[] { j, i }); if (destRgb == null) destRgb = pixelArray[i][j]; } } if (xList.Count > 0) break; } coordinates[0] = xList.Count > 0 ? (xList[0][0] + xList[xList.Count - 1][0]) / 2 : 0; // x從0 -> max,遍歷y軸像素 if (coordinates[0] < (pixelArray[0].Length / 2)) { start = 0; end = pixelArray[0].Length - 1; inc = 1; } else { start = pixelArray[0].Length - 1; end = 0; inc = -1; } bool isFond = false; for (int i = start; i != end; i+=inc) { for (int j = startY; j < curCoor[1]; j++) { if (isSameElement(pixelArray[j][i], destRgb, confidenceItv)) { coordinates[1] = j; isFond = true; break; } } if (isFond) break; } return coordinates; } public static bool isSameElement(Pixel pixel1, Pixel pixel2, int confidence) { return Math.Pow(pixel1.pixel[0] - pixel2.pixel[0], 2) + Math.Pow(pixel1.pixel[1] - pixel2.pixel[1], 2) + Math.Pow(pixel1.pixel[2] - pixel2.pixel[2], 2) <= 3 * Math.Pow(confidence, 2); } public string ReturnCommand(string command, string[] parameter) { return adbCmdPrefix + string.Format(command, parameter); } public string ReturnCommand(string command, string parameter) { return adbCmdPrefix + string.Format(command, parameter); } public string ReturnCommand(string command) { return adbCmdPrefix + command; } public void DisposeProcess() { myCmd.DisposeProcess(); myCmd = null; }
測試:
static void Main(string[] args) { string adbPath = ""; // adb.exe路徑 PlayJumpJump testPlay = new PlayJumpJump(adbPath); if (testPlay.GetDevices()) { while (true) { testPlay.Jump2Happy(); Thread.Sleep(1200); } } testPlay.DisposeProcess(); Console.ReadKey(); } }
關于“如何使用C#代碼實現跳一跳自動跳躍”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。