微信小游戏之跳一跳-电脑自动跳跃

应用环境

  • android手机,需要启用开发者模式。
  • 电脑端的代码采用matlab编写。
  • 手机端和电脑端使用usb数据线连接。
  • 电脑端通过adb命令向手机发送相应的命令。
  • 苹果手机不支持(越狱的话也许可以,不过需要找到相应的 adb 命令)

本人手机为中兴BA910,屏幕分辨率 1280720 ,屏幕尺寸为 5.1 英寸,其他分辨率以及尺寸的手机需要修改相应的代码参数以进行适配。

完整的代码我放在了github上了,github地址:
https://github.com/lpstudy/jump-wechat-game

请注意:
上述的代码是我一个下午加上晚上前2个小时所写,包括想法,设计,代码调试,以及游戏测试,因此我基本上是以 C 的语法在写matlab,代码实在是丑陋不堪,自己由于科研方面的压力也没有精力去完善它,甚至于优化它。真的请见谅,我只想记录一下自己当时的想法,也希望能够给其他想做这个的一点哪怕小小的帮助,我就很满足了。

游戏的效果(跑了几十分钟,得了4096分,程序依然在运行,不得已手动挂掉它):

背景介绍

大约几天以前,听说微信更新了版本,小程序中增加了一个小游戏叫“跳一跳”,于是乎,我也更新了一个,打开玩了玩,一直都是 100 分左右,那叫个难受。

游戏界面大概是这个样子的:




游戏规则

游戏的过程就是通过按住屏幕,使得立着的那个小人距离向目标块上跳跃,如果跳不到目标方块上,那么游戏就失败了,分数是根据小人跳跃的块数逐步累加的。游戏者按住屏幕的时间越长,小人跳跃的越远,我们在游戏中要根据小人距离目标块的距离,来控制按住屏幕的时间,以尽可能的跳跃到目标块的中心,这样不仅有额外的加分,还因为有些目标块很小,不在中心的话,很容易掉下来。
我当时想着操作那么简单,是不是可能能够借助电脑帮我弄呢?

我的一些思路

连接手机

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 ,大图中的一个框为 s p 中黑色点的个数为 total ,且黑色点对应的索引位置为 index s(index) 为白色的点个数为 cur ,那么相似度 ratio 记为 cur/total ,也就是说以匹配小人黑色点的比率作为度量。大图中的某一个小框对应 index 位置的白色点与小人中黑色点匹配的越多,则越相似。我简单实验了一下,使用了 0.5 作为阈值,即 ratio>0.5 表明此小框就是小人。

有可能一个小人模板在整个图片中都找不到 ratio>0.5 的方框,因此我切割出了 4 的不同的小人作为模板。

 %使用多个模板进行匹配,直到有一个匹配成功
    res_i = 0;
    res_j = 0;
    finish = 0;
    for sample = 1:4
        if finish == 1
            break 
        end
        %template person
        t = imread(['samples\persons\', num2str(sample), '.bmp']);
        [a, b] = find( t == 0); %find the edge
        t = t(min(a):max(a), min(b):max(b));
        [m,n] = size(t);
        [row, col] = find(t==0);

        for i = 100:M-m
            if finish == 1
                break 
            end
            for j = 1:N-n
                if finish == 1
                    break 
                end
                total = length(row);
                cur = 0;
                for k = 1:total
                    if f(i+row(k), j+col(k)) == 1
                        cur = cur + 1;
                    end
                end
                ratio = cur * 1.0/total;
                if(ratio > 0.5)
                    fprintf('ratio=%.2f, i=%d, j=%d, filename=%s', cur * 1.0 / total, i, j, filename)
                    res_i = i;
                    res_j = j;
                    finish = 1;
                end
            end
        end
    end

    if finish == 0
    i = randi(3000000);
        fprintf('I can not find the little person, please refer the hardpics directory to check %d.png\n', i)
        figure(3), imshow(f), title('f.png')
        imwrite(f, strcat('hardpics\', filename));
        return
    end

代码中使用了多个模板,与大图中逐个进行框,计算出 cur total ,如果 ratio>0.5 ,程序就停下来, finish=1 ,这个时候我们得到小人的左上角的位置为 res_i,res_j 。如果找不到小人,程序会直接退出,并保存图片到指定位置,以便于二次检查(跑了很多个小时,从没有碰到过找不到小人的情况)。

计算目标方块的位置

我们知道小人是向左上或者右上跳跃的,也就是说,肯定是向上跳跃的,因此,从上向下逐行扫描找到大图中第一个白色的点,那就是目标方块的上边角,然后将其向下平移几十个像素,就基本到达它的中心位置了。

它的原始大图如下图所示:

基于以上思路,先寻找它的上边角。我们知道小人的头有点高,有可能它的头有可能超过目标方块的上边角,因此先把小人扣出去。

%remove the little person, in case it made some mistakes
remove_f = f;
remove_f(res_i:res_i+m, res_j:res_j+n) = 0;

在扣除小人后,我们得到remove_f如下图所示:

然后计算它的目标方块的上边角,就是上图中圆柱体最上边的那个点(我用白色的三角形进行了标记)。

% get top i and top j
[x, y] = find(remove_f == 1);
top_i = min(x); 
top_j = round(mean(find(remove_f(top_i, :) == 1)));

随后,需要判定上图中的白色三角形需要向下平移多少个像素才到达目标方块的中心点。我首先设置了两个阈值 18 50 ,也就是说通过程序跑的计算出的平移量必须在 18 50 之间。程序从白色三角形,向下逐行扫描为白色(即1)的点的位置。对于每一行,用最右边的白色点的位置减去最左边白色点的位置,得到宽度 width 。从图片可以看出,最上方的时候 width 很小,然后随着向下扫描, width 逐渐增加,随后 width 稳定一个值不变,随后又会变小。我们找到 width 稳定的那个中间点,作为它向下平移的距离,记作 center

举例:假定我们找到的 width 从上到下按照顺序依次为 1,3,6,10,13,14,15,22,25,25,25,25,25,22,18 这样的序列,那么我们知道 25 就是它稳定的值。由于有 5 25 ,我们选用中间的 25 所在的位置作为 center 值,这个数列中间的 25 是第 11 个数,因此 center=11 ,也就是说上图中的白色点需要向下平移 11 个像素。

%from top_i to down, it is x axis. 
wids = zeros(1,top_i+200);
center = -1;
for i = top_i : top_i + 200
    index = find(remove_f(i,:) == 1);
    width = max(index)-min(index);
    if isempty(width)
        width = wids(i-1);
    end
    wids(i) = width;
    if i-top_i > 0 && width < wids(i-1)
        last = i-1;
        start = 0;
        for j = i-1:-1:top_i
            if not(wids(j) == wids(i-1))
                start = j+1;
                break
            end
        end
        if last-start>8
            last = start + 4;
        end
        center = round((start+last)/2)-top_i;
        break;
    end
end

if center < 18
    center = 18;
end
if center > 50
    center = 50;
end

计算小人的中心点:
小人的中心点可以根据小人左上角的位置 res_i,res_j 进行平移,我们认为它的脚下正中间是小人的中心点, m 为小人的高度, n 为小人的宽度,减去 15 是向上平移15个像素作为中心。

source_i = res_i + m - 15;
source_j = res_j+round(n/2); %脚跟距离脚的中心,需要向上提15个像素

计算目标方块的中心点:
目标方块的中心点,列已经确定了,只需要行向下平移 center 个像素即可。

%calculate distance
target_i = top_i + center;
target_j = top_j; %最上方的那个点,向下平移50个像素,认为是center。

如下图所示:

然后根据 source target 计算小人和目标方块的距离,即

%计算距离
dis = sqrt( (target_i-source_i)^2 + (target_j-source_j)^2 ) ;
fprintf(' dis = %.2f\n', dis)

根据距离dis来计算出要触摸的毫秒数

我们需要制定一个字典,来根据距离 dis 得到相应的毫秒数 ms 。 我才用的是比较原始的方式,手机端启动游戏,然后电脑端运行上述的代码,得到 dis ,然后估计一个时间,例如 500ms ,通过adb给手机发送触摸屏幕 500ms 的指令。 如果小人能够正确落到标记的目标方块点,就记录这个 (dis,ms) 到字典 dict 中,慢慢的,我可以收集 10 几个点。

有了这些点,我画了一个点图,感觉像是线性的,因此就简单使用了matlab中自带的一元线性拟合的代码,拟合的一条直线(猜测是最小二乘法)。这样,随后给出任意的 dis ,都可以根据直线方程得到相应的 ms

线性拟合的代码如下:

%根据距离,以及拟合的直线,得到相应的毫秒数 y
dict = [
    [163,350], [197.04, 420], [207, 430], [270, 550], [287, 570], [310, 640], [321, 670], [348.63, 730], [367.27, 769],[374, 780],[393.61, 800], [409.93, 841] 
];

x = dict(1:2:end);
y = dict(2:2:end);

p = polyfit(x,y,1);
y1=polyval(p,x);  %计算出拟合的y值
%figure(3),plot(x,y,'k*',x,y1,'r-');  %画出数据对比图,黑点是原始数据,红线是拟合曲线
y = polyval(p, dis);

本人lpstudy,转载请注明出处,谢谢。

猜你喜欢

转载自blog.csdn.net/lipeng08/article/details/78984422