《家国梦》游戏自动化测试

3,401 阅读5分钟

《家国梦》是最近很火的一款不用氪金的手游,在周围同学好友的怂恿下,我走上了“不归路”。这游戏玩法相当简单,就是拾取金币搬运货物,攒足金币升级建筑。在这过程中,我们还可以学习国家当前政策。

由于游戏玩法很简单,这让我萌发了自动化测试(开挂)的念头。

项目地址: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 的问题,哈哈哈。