《家國夢》是最近很火的一款不用氪金的手遊,在周圍同窗好友的慫恿下,我走上了「不歸路」。這遊戲玩法至關簡單,就是拾取金幣和搬運貨物,攢足金幣升級建築。在這過程當中,咱們還能夠學習國家當前政策。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 的問題,哈哈哈。