C#实现微信跳一跳小游戏的自动跳跃助手开发实战

 更新时间:2020年6月25日 11:17  点击:2071

一、前言:

前段时间微信更新了新版本后,带来的一款H5小游戏“跳一跳”在各朋友圈里又火了起来,类似以前的“打飞机”游戏,这游戏玩法简单,但加上了积分排名功能后,却成了“装逼”的地方,于是很多人花钱花时间的刷积分抢排名。后来越来越多的聪明的“程序哥们”弄出了不同方式不同花样的跳一跳助手(外挂?),有用JS实现的、有JAVA实现的、有Python实现的,有直接物理模式的、有机械化的、有量尺子的等等,简直是百花齐放啊……

赶一下潮流,刚好有点时间,于是花了一个下午时间,我也弄了一个C#版本的简单实现。

二、实现:

简单的实现流程: 连接手机 -> 获取跳一跳游戏界面 -> 获取位置(棋子位置和要跳跃的落脚点位置) -> 点击棋子跳跃

1、连接手机

电脑要连接并操作安卓手机,一般是通过ADB协议连接手机并进行操作。连接手机前要求手机已开启USB调试模式,可通过USB线或者TCP方式连接手机。正常只要电脑安装了adb sdk tools之类的工具包,就会自带有adb命令,所以C#要能操作手机,简单实现就是直接利用现成的adb命令。

手机通过USB线接入电脑后,在CMD窗口输入以下adb devices命令,如果显示有device列表则表示手机已连接成功可以对手机进行操作了。

C:\Users\k>adb devices
List of devices attached
e832acb device

2、获取游戏界面

获取手机界面的截图可通过以下adb命令获取:

adb shell screencap -p [filename]

参数 :

- p 表示截图保存格式为PNG图像格式。

filename: 截图保存的路径地址(手机路径),如果不输入则将截图数据直接输出到当前控制台会话,否则会将截图保存到相关路径地址(必须有写权限)

为避免文件保存到手机后还要再执行adb pull(拉文件到本地电脑)的操作,所以选择不带filename参数的命令。在C#代码里通过Process这个类进行adb命令的调用执行,实现代码如下:

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的原因,在它输出的截图数据流中会对'\n'(0A)这个字符替换为''\r\n'(0D0A)这两个字符,并且在测试中还发现不同的手机替换次数还不相同的,有可能替换一次,也有可能替换二次!所以为解决这个问题,先计算在最开始的10字节里的数据出现了多少次'\r'(0D)字符后再出现‘\n'(0A)字符,因为正常的PNG文件,在文件头的第4,第5个字节位置里会有'\r\n'(0D0A)标志,所以检查出来的出现次数就表示'\n'(0A)被adb client替换了多少次,之后再对整个接收到的数据流进行'\n'(0A)还原(删除无用的'\r'(0D)字符)。

>>统计'\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;
 }

>>对接受到的截图数据流进行'\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、获取棋子与跳跃落脚点位置

将获取到的手机界面截图显示到软件窗体上的PictureBox控件上,可用鼠标的左右键分别点击图片位置标示棋子位置和需要跳的落脚点位置,鼠标点击的坐标位置即表示手机界面的坐标位置。由于手机界面截图在PictureBox控件显示时为了能一屏全图显示,对图片做了缩放处理,且图片缩放后如果图片的宽度小于PictureBox控件的宽度,PictureBox会将图片居中后显示。所以鼠标点击的坐标位置还需要进行坐标转换才可以映射为手机界面里的绝对坐标位置。

转换计算方法:先计算PictureBox控件的图片缩放值和图片显示的左边距,然后再对鼠标点击坐标进行缩放计算。代码如下:      

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

如全靠手动鼠标点击坐标位置来玩游戏,这和直接在手机里手动玩游戏是没有什么区别的,区别只在于能够跳跃精准些(跳跃力度能自动计算出,下面会讲),所以程序还要能够实现自动化,就是要能够自动找出棋子与跳跃落脚点的位置。

A、找棋子的坐标位置

棋子的位置非常的好找,对游戏界面里的棋子(图2黄色块)进行放大可以发现棋子底部有一块区域(图3白色块)的颜色值是固定的R(54)G(60)B(102)颜色,如下两图:

(图2)

(图3)

根据棋子的这一颜色特点在获取到手机界面截图时,对图片象素进行扫描,查找R(54)G(60)B(102)这一颜色,找到的坐标位置就是棋子的位置。为了能快速扫描图片,不采用效率较低下的GetPixel方法获取颜色值,而采用LockBits方法锁定图片数据到内存,再采用指针移动获取象素颜色,由于采用了指针,代码需要开启unsafe定义。且棋子正常情况下不会在最顶部和最底部出现,所以不需要对整张界面图片扫描,只扫描20%-63%区域的数据,并且从底部开始找起。

B、找跳跃的落脚点位置

写此助手只是无聊时的产出物,所以我只是简单实现。游戏中如果连续跳到了目标物的中间位置时,新目标物的中间部分会出现一个白色圈(如上图2的红色块),如果再跳中此位置,会进行加分。根据这一特点,程序找出那一白色圈圈的位置即可做为落脚点位置,白色圈的颜色值为R(254)G(254)B(254),如果没有此白色圈位置,则手动鼠标选择落脚点位置。实现此功能后,程序基本上也能实现90%左右的自动化跳跃了。

查找代码实现如下:

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、棋子跳跃

要能跳跃,首先需要知道一个蓄力时间,就是按住棋子多久的时间,此蓄力时间的计算公式如下:

蓄力时间 = 距离 * 力度系数

“距离”就是棋子位置与跳跃落脚点位置的距离,根据上面的方法得出这两个位置的坐标点后,根据直角三角形的勾股定理即可求出,代码如下:      

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

“力度系数”  是一个常量值,具体怎么定义没去细查,我采用的计算公式是: “力度系数 = 1495 / 手机分辨率的宽度值”, 如我的手机分辨率是1080*1920,则力度系数就是 1495 / 1080 = 1.3842....

算出了蓄力时间后通过以下adb命令发送到手机即可模拟点击操作。

adb shell input swipe <x1> <y1> <x2> <y2> [duration(ms)]

x1, y1 就是棋子的坐标位置

x2, y2 还是棋子的坐标位置

duration 蓄力时间值,由距离*力度系数得出。

代码如下:   

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

三、结束语

程序实现很简单,都是通过adb命令与手机进行交互操作。如果你认为对你有帮助麻烦赞下即可:)积分别玩太过哦。


可执行文件下载地址:http://xiazai.jb51.net/201801/yuanma/JumperHelper.rar

代码仓库:https://github.com/kingthy/JumperHelper

总结

以上所述是小编给大家介绍的C#实现微信跳一跳小游戏的自动跳跃助手开发实战,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

[!--infotagslink--]

相关文章

  • C#实现简单的登录界面

    我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
  • 浅谈C# 字段和属性

    这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
  • C#中截取字符串的的基本方法详解

    这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • C#实现简单的Http请求实例

    这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
  • C#中new的几种用法详解

    本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
  • 使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)

    这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#开发Windows窗体应用程序的简单操作步骤

    这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • C#和JavaScript实现交互的方法

    最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • 轻松学习C#的基础入门

    轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
  • C#变量命名规则小结

    本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
  • C#绘制曲线图的方法

    这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • c#中(&&,||)与(&,|)的区别详解

    这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • 经典实例讲解C#递归算法

    这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#中list用法实例

    这篇文章主要介绍了C#中list用法,结合实例形式分析了C#中list排序、运算、转换等常见操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25