工具: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 }
---恢复内容结束---