《家国梦》是最近很火的一款不用氪金的手游,在周围同学好友的怂恿下,我走上了“不归路”。这游戏玩法相当简单,就是拾取金币和搬运货物,攒足金币升级建筑。在这过程中,我们还可以学习国家当前政策。
由于游戏玩法很简单,这让我萌发了自动化测试(开挂)的念头。
项目地址:github.com/Jiahonzheng… 。
演示视频:www.bilibili.com/video/av692… 。
MuMu 模拟器
我们使用网易游戏推出的 MuMu 模拟器,进行自动化测试。安装过程很简单的,这里也就不提了。这里的核心要点是 开启 USB 调试选项 和 adb 调试地址(127.0.0.1:7555)。
BTW,由于我使用的手机是 SONY Xperia Z5 Premium ,故我将模拟器的分辨率设置为 1920*1080 ,项目所用到的素材也都是基于此分辨率制作的。
UIAutomator2
我们使用 UIAutomator2 作为自动化测试工具,其工作流程大致如下:
在移动设备上安装 ATX 守护进程,其会启动 UIAutomator2 服务(默认端口:7912)进行监听。
我们在PC上编写测试脚本,发送脚本至到移动设备的server端。
移动设备通过 Wi-Fi 或 USB 接收到 PC 发送的脚本,执行制定的操作。
我们执行下列命令完成 UIAutomator2 的安装和初始化工作(请务必确保已完成 adb 连接)。
# 安装依赖 python -m pip install uiautomator2 # 安装 ATX 应用 python -m uiautomator2 init |
在安装完 ATX 应用后,我们点击应用内部的**”启动 UIAutomator2“**,确保服务已开启。随后,我们编写并执行以下代码,即可生成屏幕快照。
import uiautomator2 as u2 d = u2.connect("127.0.0.1:7555") d.screenshot("Game.jpg") |
滑屏拾币
在游戏中,每栋建筑都可产生一定数量的金币,我们可在建筑物间滑动,来拾取金币。为实现滑屏拾币的自动化,我们可调用 device.swipe 方法,这是 uiautomator2 提供的实现触摸滑动的函数,我们需要为其传入起始点屏幕坐标和终止点屏幕坐标。
便于开发,我们为每块地建立对应的编号,具体如下图所示。
编号与屏幕位置的对应关系如下。请注意,这是 1920*1080 尺寸下的屏幕位置。
@staticmethod def _get_position(key): """ 获取指定建筑的屏幕位置。 """ positions = { 1: (294, 1184), 2: (551, 1061), 3: (807, 961), 4: (275, 935), 5: (535, 810), 6: (799, 687), 7: (304, 681), 8: (541, 568), 9: (787, 447) } return positions.get(key) |
我们的滑屏拾币的策略很简单:分 3 次滑屏,第 1 次是 1 - 3 号建筑,第 2 次是 4 - 6 号建筑,第 3 次是 7 - 9 号建筑。
def _swipe(self): """ 滑动屏幕,收割金币。 """ for i in range(3): # 横向滑动,共 3 次。 sx, sy = self._get_position(i * 3 + 1) ex, ey = self._get_position(i * 3 + 3) self.d.swipe(sx, sy, ex, ey) |
OpenCV
目测该游戏是使用 Unity 实现,我们在 weditor 里无法获取足够的 Hierarchy 信息,因此为了实现搬运货物的功能,我们只能选择图像识别的策略:我们获取游戏的屏幕快照,然后判断其中是否含有货物,若有则搬运至目的建筑。我们可以使用 OpenCV 的模版匹配功能实现此需求。
首先,我们需要安装 OpenCV 依赖。
python -m pip install opencv |
我们先对 cv2.matchTemplate 进行一次简单的测试,看它的效果如何。
import cv2 # 读取快照 screen = cv2.imread('Game.jpg') # 读取货物图片 template = cv2.imread('targets/Sofa.jpg') # 获取货物图片的长宽信息 th, tw = template.shape[:2] # 调用 OpenCV 的模版匹配方法 res = cv2.matchTemplate(screen, template, cv2.TM_SQDIFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # min_val 可用来判断是否检测到货物 # 矩形左上角坐标 tl = min_loc # 矩形右下角坐标 br = (tl[0] + tw, tl[1] + th) cv2.rectangle(screen, tl, br, (0, 0, 255), 2) cv2.imwrite('Result.jpg', screen) |
执行上述代码,我们即可在快照中标记出 Sofa 的位置(红框圈住的物体),这表明此方法是可以 work 的。
搬运货物
我们封装了 UIMatcher 类,用于探测物体是否存在,并采取相应举动。我们根据 min_val 的值,我们来判断是否已检测到货物。
# 阈值判断。 if min_val > 0.15: return None |
我们在 _match_target 函数中,实现了搬运货物的功能。由于 OpenCV 的模版匹配也有“智障”的时候,我们采用了冗余搬运的方式。
def _match_target(self, target: TargetType): """ 探测货物,并搬运货物。 """ # 获取当前屏幕快照 screen = self.d.screenshot(format="opencv") # 由于 OpenCV 的模板匹配有时会智障,故我们探测次数实现冗余。 counter = 6 while counter != 0: counter = counter - 1 # 使用 OpenCV 探测货物。 result = UIMatcher.match(screen, target) # 若无探测到,终止对该货物的探测。 # 实现冗余的原因:返回的货物屏幕位置与实际位置存在偏差,导致移动失效 if result is None: break sx, sy = result # 获取货物目的地的屏幕位置。 ex, ey = self._get_target_position(target) # 搬运货物。 self.d.swipe(sx, sy, ex, ey) |
组装程序
到这里,我们已经把两个核心功能(滑屏拾币 和 搬运货物)都实现了。现在,我们需要对其组装。我们的方式很简单粗暴,在 Automator 类的 start 方法中,我们在循环里,周而复始地进行搬运货物和滑屏拾币的任务。
def start(self): """ 启动脚本,请确保已进入游戏页面。 """ while True: # 判断是否出现货物。 for target in TargetType: self._match_target(target) # 简单粗暴的方式,处理 “XX之光” 的荣誉显示。 # 当然,也可以使用图像探测的模式。 self.d.click(550, 1650) # 滑动屏幕,收割金币。 self._swipe() |
结语
在这篇博客中,我们使用了 MuMu 模拟器、UIAutomator2 和 OpenCV 实现了《家国梦》游戏的自动化测试,解决了两个“核心“玩法的自动化模拟问题:滑屏拾币 和 搬运货物 。当然,我们的实现是存在很多可以改进的地方,如货物的探测算法,或许我们可以使用机器学习来解决这个 Object Detection 的问题,哈哈哈。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理