腾讯在2017年底推出一款在微信小程序跳一跳的游戏,这个游戏的玩法非常简单,手指长按屏幕“i”形小人,不断在各种方形道具上跳跃而获得分数,直到最终摔下去,然后在排行榜上和好友PK。这种无需下载、即点即玩的小游戏,让人们随时随地开跳,并且自带“社交属性”,用户可以和微信好友之间互相PK排名。
这类H5轻度手游的一个特点是产品简单好玩易操作,又不失乐趣均是其共同的特征。产品玩法足够单一聚焦,比如跳一跳,人们仅仅拖住小人划一定的弧度即可。这使得这类产由于上手操作几乎无门槛,熟人圈子朋友看到后可以迅速上手操作试玩,加入分数比拼的游戏大战。
但自从这款小游戏火了之后,网上有不少的程序猿大神用各种编程语言来破解小游戏的分数,其中用Python语言来实现的程序猿就多,不过本人是搞Android开发的,所以在这篇文章里主要讲的是用Java语言实现刷微信跳一跳小游戏的分数。
在用Java语言实现刷微信跳一跳小游戏的分数之前先说一下一般用外挂刷游戏分数分为两种方式,一种是物理外挂,另一种是软件外挂。所谓物理外挂就是用C语言写一个小程序,然后嵌入单片机里面控制外部的物理装备来模拟玩家玩游戏的操作。物理外挂的优点是游戏后台很难发现玩家作弊行为,缺点就是需要买其它装备、刷分率较低;而所谓的软件外挂就是通过其它编程语言开发一个小程序或应用直接对游戏设备进行控制。软件外挂的优点是无需买其它装备,刷分率较高,缺点就是游戏后台很容易发现玩家作弊行为。本文实现的方式就是软件外挂。
环境搭建
1、准备Java运行与编译环境,使用Java8以上,IDE推荐使用Intellij;
2、安装Android SDK;
3、使用 git工具clone项目;
3、准备好一部已经打开开发者模式的Android手机;
请确认是否adb已经联接上你的手机;如果adb连接失败,则会导致截图与拉取截图失败,提示“find myPos, fail”;如果连着多个Android设备的话,最好关到只有一个;
打开开发者选项,找到“USB调试(安全设置)允许通过USB调试修改权限或者模拟点击这个开关,打开它;如果这个权限没有授予,则不能正常触发弹跳;
修改com.skyline.wxjumphack.Hack中ADB_PATH,将其改为你自己的ADB位置。
使用方法
有JAVA开发工具的同学可以直接运行java代码,便于代码调试,下面主要介绍运行已经打包好的jar包的方法
-
手机打开USB调试,并连接电脑
(1) 打开USB调试方法,进入设置
,找到开发者选项
,打开并勾选USB调试
;
(2)如果没有开发者选项
,进入关于手机
,连续点击版本号
5次,即可开启开发者选项
。 -
通过下面的命令,运行Android.jar
java -jar Android.jar
-
根据手机分辨率选择跳跃系数,目前已适配机型:
- 1600x2560机型推荐0.92
- 1440x2560机型推荐1.039
- 1080x1920机型推荐1.392
- 720x1280机型推荐2.078
其它分辨率请自己微调。
原理说明
-
通过adb命令控制手机截图,并取回到本地
adb shell screencap -p /sdcard/screen.png
adb pull /sdcard/screen.png -
图片分析
(1)根据棋子的颜色,取顶部和底部的特征像素点,在截图中进行匹配,找到棋子坐标
(2)由于目标物体不是在左上就是在右上,可以从上往下扫描,根据色差判断目标物体位置,其中又分为以下几种类型
(3)有靶点,即目标物体中心的白色圆点,则靶点中心为目标落点
(4)无靶点,但是纯色平面,或者规则平面,则平面中心为目标落点
(5)无靶点,又无纯色规则平面,但是左上和右上位置的斜率是固定的,可根据固定斜率的斜线和目标物体中心线的焦点计算落点
(6)计算棋子坐标和目标落点的距离
(7)距离×跳跃系数=按压屏幕的时间,不同分辨率的手机,跳跃系数也有所不同 -
通过adb命令,给手机模拟按压事件
adb shell input swipe x y x y time ```![这里写图片描述](https://img-blog.csdn.net/20180514141109138?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Z1a2FpbWVp/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 其中`x`和`y`是屏幕坐标,`time`是触摸时间,单位ms。
代码详解
这里将针对一些关键算法的代码进行解释。
寻找棋子位置
这样我们就得到了棋子的xy坐标,下面是相关代码:
/* 计算棋子位置 */
Pixel piece = new Pixel();
for (int i = TOP_BORDER; i < screenHeight - BOTTOM_BORDER; i++) {
int startX = 0;
int endX = 0;
for (int j = LEFT_BORDER; j < screenWidth - RIGHT_BORDER; j++) {
int red = Color.red(pixels[i][j].color);
int green = Color.green(pixels[i][j].color);
int blue = Color.blue(pixels[i][j].color);
if (50 < red && red < 55
&& 50 < green && green < 55
&& 55 < blue && blue < 65) { //棋子顶部颜色
//如果侦测到棋子相似颜色,记录下开始点
if (startX == 0) {
startX = j;
endX = 0;
}
} else if (endX == 0) {
//记录下结束点
endX = j;
if (endX - startX < PIECE_TOP_PIXELS) {
//规避井盖的BUG,像素点不够长,则重新计算
startX = 0;
endX = 0;
}
}
if (50 < red && red < 60
&& 55 < green && green < 65
&& 95 < blue && blue < 105) {//棋子底部的颜色
//最后探测到的颜色就是棋子的底部像素
piece.y = i;
}
}
if (startX != 0 && piece.x == 0) {
piece.x = (startX + endX) / 2;
}
}
//棋子纵坐标从底部边缘调整到底部中心
piece.y -= PIECE_BOTTOM_CENTER_SHIFT;
寻找靶点
所谓靶点,就是目标物体中心的那个小圆点。
那么我们只需要寻找颜色值为0xf5f5f5的色块就可以了,为了规避其他物体相近颜色干扰,我们可以限制色块的大小,正确大小的色块才是靶点。
但是如何计算色块的大小呢,色块最顶端到最底端y坐标的差值我们作为色块的高度,同理,最左侧到最右侧x坐标的差值作为宽度,我们只需要查找这四个顶点的坐标就可以了。
本来打算用凸包的Graham扫描算法,后来发现色块已经是凸包了,且边缘像素是连续的,那么我们按照一定顺序,遍历边缘像素,就可以在O(n^-2)的时间复杂度里,得到色块的顶点坐标了。
我们从第一个像素点开始:
/**
* 寻找色块顶点像素
*/
public static final Pixel[] findVertexs(Pixel[][] pixels, Pixel firstPixcel) {
Pixel[] vertexs = new Pixel[4];
Pixel topPixel = firstPixcel;
Pixel leftPixel = firstPixcel;
Pixel rightPixel = firstPixcel;
Pixel bottomPixel = firstPixcel;
Pixel currentPixcel = firstPixcel;
//先把坐标置于左上角
while (checkBorder(pixels, currentPixcel)//判断是否超出图像边缘
&& Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) {//判断是否是相同颜色
currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x];
}
while (checkBorder(pixels, currentPixcel)
&& Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) {
currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1];
}
//寻找上顶点像素
while (checkBorder(pixels, currentPixcel)) {
if (Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) {
currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x];
} else if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x + 1], firstPixcel)) {
currentPixcel = pixels[currentPixcel.y][currentPixcel.x + 1];
} else {
topPixel = findCenterPixcelHorizontal(pixels, currentPixcel);
break;
}
}
//寻找右顶点像素
while (checkBorder(pixels, currentPixcel)) {
if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x + 1], firstPixcel)) {
currentPixcel = pixels[currentPixcel.y][currentPixcel.x + 1];
} else if (Color.compareColor(pixels[currentPixcel.y + 1][currentPixcel.x], firstPixcel)) {
currentPixcel = pixels[currentPixcel.y + 1][currentPixcel.x];
} else {
rightPixel = findCenterPixcelVertial(pixels, currentPixcel);
break;
}
}
//寻找下顶点像素
while (checkBorder(pixels, currentPixcel)) {
if (Color.compareColor(pixels[currentPixcel.y + 1][currentPixcel.x], firstPixcel)) {
currentPixcel = pixels[currentPixcel.y + 1][currentPixcel.x];
} else if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) {
currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1];
} else {
bottomPixel = findCenterPixcelHorizontal(pixels, currentPixcel);
break;
}
}
//寻找左顶点像素
while (checkBorder(pixels, currentPixcel)) {
if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) {
currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1];
} else if (Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) {
currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x];
} else {
leftPixel = findCenterPixcelVertial(pixels, currentPixcel);
break;
}
}
vertexs[0] = leftPixel;
vertexs[1] = topPixel;
vertexs[2] = rightPixel;
vertexs[3] = bottomPixel;
return vertexs;
}
得到了四个坐标点,我们就可以计算色块的中点了,也就是目标落点。
对于没有靶点,但是落点是规则平面的,也可以用类似算法。
3. 斜率计算
对于没有靶点,又不是规则平面的,我们怎么计算落点呢,这时候就要用到斜率了。
可以看得出来,每次左上角或右上角出现的物体,针对当前物体的方向都是一样的,也就是两个物体中心的连线,斜率是固定的。
基本所有的目标物体,最顶点像素中点的x坐标,都是在物体中间,我们至少先得到了目标物体x坐标了,记为des.x ,接下来要求des.y 。
计算过程如下:
斜线的公式为 y=kx+b
那么,在棋子坐标上有 piece.y=k*piece.x+b
在目标落点坐标上有 des.y=k*des.x+b
代入得到 des.y=k*(des.x-piece.x)+piece.y
然而这种算法还是有偏差的。
##玩家刷分攻略
开始游戏跳一跳是先自己跳几下,然后在用USB连电脑运行刷分,不然会被腾讯那边检测为作弊的行为而上传不了最终的分数的。
##贴上本人刷的最高分截图