@Tyhj
2018-11-13T19:18:17.000000Z
字数 5247
阅读 2087
Android
原文链接:https://www.jianshu.com/p/1578c6e694b5
去年跳一跳很火,外挂也很火,当时自己也做了一个,跳个20来万分没问题。但是后来因为游戏一直更新,自己没时间去更新软件,而且之前的程序也没有写自动检测游戏结束和保存失败的情形的功能,所以现在基本上没法用了;最近游戏凉了,基本上不会再更新了,自己也是无聊,再彻底重新做一次
要做的是一个扔在一边就可以自己跳的软件,只需一个测试手机,不需要任何其他的辅助工具就可以跳出高分,定个小目标就100万分吧
整个流程应该是先计算棋子(黑色的那个跳来跳去的就叫它棋子吧)和方块(下一个棋子落点的地方就叫它方块吧)中心点之间的距离,然后通过一个公式转换成需要点击的时间,然后自动点击屏幕进行跳跃
最简单的距离计算可以通过两个悬浮窗来实现,拖动悬浮窗到指定位置就可以计算出距离;这是可行的,但是效率低,需要手动操作,分数无上限,但是人累
//在service里面开启悬浮框代码
btnView = new ImageView(getApplicationContext());
btnView.setImageResource(R.drawable.ic_star);
windowManager = (WindowManager) getApplicationContext()
.getSystemService(Context.WINDOW_SERVICE);
params = new WindowManager.LayoutParams();
// 设置Window Type
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
// 设置悬浮框不可触摸
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | FLAG_LAYOUT_INSET_DECOR;
// 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应
params.format = PixelFormat.RGBA_8888;
// 设置悬浮框的宽高
params.width = 200;
params.height = 200;
params.gravity = Gravity.TOP;
params.x = 300;
params.y = 200;
windowManager.addView(btnView, params);
图像识别就需要先截取图像再利图像识别工具,android上最简单的使用openCV来做,简单点可以通过openCV的模板匹配来实现;这个方法需要很多模板,而且精度不高,识别时间较长,几秒钟吧,只是简单写写的话分数上限比较低(几百)
//模板匹配实现代码
Mat template = Highgui.imread(templateFilePath1, Highgui.CV_LOAD_IMAGE_COLOR);
Mat source = Highgui.imread(originalFilePath, Highgui.CV_LOAD_IMAGE_COLOR);
//创建于原图相同的大小,储存匹配度
Mat result = Mat.zeros(source.rows() - template.rows() + 1, source.cols() - template.cols() + 1, CvType.CV_8UC1);
//调用模板匹配方法
Imgproc.matchTemplate(source, template, result, Imgproc.TM_CCOEFF_NORMED);
//获得最可能点,MinMaxLocResult是其数据格式,包括了最大、最小点的位置x、y
Core.MinMaxLocResult mlr = Core.minMaxLoc(result);
System.out.println("相似度:" + mlr.maxVal);
Point matchLoc = mlr.maxLoc;
//在原图上的对应模板可能位置画一个绿色矩形
Core.rectangle(source, matchLoc, new Point(matchLoc.x + template.width(), matchLoc.y + template.height()), new Scalar(0, 255, 0));
颜色识别就是利用游戏色彩简单的特点,只需要识别颜色并找到规律就可以实现计算两点之间的距离;需要考虑的情况比较多,精度、时间都取决于写的算法;分数上限取决于算法和手机可运行时间
int color = bitmap.getPixel(x, y)
自动点击,实现方法基本上就两种,第一是执行adb命令,可以连接电脑发送 adb命令实现,或者获取手机root权限,就可以本机实现了;一种是使用Accessbility辅助功能,但是其实它做不到触摸一段时间这个功能;
/**
* @param cmd 需要执行的adb命令
*/
public static void execShellCmd(String cmd) {
try {
if (process == null) {
process = Runtime.getRuntime().exec("su");
os = new DataOutputStream(process.getOutputStream());
}
os.writeBytes(cmd + "\n");
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
转换公式可以先通过网上的粗略的公式来多次测试、调整、计算,然后得到一个比较精确的公式
double k = (distence * (-0.00020) + 1.495);
if (k > 1.4165) {
k = 1.4165;
}
time = (int) (k * distence);
实现颜色识别➕获取root权限执行adb命令的方法
第一步是在Service中开启悬浮窗,因为我们需要先开启悬浮窗才可以到游戏运行界面开启程序,需要先获取权限,获取悬浮窗权限还比较麻烦
快速集成动态权限申请:Android动态权限申请工具(包括悬浮窗)
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
//获取悬浮窗权限
FloatWindowManager.getInstance().applyOrShowFloatWindow(MainActivity.this)
第一步是截取屏幕,有了root权限截图自然很简单,但是我运行的时候发现root命令的执行好像比较慢,而且先将截图保存到本地再读取出来比较慢一点,android5.0以后是自带截屏功能(全局)的,具体实现其实也不简单,需要开启录屏服务,然后去获取图片,类似开启相机预览,取出图片的感觉吧;
快速集成截屏:Android截屏、录屏工具
//开启录屏服务
ScreenRecordUtil.getInstance().screenShot(MainActivity.this, null);
//随时获取截图
Bitmap bitmap = ScreenRecordUtil.getInstance().getScreenShot();
颜色识别,其实就是看看两种颜色是不是一样或者相似,用LAB颜色空间算法,可以拿到两种颜色的差别有多大;
/**
* LAB颜色空间计算色差,基于人眼对颜色的感知,
* 可以表示人眼所能感受到的所有颜色。
* L表示明度,A表示红绿色差,B表示蓝黄色差
*/
public static int labAberration(int color1, int color2) {
int r1 = Color.red(color1); // 取高两位
int g1 = Color.green(color1);// 取中两位
int b1 = Color.blue(color1);// 取低两位
int r2 = Color.red(color2); // 取高两位
int g2 = Color.green(color2);// 取中两位
int b2 = Color.blue(color2);// 取低两位
int rmean = (r1 + r2) / 2;
int r = r1 - r2;
int g = g1 - g2;
int b = b1 - b2;
return (int) Math.sqrt((2 + rmean / 256) * (Math.pow(r, 2)) + 4 * (Math.pow(g, 2)) + (2 + (255 - rmean) / 256) * (Math.pow(b, 2)));
}
背景颜色是最重要的,因为大部分都是背景颜色,只要当前获取到的颜色和背景颜色不一致就可以知道应该是是进入方块了;可以从第一个点获取到背景颜色;可以注意到背景颜色基本上是一样的,稍微有一点点渐变,为了准确可以在每次获取颜色的时候更新背景颜色,使背景颜色更接近当前位置的背景;
棋子位置是位于屏幕的下方的,所以为了节省时间只截取Bitmap的相应比例部分进行识别;棋子是永远不变的,从上面取几个特征颜色点;从下边遍历像素点的颜色,和对比特征颜色就可以找到棋子的起跳点位置
棋子的长宽是不变的,找到棋子的位置后,我们将原bitmap上棋子所在的位置用背景颜色覆盖,这样可以避免之后识别的颜色干扰
假设第二张图片就是我们获取到的图片,我们要找到方块的中心就应该找到对角线,中心其实就是最左边的角和最右边的角的连线的中点;找到第一个顶点也很重要,这样可以知道方块的颜色,便于找其他点;而且当左右两个点有一个不准的时候,可以用顶点的X坐标和准的点的Y坐标来使用也可以;当两个点都不准的时候,直接使用顶点的X坐标和把Y坐标下移一点来使用也可以挽救一下
找顶点很简单,我们知道背景颜色,从上遍历下来只要颜色和背景颜色不一样就可以判定这是顶点了;
找第一个的点的时候可能会被一些元素影响,比如音符,这个比较好办,判断进入的这个颜色的长宽就可以排除,方块都是比较大的,影响元素都很小
方块是不是纯色可以在找到顶点后取一个小范围来判定是不是纯色
这个有很多方法,第一种是继续遍历下去,每次进入方块点的X坐标会减小,每次出方块点的X坐标会增加,到最小和最大就是要找的两边的点;这种方法可行,但是中间会出现很多特殊情况;
这次选择另一种方法,方块左右两边和水平线的角度是固定的,斜率double k = 0.5773
,利用角度和进入坐标可以生成一个直线的方程,按照这个方程的点往下找,当获取到和背景颜色一样或者和方块的颜色不一样的点就认为是左右两边要找的点了;
方块的颜色不是纯色的,不是纯色,第一个点还是能找准,找左右点的时候以颜色和背景颜色不一样为出去的点就好了,这样当左右两边(一般只会有一边)有方块的颜色连在一起的时候就会有一边的位置找不准,这时候根据两个点和顶点连线的距离判定,太远或者太近都舍弃;
当方块中只有一点线段或者中间是其他颜色的话,取一定的范围判断颜色是判断不出来不是纯色的,这时候可以采取尝试跳过不一样的颜色再次回到方块的颜色继续计算;
首先是方块形状,有的是圆形的,形状其实不影响上面的做法;
其实一般都会跳到中心,这时候中心是会出现一个白点的,在我们找到中心点的时候,如果点的颜色不是白色可以找找周围有没有白色,有的话再找到白点的中心,作为方块的中心
遍历颜色没有必要一个一个像素遍历,可以先快速遍历,找到不同点以后再回过头精确的遍历
其实识别的时间很短,200ms以内,用adb执行点击需要的时间很长,可能就一秒左右吧,很慢了,然后跳一次后会产生一些波纹,需要等到波纹消失后才可以截图,这些都是需要等待的地方;
在此基础上其实还会遇到很多特殊的情况,难复现,而且也不可能一直盯着手机;所以自动开始、保存失败时的截图、崩溃日志都是很重要的东西;
快速集成异常捕获:Android捕获Crash信息简单封装
CrashHander.getInstance().init(this, new ISaveErro() {
@Override
public void saveErroMsg(Throwable throwable) {
}
});
现在的版本大概每个小时可以跳2.4万,上次优化以后,只跳了一次就跳到24万分,但是崩溃只保存了日志和截图,没保留之崩溃前的几张有用的截图,所以原因没找到,这样优化下去感觉手机不卡可以跳到地老天荒;