应用环境
android
手机,需要启用开发者模式。- 电脑端的代码采用
matlab
编写。 - 手机端和电脑端使用
usb
数据线连接。 - 电脑端通过
adb
命令向手机发送相应的命令。 - 苹果手机不支持(越狱的话也许可以,不过需要找到相应的 adb 命令)
本人手机为中兴BA910,屏幕分辨率 1280∗720 ,屏幕尺寸为 5.1 英寸,其他分辨率以及尺寸的手机需要修改相应的代码参数以进行适配。
完整的代码我放在了github上了,github地址:
https://github.com/lpstudy/jump-wechat-game
请注意:
上述的代码是我一个下午加上晚上前2个小时所写,包括想法,设计,代码调试,以及游戏测试,因此我基本上是以 C 的语法在写matlab,代码实在是丑陋不堪,自己由于科研方面的压力也没有精力去完善它,甚至于优化它。真的请见谅,我只想记录一下自己当时的想法,也希望能够给其他想做这个的一点哪怕小小的帮助,我就很满足了。
游戏的效果(跑了几十分钟,得了4096分,程序依然在运行,不得已手动挂掉它):
背景介绍
大约几天以前,听说微信更新了版本,小程序中增加了一个小游戏叫“跳一跳”,于是乎,我也更新了一个,打开玩了玩,一直都是
游戏界面大概是这个样子的:
游戏规则
游戏的过程就是通过按住屏幕,使得立着的那个小人距离向目标块上跳跃,如果跳不到目标方块上,那么游戏就失败了,分数是根据小人跳跃的块数逐步累加的。游戏者按住屏幕的时间越长,小人跳跃的越远,我们在游戏中要根据小人距离目标块的距离,来控制按住屏幕的时间,以尽可能的跳跃到目标块的中心,这样不仅有额外的加分,还因为有些目标块很小,不在中心的话,很容易掉下来。
我当时想着操作那么简单,是不是可能能够借助电脑帮我弄呢?
我的一些思路
连接手机
adb命令
既然要用电脑去分析,那么首先需要能够通过电脑给手机发送按压屏幕的指令,我原来简单玩过android,因此知道有个adb的东东,可以连接到手机端的shell,进行各种各样的操作。我于是下载了adb,并把它加入到windows 系统的环境变量里面,这样就可以通过命令行使用adb命令了。
adb命令的一个帮助界面:
查看手机设备是否连接
当adb命令可以正常使用后,还需要将手机通过usb数据线与电脑连接起来,手机端需要启用开发者模式,开启usb调试选项,电脑端需要安装手机的驱动程序(我一般都是直接安装一个手机管家之类的电脑端软件,只要它能够识别手机,那么驱动程序可认为已经安装OK了),这个时候使用以下命令,查看设备:
adb devices
如果你的手机正常连接手机,那么会看到有一行设备,如图
与游戏相关的adb指令
经过搜索之后,发现下面的两个关键命令
截取屏幕
adb shell screencap -p /sdcard/screen.png
上述命令截取屏幕并保存到sdcard中,可以使用
adb pull /sdcard/screen.png
来将图片下载到电脑中触摸屏幕命令
adb shell input swipe 100 500 100 500 ms
其中的ms表示触摸屏幕的毫秒数,这个在判断好距离之后,得到相应的ms数,就可以向手机发送触摸屏幕命令。
程序思路框架
截取手机屏幕
利用上述的adb命令将手机屏幕的图片截取下来,这样就可以放在电脑上分析一下当前图片,为了方便,我写了一个bat脚本来对手机进行截屏,并将图片下载到电脑端。
@echo off
:: capture screen
adb shell screencap -p /sdcard/%1
adb pull /sdcard/%1
将上述代码命名为capture.bat,然后就可以在cmd命令下执行它了,如下图:
图中的命令表示将手机端的屏幕图片下载到电脑端,并保存为1.png
。
处理图片
当拿到游戏屏幕后,我的目标分为两个步骤,(1)是计算出小人距离目标方块的距离,即dis;(2)根据距离dis来计算出要触摸的毫秒数。
由于步骤1和2是整个工作的核心内容,因此单列2个大节分别讲述。
执行触摸命令
根据前面步骤计算出的毫秒数,假定为666ms,然后执行触屏命令:
adb shell input swipe 100 500 100 500 666
这样手机端就像手按压屏幕一样,小人开始发起跳跃。
%向手机发送触摸屏幕指定时间y的命令,并停顿1s
command = ['adb shell input swipe 100 500 100 500', ' ', num2str(round(y))];
fprintf('%s\n', command);
system(command);
pause(1)
图片分析核心步骤
计算出小人距离目标方块的距离
为了计算出小人距离目标方块的距离,需要两个要素,一个是小人的位置source,一个是目标方块的位置target。在知道两个要素的坐标之后,可以直接使用两个点的距离公式代入计算(target-source)^2
。
图片预处理之边缘检测-图片二值化
屏幕截取的图片是RGB的,不仅计算量很大,还复杂,因此首先将其灰度化,然后寻找其边缘,这样不仅寻找目标简单,而且整个图片都可以转换为一个二值图片。
下图是原始图片和边缘化之后的对比图:
可以看出这个出来的边缘还是非常清晰的,为了分析的方便,我将此图的上面的分数部分和下面的一段切掉,如下图所示:
这样看起来是不是很清楚了,有小人,有目标方块,你肯定想问我,别说了,代码拿回来(其实我想说,代码我是从网上随便找的一份,简单修改了一下,已经不知道它的出处了)
filename = 'ori_2.png';
ori=imread(filename);
ori = specialcase(ori);
ori=rgb2gray(ori);
%转化成灰度图
ori=im2double(ori);
%函数im2double
%使用垂直Sobcl箅子.自动选择阈值
[VSFAT Threshold]=edge(ori, 'sobel','vertical');
%边缘探测
f=edge(ori,'sobel',Threshold/6);
f = f(400:1000, :);
imwrite(f, strcat('result\edge_cut', filename))
上述的代码自动选择阈值Threshold
,用来分辨边缘,但是我实践下来感觉分辨率还不够,因此我随便写了一个Threshold/6
作为阈值,进行边缘检测,随后使用400:1000
进行切割图片(这个值你可能需要改动,因为我的屏幕分辨率是1280*720
的,因此可以这样写死)。
上面的代码还调用了一个specialcase
的函数,这主要是因为有两种颜色的盒子与背景很相似,每次都不能正确找到边缘。对于这两个类型颜色的盒子,我直接暴力修改它的颜色,specialcase的代码如下:
function [f] = specialcase(f)
[a,b,c] = size(f);
for i = 1:a
for j = 1:b
if f(i,j, 1) == 255 && f(i,j,2) == 238 && f(i,j,3) == 97 || f(i,j, 1) == 186 && f(i,j,2) == 240 && f(i,j,3) == 68
f(i,j,1) = 100;
f(i,j,2) = 149;
f(i,j,3) = 105;
end
end
end
end
小人的位置
经过上述预处理之后的图片,就是二值图片了。小人就是一个轮廓而已。为了寻找它在图片中的位置,我采用了模式匹配的思想,首先挖出几个小人图片作为模板,然后利用图片滑动思想,将小人图片在大图片中滑动,逐个对比小人图片与大图片相应的方框,查看其相似度,如果相似的话,那么就找到了大图片中的小人了。
小人模板图片:
大图的其中几个框:
事实上每个框的size都和小人是完全相同的(我是手画的,可能看起来不完全一样),小人图片如上图所示,在上图中逐个像素滑动。滑动到一个方框后就比较小人和方框的相似度。
假定小人图片为 p ,大图中的一个框为