一場競技游戲,我們在這里稍微划分一下,將其分為局外界面和局內戰斗。舉個例子,比如LOL,在大廳、組隊、選英雄界面這些都是局外界面。當我們等待讀完長長的進度條,進入到游戲,聽到"咚咚咚咚咚"的聲音后英雄出現到我們眼前,這個時候,你就已經到了局內戰斗的部分里了,也就是我們今天要討論的內容。
1. 概述
本文主要對局內戰斗的自動化測試進行講解。包含如何實現戰斗自動化的流程和腳本運行本身框架進行介紹,讀者應具備一些python的編程經驗和UI自動化的開發經驗。
對於游戲的UI自動化這里采用poco的unitysdk形式進行實現。稍微介紹一下poco。這是一個類似appium的客戶端UI自動化框架,利用界面元素來進行UI自動化操作。
官方文檔:
https://poco-chinese.readthedocs.io/zh_CN/latest/source/doc/integration.html
元素來源於UITree,UITree源於接入unitysdk的游戲客戶端。通過與外部掛載agent進行socket通信,從而將使用unity引擎游戲的UITree返回給外部供自動化相關。
UITree里面包含對應Tree中元素的所有屬性信息,其中包含元素的position屬性(位置信息),這也是我們得以鎖定對應元素進行操作的核心依據。
通過pocosdk獲取游戲客戶端UI層信息,對UI信息中的具體內容做對應處理。
最后反饋對應數據給AI,由AI進行對應決策,將決策信息返回給本地后執行對應的UI自動化操作代碼,從而完成決策,通過大量決策的實施,完成整局的實時操作。
再引入多輪重開檢測機制,進而完成局內戰斗的自動化對戰。
一些簡單的演示:
在unity項目中,需要在unity中安裝Poco的SDK
Poco調用方法
import time
from poco.drivers.unity3d import UnityPoco
poco = UnityPoco()
poco('btn_start').click()
time.sleep(1.5)
shell = poco('shell').focus('center')
for star in poco('star'):
star.drag_to(shell)
time.sleep(1)
assert poco('scoreVal').get_text() == "100","score correct."
poco('btn_back',type = 'Button').click()
## Poco調用舉例
poco = UnityPoco() #1、通過接口調用unity中的pocosdk,SDK對整個ui樹進行遍歷,將dump后的json信息傳出以供調用。
poco('btn_start').click() #2、在得到的UI樹中找到‘btn_start’的元素位置信息,通過adb進行點擊操作。
2. 具體流程
流程描述:
通過每次對當前UI數據的檢查,判斷其在局內還是局外。局外會進入到局內對局,處於局內則會檢測當前局內狀態,將當前的局內UI信息做整合,提取指定信息發送給AI決策服務,再根據AI的返回來進行對應的UI自動化代碼操作。
3. 實現過程
3.1 UI層數據獲取
-
使用poco.freeze,利用里面的agent dump把當前的UI樹保存下來(因為要用到root節點,root節點下的內容無法用children獲取)。
-
其他的數據使用poco.freeze調用正常的poco方法進行提取即可。
3.2 指定數據提取
-
確認好AI一共需要我們提供哪些數據,這些數據能不能從游戲的UI信息中讀取出來。確認好后,我們就可以開始進行對應項的提取了。
-
要注意提取的UI元素是不是在特定UI中才會有,他能不能被容易的獲取,也就是對提取指定信息的難易度和是否需要條件構建進行判斷。
-
以自走棋游戲舉例,對於棋子的提取會復雜一些,因為沒有對應棋盤上每個格子的坐標,只有放在根目錄下的美術資源。這部分的數據一個是需要存起來自己寫方法提取,一個是要根據里面屬性包含的信息判斷其在場上還是在手牌。
目前這一部分的判斷經歷過三個階段:
-
靠pos屬性判斷。
-
靠對pocosdk接入unity的c++源碼進行修改添加的rawpos屬性進行判斷。
-
靠這兩者綜合判斷,保證不會有判斷錯的情況出現 。
3.3 數據接口請求過程
請求數據示范樣本:
{"round_drawscount": 0,
"round_actionscount":2,
"round":17,"level":6,
"experience":2,
"money":33,
"blood":73,}
也就是包含一些基本玩家信息的json數據。
AI接口返回數據:
{'eventId': ACTION_UPGRADE}{'eventId': ACTION_DRAW}{'eventId': ACTION_PICK,'params': {'draw_cardIndex': 0}}{'eventId': ACTION_DROP,'params': {"hand_cardIndex":0}}{'eventId': ACTION_SWAP, 'params':{"hand_cardIndex":0,"play_listIndex":1}}{'eventId': ACTION_SWAP, 'params':{"hand_cardIndex":2,"play_listIndex":1}}{'eventId': ACTION_SWAP, 'params':{"hand_cardIndex":0,"play_listIndex":2}}
大致是一些游戲AI的決策內容,通過不同的事件ID告知你應該做什么樣的事情。這里是以目前的自走棋游戲為例進行展示。
具體返回內容就根據實際的業務需求去制定了。
接口須知:
-
所有發送的參數需要向開發人員確認。
-
會存在后期的變動維護,例如傳參要求和增刪參數。
-
要注意發送的數據是經過處理為標准格式。
3.4 返回決策實施
決策實施這里以自走棋類游戲的操作為例:
-
刷棋盤:保證商店處於打開狀態后刷新。
-
買棋子:保證商店處於打開后購買對應索引位置棋子。
-
升人口:隨時都會有的按鈕,直接用點就可以。
-
上下交換棋子:根據對應index有無棋子來判斷對應上下棋子的決定。
-
賣棋子:點擊對應index位置,再點擊sell按鈕。
#對應返回eventID執行不同操作
eventid = self.reqs_data["eventId"]
if eventid == 0:
self.round_over([])
elif eventid == 1:
self.read_chessbook()
elif eventid == 2:
self.refresh()
elif eventid == 3:
self.buy_chess(self.reqs_data["params"]["draw_cardIndex"])
elif eventid == 4:
self.sell_chess(self.reqs_data["params"]["hand_cardIndex"])
elif eventid == 5:
self.up_down_change(self.reqs_data["params"])
3.5 腳本運行框架介紹
其實這個框架歸類的比較簡單,就四個大塊:
(1)runner:包含觸發檢測的觸發器,用於傳入指定包名,選擇對應服務器等操作進行持續集成相關操作;包含動作調用,用來執行對應返回值的操作;包含日志記錄,記錄過程中的游戲運行日志和adb日志等;包含屏幕錄制,記錄游戲過程中的運行視頻,提交視頻數據給UI檢測接口判斷有無UI異常出現(多為花屏白屏和其他明顯UI異常情況)。
(2)datas:游戲運行數據的記錄文件,包含日志的存放和游戲對應戰斗結果的存放(例如當前回合,本局戰斗的排名勝負)。
(3)common_cls:一些公共模塊,包含與AI的通信,數據的發送和返回;包含UI的通用操作,如自動安裝對應版本游戲客戶端后啟動游戲,過關新手引導,生成新賬號等操作;包含通過對AI決策調用對應UI操作方法的函數,也就是動作函數。
(4)requirements:整個工程是通過python完成的,這個就是幫你一鍵安裝所有所需庫的文件,其中涉及到了一些對poco框架的二次開發,需要本地修改后使用。
大致的runner流程:
確保你的設備已與電腦開通調試連接,再啟動poco與adb的本地通信,對當前UI進行判斷是否在局內,局內走提取指定信息,發送給ai進行決策,再執行AI的決策。通過每次檢測有無戰斗結束進行重開實現多輪流程,runner的流程圖與具體實現流程圖基本一致。
3.6 相關優化問題
關於黑名單: 比較大型的游戲,局內戰斗的元素和UI的復雜程度也是很高的,保存一次UI樹的信息也有2M以上的數據,而獲取時間會隨着UI復雜度的上升而上升。從最終效果來看,戰斗從前期到后期,獲取時間會由1秒到4秒逐漸上升,這樣的速度是不能滿足實現業務需求的,因此建議從poco的unitySDK上添加黑名單機制,以提高獲取UITree的效率。
建議利用freeze函數: 多次提取會重復的獲取UI元素浪費時間,目前已經解決這個問題可以只獲取一次就提取出所有參數。目前較大型游戲因局內戰斗UI結構復雜,獲取一次UI信息在一秒到三秒內不等。
UI層操作時間問題: 需要避的坑有兩個 (1)minicap截圖:這個要關掉,實際會采用錄屏行為,不需要截圖。截圖服務啟動會消耗時間且截圖本身也消耗性能 (2)adb本地socket通信建立:確保一次建立,這是poco click swipe等方式進行指令的必要條件,需要提前自己啟動起來,讓他們時刻保持通信,這樣會大大降低單次操作時間。現在單次實行操作的時間已經可以控制在最快0.2秒最慢0.8秒。
獲取UITree時間問題: 通過觀察發現,UITree的獲取時間跟界面的復雜程度是正相關的。一個比較簡單的局外界面可以在0.5秒內就獲取完畢,而較為復雜的局內戰斗就要約2秒多的時間。對於實際操作而言,這樣的過程都太長了。目前探索出的解決方法有兩個:(1)利用黑名單,(2)只dump一次,更新后的數據不做重新獲取,而是采用利用預期結果的數據本地維護。
有關poco的c++層sdk修改文章請嘗試自行百度..
3.7 注意事項
(1)獲取一次UI信息的時間會隨着UI復雜度的提升而提升,局內較為復雜,到后期尤甚。通常在兩秒多三秒左右,實際使用要根據項目做具體優化。
(2)處理獲取出的原始數據到提取對應數據會用到0.2秒左右時間。
(3)操作間要保證存在足夠時間間隔,一個是利於等待操作后UI確實刷新,一個是保證UI切換展示出內容需要時間 。
(4)雙擊功能需要觀察能不能實現現有客戶端的操作(adb和poco double click都不行),有的客戶端要求雙擊的兩次點擊時間間隔太短,不一定能通過自動化實現。
4. 結論
本文主要描述了通過局內調用AI的方式實現自動打游戲。利用這個框架可以進行局內的多輪流程檢測(甚至上分),從而幫助測試同學免去重復去測局內戰斗的皮肉之苦,同時可以記錄過程視頻,交由AI或其他api判斷有無花屏白屏的情況,adb報錯,unity報錯信息都可以進行實時收集,接下來我們也會對局外的AI探索性遍歷展開研究,請大家持續關注我們,並指出我們的不足。
PS:
我們是行者AI,我們在“AI+游戲”中不斷前行。
如果你也對游戲感興趣,對AI充滿好奇,那就快來加入我們(hr@xingzhe.ai)。