溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

如何使用C#代碼實(shí)現(xiàn)跳一跳自動(dòng)跳躍

發(fā)布時(shí)間:2021-08-06 14:19:36 來(lái)源:億速云 閱讀:204 作者:小新 欄目:編程語(yǔ)言

這篇文章將為大家詳細(xì)講解有關(guān)如何使用C#代碼實(shí)現(xiàn)跳一跳自動(dòng)跳躍,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

效果圖

如何使用C#代碼實(shí)現(xiàn)跳一跳自動(dòng)跳躍如何使用C#代碼實(shí)現(xiàn)跳一跳自動(dòng)跳躍

       因?yàn)楸容^急著做出成品,所以細(xì)節(jié)上沒(méi)多細(xì)摳。感覺(jué)設(shè)置的跳躍速度稍快了一點(diǎn),有興趣的同學(xué)可以實(shí)測(cè)一下。也有一個(gè)因素是測(cè)試時(shí)后臺(tái)程序比較多,影響了結(jié)果。
       原理其實(shí)也是跟大家想的一樣很簡(jiǎn)單,無(wú)非就是三個(gè)要素:距離、速度、時(shí)間。就是通過(guò)當(dāng)前小藍(lán)人腳底所在的像素坐標(biāo)和目標(biāo)平臺(tái)中心像素的坐標(biāo)計(jì)算距離,除以事先通過(guò)測(cè)試得出的速度,得出觸摸屏幕時(shí)間,由程序發(fā)出“觸摸”指令,實(shí)現(xiàn)定點(diǎn)跳躍。不過(guò)在做自動(dòng)計(jì)算跳躍所需觸摸時(shí)間之前還是要做一些準(zhǔn)備功夫的。下面直接說(shuō)一下詳細(xì)的過(guò)程吧。

準(zhǔn)備工作:

1、通過(guò)PS等工具獲取①小藍(lán)人最底下一行(作為當(dāng)前位置Y坐標(biāo))和最左邊一列(作為當(dāng)前位置X坐標(biāo))的像素RGB,實(shí)測(cè)在本機(jī)基本都是一樣的X(54,63, 102),Y(43, 43, 73)。圖片左上角、右下角坐標(biāo)分別為[0,0][Xmax,Ymax]。②獲取小藍(lán)人的頭的寬度(所占像素點(diǎn))。③獲取左上角分?jǐn)?shù)最底下一行的像素y坐標(biāo)。

2、通過(guò)指令

adb shell input touchscreen swipe x y x y 延時(shí)(ms)

(x、y為觸摸屏幕的坐標(biāo)),結(jié)合photoshop測(cè)試出“跳一跳”每一條的速度。本例中測(cè)得結(jié)果約為17 / 24(pixel/ms),實(shí)際游戲中的速度略小于這個(gè)速度。大家用代碼可以精確測(cè)一下,我已經(jīng)沒(méi)耐心了0.0。

3、電腦準(zhǔn)備好調(diào)試環(huán)境(因?yàn)楦F所以測(cè)試用的是自己的Android機(jī),所以要準(zhǔn)備好ADK(platform-tools/adb.exe);另外本次測(cè)試語(yǔ)言是C#)

4、手機(jī)開(kāi)啟調(diào)試模式,連接電腦,打開(kāi)“跳一跳” 

過(guò)程:

一、獲取設(shè)備號(hào)(獲取序列號(hào),或者直接查看手機(jī)信息),指令:

adb devices

二、截取手機(jī)當(dāng)前畫(huà)面到sd卡(本機(jī)存儲(chǔ)格式為png,實(shí)測(cè)手機(jī)按鍵截屏為jpg(失真)),指令:

adb -s 設(shè)備號(hào) shell screencap -p /sdcard/temp.png

三、復(fù)制文件到電腦,指令:

adb -s 設(shè)備號(hào) pull /sdcard/temp.png 保存路徑

四、刪除文件,指令:

adb -s 設(shè)備號(hào) shell rm /sdcard/temp.png

五、獲取小藍(lán)人腳底像素坐標(biāo)和目標(biāo)平臺(tái)中心像素坐標(biāo),下面詳細(xì)說(shuō)說(shuō)里面的步驟

1、通過(guò)Bitmap類(lèi)讀取圖片,再用unsafe代碼利用指針把RGB數(shù)據(jù)直接從內(nèi)存拷出來(lái)存放到byte數(shù)組中(這步其實(shí)不用也可以但不知道直接通過(guò)Bitmap獲取像素效率會(huì)不會(huì)很低,大家可以測(cè)了分享一下結(jié)果)
2、用兩層循環(huán)y從max->0,遍歷x軸像素,通過(guò)對(duì)比找出小藍(lán)人位置,本例通過(guò)兩個(gè)rgb像素的標(biāo)準(zhǔn)差不超過(guò)3作為置信偏差判斷兩個(gè)像素是否為同一元素。再稍微處理一下就可得出當(dāng)前坐標(biāo)。
3、利用上面得到的坐標(biāo)P以及一開(kāi)始準(zhǔn)備工作中提到的分?jǐn)?shù)底行y坐標(biāo)(取大于該y作為startY即可)再進(jìn)行對(duì)目標(biāo)坐標(biāo)的搜索:用兩層循環(huán)y從startY->Py,遍歷x軸像素(利用P的x坐標(biāo)縮小搜索的x坐標(biāo)范圍:若x位于左半屏則搜索Px+40->Xmax,反之搜索0->Px-40,注:不縮小范圍會(huì)出錯(cuò),原因大家想想)。(這個(gè)40可取大于小藍(lán)人頭寬度一半的值即可)
4、那就用我們的勾三股四弦五定理再開(kāi)根求出距離。距離除以速度得出時(shí)間。

六、發(fā)送觸摸指令實(shí)現(xiàn)定時(shí)跳躍,指令:

adb shell input touchscreen swipe x y x y延時(shí)(ms)

       這里不得不說(shuō)一下,當(dāng)時(shí)找半天找不到定時(shí)觸摸的指令,網(wǎng)上有個(gè)用6個(gè)指令組合實(shí)現(xiàn)定時(shí)觸摸屏幕的方法,但實(shí)測(cè)無(wú)效,而且也怕指令這么多,延時(shí)還是分開(kāi)控制,肯定會(huì)對(duì)跳躍結(jié)果有很大影響。后面看到一條利用swipe指令實(shí)現(xiàn)的評(píng)論,真是醒目。swipe雖然是滑動(dòng)指令,但如果設(shè)置起止坐標(biāo)都是同一個(gè)坐標(biāo)不就相當(dāng)于實(shí)現(xiàn)了定點(diǎn)定時(shí)觸摸了嗎。

七、七就是一直重復(fù)二~六的步驟就是了。

       本次測(cè)試很東西都是急著做,沒(méi)仔細(xì)研究,例如獲取跳躍速度這個(gè)就是傻瓜式的通過(guò)手動(dòng)發(fā)送跳躍指令、截圖用ps手動(dòng)計(jì)算出來(lái)的。大家可以用代碼實(shí)現(xiàn)一下。希望大家指正可以改進(jìn)的地方。

C#源碼如下

Cmd類(lèi),實(shí)現(xiàn)cmd執(zhí)行命令

class Cmd 
{ 
 private System.Diagnostics.Process process; 
 private bool isExecuted; // 是否執(zhí)行過(guò)命令 
 private string command; // 上次執(zhí)行命令 
 private int result;  // 上次執(zhí)行命令結(jié)果 
 private string resultContent; // 上次執(zhí)行命令返回結(jié)果 
 public Cmd() 
 { 
 process = new System.Diagnostics.Process(); 
 process.StartInfo.FileName = "cmd.exe"; 
 process.StartInfo.UseShellExecute = false; //是否使用操作系統(tǒng)shell啟動(dòng) 
 process.StartInfo.RedirectStandardInput = true;//接受來(lái)自調(diào)用程序的輸入信息 
 process.StartInfo.RedirectStandardOutput = true;//由調(diào)用程序獲取輸出信息 
 process.StartInfo.RedirectStandardError = true;//重定向標(biāo)準(zhǔn)錯(cuò)誤輸出 
 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();//等待程序執(zhí)行完退出進(jìn)程 
  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();//等待程序執(zhí)行完退出進(jìn)程 
  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 "從未執(zhí)行過(guò)命令"; 
 } 
 } 
 public void DisposeProcess() 
 { 
 process.Dispose(); 
 } 
} 
 
class Pixel 
{ 
 public byte[] pixel = new byte[3]; 
 public Pixel() 
 { 
 
 } 
}

Pixel類(lèi),存放RGB字節(jié)

class Pixel 
 { 
 public byte[] pixel = new byte[3]; 
 public Pixel() 
 { 
 
 } 
 }

PlayJumpJump類(lèi),實(shí)現(xiàn)主要分析計(jì)算和跳躍操作

class PlayJumpJump 
 { 
 private static readonly int confidenceItv = 3; // 兩個(gè)rgb標(biāo)準(zhǔn)差小于等于3認(rèn)為是同一元素 
 private static readonly Pixel manXRgb = new Pixel { pixel = new byte[] { 54, 63, 102 } }; // 小人X坐標(biāo)rgb 
 private static readonly Pixel manYRgb = new Pixel { pixel = new byte[] { 43, 43, 73 } }; // 小人Y坐標(biāo)rgb 
 private static readonly double startYPer = 0.15625; // 分?jǐn)?shù)下一行Y為第289,取 300 / 1920 = 0.15625, 從下一行開(kāi)始搜索目標(biāo) 
 private static readonly double Speed = 17.0 / 24; // 速度,最重要的因素,這也是約摸算出來(lái)的 
 private static readonly string[] TouchCoor = new string[] { "800", "1700" }; // 觸屏位置 
 private static readonly string Format = "png"; // 本人用機(jī)子截取為png,也可不設(shè)格式(實(shí)測(cè)bitmap與ps cc打開(kāi)同一jpg,同一像素點(diǎn)rgb值不一致,懷疑是bitmap打開(kāi)jpg會(huì)有失真) 
 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; // 減去占位字節(jié)(可能出于性能或兼容性考慮,Stride為4的倍數(shù)) 
  } 
 
  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個(gè)字節(jié)忽略1個(gè)透明度字節(jié) 
   } 
  } 
 
  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; 
 }

測(cè)試:

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(); 
 } 
 }

關(guān)于“如何使用C#代碼實(shí)現(xiàn)跳一跳自動(dòng)跳躍”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI