期末花了一天复习时间写了,调试了。觉得有点乱,今天重新写了一遍。
没学过图像处理,这里用的方法是纯的像素分析。比较low。
概述
控制部分:连接安卓机,adb截图,保存到本地,然后算出点击时间,adb来执行触屏。 这个都是一样的。
核心部分:找到人坐标和落点坐标。
核心(java实现)
1.寻找紫人的坐标
BufferedImage读取图片。getRGB获取每一个点的rgb值,紫人臀部我取的点是Color(227,108,76)。
直接搜索全图没有必要,太费时,一般手机都是1080*1920的,可以在一个范围内搜索,我取得搜索范围是Range(200, 1000, 900, 1300)。
(可以用颜色距离,跳着搜索,优化搜索加快搜索速度)
2.找到目标平台的最高点
就是这个点:
这个点这样找:
取Point(540, 650)这个点作为背景颜色的点,竖下来的y轴从650开始搜索。
如果紫人在540左边,即屏幕的左侧,则搜索范围就是Range(540,650,1080,1920)
否则就搜索左边,Range(0,650,540,1920)
在搜索范围内逐行搜索,如果遍历的点的颜色,和Point(540, 650)这个点颜色的距离(rgb差值的平方和的方根)小于30的就判断为背景。直到搜索到不是背景的点,就是要找的点。
例如:
这样搜索。
3.找到目标平台的最低点
从最高点开始,向下搜索相近的颜色。
相近是指遍历的点的颜色和最高点颜色距离小于10。
一行一行搜,直到刚才搜索的10行都没有相近的颜色时结束搜索。
每行搜索的横向范围,是最近搜索到有相邻颜色的那一行的最小横坐标-35到最大横坐标+35。
取最后搜索到的那一行的最左和最右点的中点,最为目标平台最低点。
例如:
搜索范围。
这样可以让某些颜色断开的面也能被正确找到最低点。
之前用的是广搜。这次重写还有一方面就是提升执行效率。
4.对一些特殊平台做特判
有些平台颜色很不规则。比如:
所以做个特判,如果颜色是xxx,就说明是xxx,然后最低点直接根据最高点进行位移。这个唱片的位移是(0,245)。
像这种奶瓶子。
会把下面的乳白色也搜索到,搜索跨越范围减小,别的有些又搜索不好,所以特判。
特判不能直接拿上面的白色作为特判点的颜色,因为还有别的平台最高点是白色的。
所以特判算法是从最高点开始下来的一列搜索特判颜色值。
所以这个奶瓶子的特判点的颜色我取的是Color(101, 135, 164)。下面的蓝色。
每次搜到最高点,先搜索这一列颜色,查找有没有特判颜色,有的话就直接加上位移,作为最低点。否则按步骤3来找最低点。
5.计算中心点
取最高点和最低点的中点,作为平台的中心点。
6.剩下的
根据紫人臀部点和平台中点,计算距离,根据距离线性计算按压时间。
其他
之前写的那个计算部分需要2,3秒,太慢了,我在旁边看的都等不及了。
现在这个计算部分差不多要0.1-0.2s左右。
真机调试,adb指令执行太慢,所以总的时间还是很长。一个截图就要2-3秒,pull发回手机又要1s左右。input swipe模拟点击也耗时。
用x86的安卓模拟器快一些。那个截图就快很多,1s不到。
这个游戏是有反外挂机制的。最初点击屏幕是固定点,直接分数无法上传,之后改成点击范围内随机点,不容易被检测为外挂。
然后现在貌似分数到了800分,我的程序都会被检测出来。在点击坐标点随机上下功夫,没用。在相邻点击时间间隔上做处理,也没用。然后现在想让它点的快一点,算法上还可以了,那些控制的部分还得想想别的办法。
这是模拟器跑的。
源码
Core.java
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Core {
final int WIDTH = 1080, HEIGHT = 1920;
final int HALF_WIDTH = 540, HALF_HEIGHT = 960;
BufferedImage src;
final int manRGB = new Color(54, 60, 102).getRGB();// 人臀部颜色
final Range manSearRange = new Range(200, 1000, 900, 1300);// 人的搜索范围
Point manPos;// 50ms
boolean inLeft;
Range topPosSearRange;
Point bgBasePos = new Point(540, 650);
Point topPos;// 目标平台最高点
final int StageColorDis=5;//平台颜色容差
final int leapDis = 10;// 颜色搜索最大跨越的距离
final int sxext = 35;// 搜索横向延伸距离
Point bottomPos;// 目标平台最低点
// 木椅子,WZC瓶子,唱片,xx,xx
final int[] scTopRGB = {
new Color(214, 179, 146).getRGB(),
new Color(101, 135, 164).getRGB(),
new Color(220, 200, 161).getRGB(),
new Color(136, 101, 99).getRGB(),
new Color(225, 199, 141).getRGB(),
new Color(247, 169, 85).getRGB(),
};
final Point[] scOffset = {
new Point(10, 186),
new Point(25, 50),
new Point(0, 245),
new Point(0, 175),
new Point(0, 175),
new Point(0, 212),
};
Point tarPos;// 目标落点
int distance;// 距离
void calc(String path) throws IOException {
src = ImageIO.read(new File(path));
// rotate();//旋转图片
searchMan();
searchTopPos();
searchBottomPos();
calcTarPos();
calcDis();
}
void rotate() throws IOException{
System.out.println(src.getWidth()+" "+src.getHeight());
BufferedImage o=new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
for(int i=0;i<HEIGHT;i++)
for(int j=0;j<WIDTH;j++){
int rgb=src.getRGB(i, j);
o.setRGB(j, HEIGHT-1-i, rgb);
}
src=o;
// ImageIO.write(src, "png", new File("1.png"));
}
void searchMan() {
boolean break_flag = false;
for (int x = manSearRange.x1; x < manSearRange.x2 && !break_flag; x++)
for (int y = manSearRange.y1; y < manSearRange.y2; y++)
if (src.getRGB(x, y) == manRGB) {
manPos = new Point(x, y);
break_flag = true;
break;
}
if (manPos.x < HALF_WIDTH)
inLeft = true;
else
inLeft = false;
}
void searchTopPos() {
if (inLeft) {// 搜索右侧
topPosSearRange = new Range(HALF_WIDTH, bgBasePos.y, WIDTH, HEIGHT);
} else {// 搜索左侧
topPosSearRange = new Range(0, bgBasePos.y, HALF_WIDTH, HEIGHT);
}
final int bgRGB = src.getRGB(bgBasePos.x, bgBasePos.y);
for (int y = topPosSearRange.y1; y < topPosSearRange.y2; y++) {
for (int x = topPosSearRange.x1; x < topPosSearRange.x2; x++) {
if (!ColorUtil.similar(src.getRGB(x, y), bgRGB, 30)) {// 不是背景色
topPos = new Point(x, y);
// markPoint(x,y);
return;
}
}
}
}
void searchBottomPos() {
if (specialCheck())
return;// 特判
int stageRGB = src.getRGB(topPos.x, topPos.y);
Point bpl = topPos, bpr = topPos;
for (int y = topPos.y; y < bpl.y + leapDis && y < HEIGHT; y++) {
int xl = bpl.x - sxext, xr = bpr.x + sxext;
if (xl < 0)
xl = 0;
if (xr > WIDTH)
xr = WIDTH;
for (int x = xl; x < xr; x++) {
if (ColorUtil.similar(src.getRGB(x, y), stageRGB, StageColorDis)) {
bpl = new Point(x, y);
break;
}
}
for (int x = xr - 1; x > xl; x--) {
if (ColorUtil.similar(src.getRGB(x, y), stageRGB, StageColorDis)) {
bpr = new Point(x, y);
break;
}
}
}
bottomPos = new Point((bpl.x + bpr.x) / 2, bpl.y);
// markPoint(topPos.x,topPos.y);
// markPoint(bottomPos.x,bottomPos.y);
}
boolean specialCheck() {//特判
for (int i = scTopRGB.length - 1; i >= 0; i--) {
for(int y=topPos.y;y<HEIGHT;y++){
if (src.getRGB(topPos.x, y) == scTopRGB[i]) {
bottomPos = new Point(topPos.x + scOffset[i].x, topPos.y + scOffset[i].y);
return true;
}
}
}
return false;
}
void calcTarPos() {
tarPos = new Point((topPos.x + bottomPos.x) / 2, (topPos.y + bottomPos.y) / 2);
// markPoint(tarPos.x, tarPos.y);
}
void calcDis() {
distance = manPos.distance(tarPos);
}
final int markSize = 3;
void markPoint(int x, int y) {//标记某个点,然后保存成图片,便于分析
for (int i = x - markSize; i <= x + markSize; i++) {
for (int j = y - markSize; j <= y + markSize; j++) {
src.setRGB(i, j, 1000);
}
}
}
void printImg(String fn) throws IOException {
ImageIO.write(src, "png", new File(fn));
}
}
class ColorUtil {
static boolean similar(int s1, int s2, int SD) {//颜色相似 颜色1 颜色2 距离
Color c1 = new Color(s1), c2 = new Color(s2);
int r1 = c1.getRed(), g1 = c1.getGreen(), b1 = c1.getBlue();
int r2 = c2.getRed(), g2 = c2.getGreen(), b2 = c2.getBlue();
int dis = (int) Math.sqrt(Math.pow(r1 - r2, 2) + Math.pow(g1 - g2, 2) + Math.pow(b1 - b2, 2));
// System.out.println("dis "+dis);
return dis < SD;
}
}
class Point {
int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public String toString() {
return this.x + " " + this.y;
}
int distance(Point p) {
return (int) Math.sqrt(Math.pow(x - p.x, 2) + Math.pow(y - p.y, 2));
}
}
class Range {
int x1, y1, x2, y2;
public Range(int x1, int y1, int x2, int y2) {//左上角 右下角
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
}
Other
Core c = new Core();
Runtime r = Runtime.getRuntime();
Process p;
p = r.exec("cmd /c adb shell /system/bin/screencap -p /sdcard/ss.png");//截图
p.waitFor();//等待命令执行完毕
p = r.exec("cmd /c adb pull /sdcard/ss.png " + path);//图片保存到本地
p.waitFor();
c.calc(path);//传入文件路径
int dis=c.distance;//获得距离
int pressTime = calcPressTime(c.distance);//计算点击时间
String pressPos = getPressPos();
p = r.exec("cmd /c adb shell input swipe " + pressPos + pressTime);//点击
p.waitFor();
static String getPressPos() {
double sx = 300 + rand.nextInt(100) + rand.nextDouble();
double sy = 1500 + rand.nextInt(100) + rand.nextDouble();
double ex = sx + rand.nextInt(30) + rand.nextFloat();
double ey = sy + rand.nextInt(30) + rand.nextFloat();
return sx + " " + sy + " " + ex + " " + ey + " ";
}
Thread.sleep(2000);//等待人跳完还有白色光圈消失
注:
我的点击时间是 1.32*距离+80 。有些时候不太好。
不知道这个是不是线性的。如果是线性的,距离近跳的比较远说明截距b偏大;如果距离远跳的偏近,说明斜率k偏小。
不知道这个距离是不是这样算的,能不能直接找某个紫色的点然后算距离。。
特判可能还缺两个。
等待2000ms可能会有点长,可以测试一下,缩短一点。
有些模拟器截图出来是横着的,要先转过来,不转就得改程序的核心部分了。