利用电脑玩Android版“天天连萌”刷高分(四)——模拟按键及程序优化
程序员文章站
2022-06-07 21:39:41
...
这一系列文章,没想到从去年10月份以来,写了三篇我就忘了写了,现在才想起来,所以一不小心就成了跨年系列文章了。
第四篇主要是写一下如何进行模拟按键,以及对程序的一些优化以使到分数更容易达到更高的分。
时间一段时间了,毕竟是去年在写的文章,都忘了原来项目的代码了。
模拟发送按键消息到手机,一开始百度到的是使用monkeyrunner.jar包里的api,但是该相关的api,在貌似4.0版本之后就改动了,构造方法要传进两个我不知道要传什么的参数。所以在这里,我使用了sdk里面的另外的API,即chimpchat.jar包里的api。
进行模拟按键,需要获取一个IChimpDevice对象,获取的方法如下:
IChimpDevice有以下主要的API:
还有其他一些方法,在此不一一列举了。
庆幸的是,在天天连萌中,需要模拟的事件还是挺简单的,只是触摸,也就是用了其中的mChimpDevice.touch(int, int, ToushPressType)方法。
在前面的文章中,我们已经继续出需要触摸的元素在数组中的位置,再根据已经知道的边距,以及每个元素所占的宽高,我们可以继续出它在屏幕当中的位置。但是需要注意的是,前面截屏,获取到的图像是竖屏的,我们进行处理过程中,也一直都是用竖屏的。但是在该游戏里模拟按钮,使用的却是横屏下的坐标,所以对于传过来的元素的位置,我们还需要进行转换。代码如下:
然后再在我们的Main.java中,进行整个游戏的过程。先写一个循环,在循环中先截图,然后设置数据,然后进行路径搜索,最后将搜索到的坐标传给模拟按键的方法进行模拟消除。main方法代码如下:
程序流程基本如上。接下来说一下如何优化。
实际上,电脑将图像转化为数组并进行路径搜索的过程是很快的,只需要几十毫秒。所以当截完一张图之后,电脑很快就计算完成并进行按键模拟。但是手机上接收按键信息并处理,游戏的方块消除及消除的动画的显示都需要时间和处理器。所以当电脑的整个过程太快时,会造成手机画面卡,反而影响下一次的截图。并且下一次的截图通常都是带着许多消除动画的,影响图像识别及转化。所以需要在触摸事件中加上延迟。在我的手机中,测试结果发现15毫秒到30毫秒比较合适(关掉手机中的声音播放,降低分辨率及帧数等都有利于使电脑上的按键延迟设置得更小)。具体设置多少看手机。
另外,这里使用的截图方法,截取一张图需要1200毫秒左右,这时间还包括了从手机传输截屏数据到电脑的时间。如果开启后台线程,不断地进行截图,便可以将平均截图时间减少。同样以我的手机为例,测试到以3个线程最为合适。另外,main方法中我也没再写做一个死循环,考虑到一次游戏结束之后,将不会再消除成功,所以当5秒没有任何消除时即认为游戏结束,退出循环。所以最后修改Main.java代码如下:
第四篇主要是写一下如何进行模拟按键,以及对程序的一些优化以使到分数更容易达到更高的分。
时间一段时间了,毕竟是去年在写的文章,都忘了原来项目的代码了。
模拟发送按键消息到手机,一开始百度到的是使用monkeyrunner.jar包里的api,但是该相关的api,在貌似4.0版本之后就改动了,构造方法要传进两个我不知道要传什么的参数。所以在这里,我使用了sdk里面的另外的API,即chimpchat.jar包里的api。
进行模拟按键,需要获取一个IChimpDevice对象,获取的方法如下:
AdbBackend adbBack = new AdbBackend(); IChimpDevice mChimpDevice = adbBack.waitForConnection();
IChimpDevice有以下主要的API:
// 获取各层级的view以便查询view的状态。 HierarchyViewer getHierarchyViewer(); // 返回一个ChimpManager对象。 ChimpManager getManager(); // 获取设备的属性 String getProperty(String key); // 获取所有我们能获取的设备属性 Collection<String> getPropertyList(); // 获取系统的属性 String getSystemProperty(String key); // 安装指定的程序 boolean installPackage(String path); // 运行指定的程序。 Map<String, Object> instrument(String packageName, Map<String, Object> args); // 删除指定的程序 boolean removePackage(String packageName); // 执行shell命令 String shell(String cmd); // 发送广播 void broadcastIntent(@Nullable String uri, @Nullable String action, @Nullable String data, @Nullable String mimeType, Collection<String> categories, Map<String, Object> extras, @Nullable String component, int flags); // 释放资源 void dispose(); // 拖动 void drag(int startx, int starty, int endx, int endy, int steps, long ms); // 按下 void press(String keyName, TouchPressType type); // 重启设备 void reboot(@Nullable String into); // 启动一个Activity void startActivity(@Nullable String uri, @Nullable String action, @Nullable String data, @Nullable String mimeType, Collection<String> categories, Map<String, Object> extras, @Nullable String component, int flags); // 截图 IChimpImage takeSnapshot(); // 触摸 void touch(int x, int y, TouchPressType type); // 打字输入 void type(String string);
还有其他一些方法,在此不一一列举了。
庆幸的是,在天天连萌中,需要模拟的事件还是挺简单的,只是触摸,也就是用了其中的mChimpDevice.touch(int, int, ToushPressType)方法。
在前面的文章中,我们已经继续出需要触摸的元素在数组中的位置,再根据已经知道的边距,以及每个元素所占的宽高,我们可以继续出它在屏幕当中的位置。但是需要注意的是,前面截屏,获取到的图像是竖屏的,我们进行处理过程中,也一直都是用竖屏的。但是在该游戏里模拟按钮,使用的却是横屏下的坐标,所以对于传过来的元素的位置,我们还需要进行转换。代码如下:
/** * 触摸 * * @param p 在数组中的横、纵坐标位置。 * @throws InterruptedException */ public void touch(Point p) throws InterruptedException { // 截图使用的是竖屏,这里触摸使用的是横屏 int x = PADDING_TOP + (p.x - 1) * IMAGE_HEIGHT + CORNER_HEIGHT; int y = 480 - (PADDING_LEFT + (p.y - 1) * IMAGE_WIDTH + CORNER_WIDTH); mChimpDevice.touch(x, y, TouchPressType.DOWN_AND_UP); }
然后再在我们的Main.java中,进行整个游戏的过程。先写一个循环,在循环中先截图,然后设置数据,然后进行路径搜索,最后将搜索到的坐标传给模拟按键的方法进行模拟消除。main方法代码如下:
while (true) { img = robot.snapshot(); robot.setNum(img); robot.startSearch(); }
程序流程基本如上。接下来说一下如何优化。
实际上,电脑将图像转化为数组并进行路径搜索的过程是很快的,只需要几十毫秒。所以当截完一张图之后,电脑很快就计算完成并进行按键模拟。但是手机上接收按键信息并处理,游戏的方块消除及消除的动画的显示都需要时间和处理器。所以当电脑的整个过程太快时,会造成手机画面卡,反而影响下一次的截图。并且下一次的截图通常都是带着许多消除动画的,影响图像识别及转化。所以需要在触摸事件中加上延迟。在我的手机中,测试结果发现15毫秒到30毫秒比较合适(关掉手机中的声音播放,降低分辨率及帧数等都有利于使电脑上的按键延迟设置得更小)。具体设置多少看手机。
另外,这里使用的截图方法,截取一张图需要1200毫秒左右,这时间还包括了从手机传输截屏数据到电脑的时间。如果开启后台线程,不断地进行截图,便可以将平均截图时间减少。同样以我的手机为例,测试到以3个线程最为合适。另外,main方法中我也没再写做一个死循环,考虑到一次游戏结束之后,将不会再消除成功,所以当5秒没有任何消除时即认为游戏结束,退出循环。所以最后修改Main.java代码如下:
public class Main { private static BufferedImage img = null; private static Executor executors = Executors.newCachedThreadPool(); private static boolean isOver = false; public static void main(String[] args) throws IOException, InterruptedException { final Robot robot = new Robot(); final long startTime = System.currentTimeMillis(); new Thread() { public void run() { try { while (!isOver) { executors.execute(new Runnable() { @Override public void run() { img = robot.snapshot(); } }); Thread.sleep(350); } } catch (InterruptedException e) { e.printStackTrace(); } }; }.start(); BufferedImage preImage = null; long lastClearTime = System.currentTimeMillis(); while (System.currentTimeMillis() - lastClearTime < 5000 || System.currentTimeMillis() - startTime < 60000) { long snapTime = System.currentTimeMillis(); while (img == null || img == preImage) { Thread.sleep(50); } preImage = img; System.out.println("snapTime:" + (System.currentTimeMillis() - snapTime)); robot.setNum(img); if (robot.startSearch()) { lastClearTime = System.currentTimeMillis(); } System.out.println("playTime:" + (System.currentTimeMillis() - snapTime)); } System.out.println("is over"); isOver = true; System.exit(0); } }