《家国梦》是最近很火的一款不用氪金的手游,在周围同窗好友的怂恿下,我走上了“不归路”。这游戏玩法至关简单,就是拾取金币和搬运货物,攒足金币升级建筑。在这过程当中,咱们还能够学习国家当前政策。python
因为游戏玩法很简单,这让我萌发了自动化测试(开挂)的念头。git
项目地址:github.com/Jiahonzheng… 。 演示视频:www.bilibili.com/video/av692… 。github
咱们使用网易游戏推出的 MuMu 模拟器,进行自动化测试。安装过程很简单的,这里也就不提了。这里的核心要点是 开启 USB 调试选项 和 adb 调试地址(127.0.0.1:7555)。算法
BTW,因为我使用的手机是 SONY Xperia Z5 Premium ,故我将模拟器的分辨率设置为 1920*1080 ,项目所用到的素材也都是基于此分辨率制做的。bash
咱们使用 UIAutomator2 做为自动化测试工具,其工做流程大体以下:机器学习
咱们执行下列命令完成 UIAutomator2 的安装和初始化工做(请务必确保已完成 adb 链接)。ide
# 安装依赖
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)
复制代码
目测该游戏是使用 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 的问题,哈哈哈。