工具:ADB
原理:
- 开始游戏后,使用ADB工具让手机截屏发送到电脑
- 分析图像中小人与目标中心点间的距离,根据一定比例计算出需要触屏的时间
- 使用ADB进行模拟点击(触屏)相应的时间,完成精准跳跃
程序代码:
1 import java.awt.EventQueue; 2 import java.awt.Graphics; 3 4 import javax.swing.JFrame; 5 import javax.swing.JLabel; 6 import java.awt.BorderLayout; 7 import java.awt.Color; 8 9 import javax.imageio.ImageIO; 10 import javax.swing.ImageIcon; 11 import java.awt.event.MouseAdapter; 12 import java.awt.event.MouseEvent; 13 import java.awt.image.BufferedImage; 14 import java.io.File; 15 import java.io.IOException; 16 import java.math.BigDecimal; 17 18 public class Skip { 19 /* 20 * 以下参数根据手机配置自行调整: 21 */ 22 private final int time = 2800; //执行跳跃间隔时间(单位:ms) 23 private final double scale = 2.04; //用于计算按下屏幕时间的比例,触屏时间=距离*scale 24 25 private JFrame frame; //窗体 26 private JLabel lblNewLabel; //用于显示图片的Label 27 private BufferedImage image; //手机截屏 28 private int skipTime = 0; //跳跃次数 29 /** 30 * Launch the application. 31 */ 32 public static void main(String[] args) { 33 EventQueue.invokeLater(new Runnable() { 34 public void run() { 35 try { 36 Skip window = new Skip(); //跳一跳类实例 37 window.frame.setVisible(true); //设置窗体可见 38 } catch (Exception e) { 39 e.printStackTrace(); 40 } 41 } 42 }); 43 } 44 45 /** 46 * Create the application. 47 */ 48 public Skip() { 49 initialize(); //初始化 50 } 51 52 /** 53 * Initialize the contents of the frame. 54 */ 55 private void initialize() { 56 image = Control.getScreen(); //截取第一个图片 57 Control.getFoot(image); //寻找计算小人位置 58 Control.getTarget(image); //寻找目标中心 59 ImageIcon imageIcon = new ImageIcon(image); //以截图创建一个ImageIcon对象,供标签使用 60 61 frame = new JFrame(); //创建一个窗体 62 frame.setBounds(100, 0, imageIcon.getIconWidth(), imageIcon.getIconHeight()); //设置窗体大小位置 63 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //关闭窗体时结束进程 64 65 lblNewLabel = new JLabel("手机屏幕"); //创建标签 66 //给标签添加单机时间:单机后开始自动跳跃 67 lblNewLabel.addMouseListener(new MouseAdapter() { 68 @Override 69 public void mouseClicked(MouseEvent e) { 70 Thread skip = new Thread(new SkipRun()); 71 skip.start(); 72 } 73 }); 74 lblNewLabel.setIcon(imageIcon); //设置标签图片 75 frame.getContentPane().add(lblNewLabel, BorderLayout.CENTER); //添加标签到窗体中 76 } 77 78 //单步跳跃 79 protected void skip() { 80 double distance = Control.getDistance(image); //计算距离 81 Control.mouseon(Integer.parseInt(new BigDecimal(String.valueOf(distance * scale)).setScale(0, BigDecimal.ROUND_HALF_UP).toString())); //模拟触屏 82 try { 83 //记录本次图片 84 ImageIO.write(image, "png", new File("debug" + skipTime++ + ".png")); 85 } catch (IOException e) { 86 e.printStackTrace(); 87 } 88 } 89 90 91 //自动跳跃主线程 92 class SkipRun implements Runnable{ 93 94 @Override 95 public void run() { 96 //循环跳跃 97 while(true) { 98 skip(); 99 try { 100 //小人跳跃需要花一定时间,在此等待3秒 101 Thread.sleep(time); 102 } catch (InterruptedException e1) { 103 e1.printStackTrace(); 104 } 105 //将当前手机屏幕显示在窗体中 106 image = Control.getScreen(); 107 lblNewLabel.setIcon(new ImageIcon(image)); 108 } 109 } 110 111 } 112 113 } 114 115 /** 116 * 工具类,负责发送ADB命令、计算小人与目标距离 117 */ 118 class Control { 119 private static int footX = 0, footY = 0; //小人底部坐标 120 private static int targetX = 0,targetY = 0; //目标方块中心坐标 121 //获取截屏 122 public static BufferedImage getScreen(){ 123 //截屏 124 try { 125 //手机截屏,存储到SD卡 126 Process process = Runtime.getRuntime().exec("adb shell screencap /sdcard/screen.png"); 127 process.waitFor(); 128 //将截图传到电脑,以便接下来的分析 129 process = Runtime.getRuntime().exec("adb pull /sdcard/screen.png screen.png"); 130 process.waitFor(); 131 } catch (IOException | InterruptedException e) { 132 //异常处理 133 e.printStackTrace(); 134 } 135 //加载文件 136 File file = new File("screen.png"); 137 if (file == null || !file.exists()) { 138 System.out.println("获取截屏失败"); 139 return null; 140 } 141 142 //加载图片 143 BufferedImage image = null; 144 try { 145 image = ImageIO.read(file); 146 } catch (IOException e) { 147 e.printStackTrace(); 148 } 149 return image; 150 } 151 152 //长按屏幕 153 public static void mouseon(int time) { 154 try { 155 Runtime.getRuntime().exec("adb shell input swipe 200 200 200 200 " + time); 156 } catch (IOException e) { 157 // TODO Auto-generated catch block 158 e.printStackTrace(); 159 } 160 } 161 162 //计算距离 163 public static double getDistance (BufferedImage image) { 164 getFoot(image); //获取小人坐标 165 getTarget(image); //获取目标坐标 166 return Math.sqrt((footX - targetX) * (footX - targetX) + (footY - targetY) * (footY - targetY)); 167 } 168 169 public static void getFoot(BufferedImage image){ 170 171 Color footColor = new Color(54,60,102); //小人底部颜色 172 int top = (int)(image.getHeight() * 0.5); //扫描范围顶部(X) 173 int bottom = (int) (image.getHeight() * 0.7); //扫描范围底部(X) 174 /* 175 * 自底向上扫描小人脚下位置 176 */ 177 for(int i = bottom;i > top;i--) 178 { 179 for(int j = 0;j < image.getWidth();j++) 180 { 181 //如果当前颜色与小人脚部颜色相近 182 if(Math.abs(image.getRGB(j, i) - footColor.getRGB()) < 200) { 183 footX = j; 184 footY = i - 10; //自左向右扫描,原始结果会偏左,在此稍向右移 185 i = top; //结束外层循环 186 break; 187 } 188 } 189 } 190 /* 191 * 用蓝色方块标记找到的小人位置 192 */ 193 Graphics g = image.getGraphics(); 194 g.setColor(Color.BLUE); 195 g.fillRect(footX - 5, footY - 5, 10, 10); 196 } 197 198 public static void getTarget(BufferedImage image) { 199 200 /* 201 * 第一步,找到目标方块上端顶点,该顶点的X坐标即中心的X坐标 202 */ 203 204 int top = (int)(image.getHeight() * 0.35); //扫描范围顶部 205 int bottom = (int)(image.getHeight() * 0.49); //扫描范围底部 206 Color headColor = new Color(56, 54, 71); 207 /* 208 * 自顶向下扫描目标方块顶端位置 209 */ 210 for(int i = top;i < bottom;i++) 211 { 212 Color bgColor = new Color(image.getRGB(10, i)); //取背景色 213 for(int j = 0;j < image.getWidth();j++) 214 { 215 /* 216 * 小人可能高于目标方块,为排除干扰,略过小人所在的纵坐标(列) 217 */ 218 if (j >= footX - 30 && j <= footX + 30) { 219 continue; 220 } 221 Color color = new Color(image.getRGB(j, i)); //取当前颜色 222 int t = 0; 223 t = Math.abs(bgColor.getRGB() - color.getRGB()); //计算色差 224 225 if (t > 200000){ //如果与背景色不同 226 targetX = j; //记录行坐标 227 targetY = i; //记录纵坐标 228 i = bottom; //结束外层循环 229 break; //结束内层循环 230 } 231 } 232 } 233 234 /* 235 * 第二步,从顶点开始,向下扫描,找到方块占据最多列的那一行,即为目标的Y坐标 236 */ 237 238 int maxLength; //方块的直径 239 int x = targetX; //行扫描起始坐标 240 int y = targetY; //直径所在行(y坐标) 241 242 /* 243 * 如果在下面多行,该方块占据的列都相同,说明本行是中心所在行, 244 * 即Y坐标就是本行。使用flag记录连续相同的行数 245 */ 246 247 int flag = 0; //不满足条件标志 248 for(int i = y + 1;i < y + 101 && flag < 8;i++) //当连续8行直径相同后,结束循环 249 { 250 for(int j = x;j < image.getWidth();j++) 251 { 252 Color bgColor = new Color(image.getRGB(image.getWidth() - 10, i)); //取背景色 253 Color color = new Color(image.getRGB(j, i)); //取当前颜色 254 if (( Math.abs(bgColor.getRGB() - color.getRGB())) <= 703400) { //如果与背景色颜色相近 255 if (j > x) { //当前x坐标大于之前的x,(说明方块在本行占据的列更多) 256 x = j; //当前x坐标赋值给x 257 targetY = i; //直径所在行替换为当前y 258 flag = 0; 259 }else { //当前x坐标不大于之前的x,(说明方块在本行占据的列不是最多) 260 flag++; //此标志+1 261 } 262 break; 263 } 264 } 265 } 266 targetY = targetY - flag; //减去多加的flag 267 268 /*至此,targetX与targetY寻找完毕*/ 269 /* 270 * 用红色方块记录目标点 271 */ 272 Graphics g = image.getGraphics(); 273 g.setColor(Color.RED); 274 g.fillRect(targetX - 5, targetY - 5, 10, 10); 275 } 276 }
---恢复内容结束---