最近沉迷于一款老手游姜挺,每天都花費(fèi)大量的時(shí)間、資源去完成游戲的任務(wù)彼硫。每天做著重復(fù)的動(dòng)作炊豪,實(shí)在是浪費(fèi)青春啊拧篮!作為一個(gè)程序猿怎么受的了词渤,我們不就是為有解決重復(fù)動(dòng)作而生的么!經(jīng)過(guò)沉痛的反思串绩,下決心我要寫一個(gè)輔助軟件去攻略這個(gè)游戲缺虐,從每天的重復(fù)動(dòng)作中解脫出來(lái)...
思路
說(shuō)干就干,這就開(kāi)啟了咱的輔助開(kāi)發(fā)之路礁凡。
說(shuō)到開(kāi)發(fā)高氮,思路最重要慧妄,只有理清了思路,才能知道代碼要怎么寫剪芍。我們玩游戲主要的操作就是:找到相應(yīng)的按鈕塞淹,然后點(diǎn)擊它。從這里可以看到兩個(gè)關(guān)鍵點(diǎn) 點(diǎn)擊屏幕
和 查找按鈕坐標(biāo)位置
罪裹,那我們?cè)趺磳?shí)現(xiàn)它呢饱普。這就要用到 Android 的開(kāi)發(fā)工具 ADB
,做 Android 開(kāi)發(fā)的同學(xué)應(yīng)該都知道状共,我們可以通過(guò) ADB
工具模擬點(diǎn)擊套耕,滑動(dòng)等一系列的動(dòng)作。
運(yùn)行命令
我們需要在我們的代碼里運(yùn)行 cmd
峡继,從而達(dá)到運(yùn)行 adb
命令冯袍。
# 運(yùn)行命令
def run_cmd(self, cmd):
time.sleep(0.1)
log.d(u'執(zhí)行命令:%s' % cmd)
execute = os.popen(cmd, 'r')
result = execute.read()
log.w(result)
return result
# 運(yùn)行 adb shell 命令
def run_adb_shell(self, cmd):
adb_cmd = 'adb shell %s' % cmd
result = self.run_cmd(adb_cmd)
return result
模擬操作
這里使用的 Mumu 模擬器來(lái)充當(dāng)?shù)氖謾C(jī),首頁(yè)我們需要使用 adb
連接我們的設(shè)備鬓椭,命令: adb connect host:prot颠猴。
# adb 連接
def connect(self):
for x in range(1, 10):
result = self.run_cmd('adb connect %s:%s' % (HOST, PORT))
if 'already connected' in result:
log.d(u'已連接')
break
elif 'connected' in result:
log.d(u'連接成功')
break
time.sleep(1)
log.d(u'adb 正在重試連接 %s ...' % x)
連接設(shè)備之后就是我們的模擬點(diǎn)擊和滑動(dòng)操作啦
# 點(diǎn)擊屏幕 point
def click(self, point, delay=2):
# 延遲執(zhí)行,避免被不確定因素影響
time.sleep(delay)
# 執(zhí)行點(diǎn)擊命令
self.run_adb_shell(r'input tap %s %s' % (point.x, point.y))
# 滑動(dòng)
def swipe(self, point_start, point_end, delay=1):
# 延遲執(zhí)行小染,避免被不確定因素影響
time.sleep(delay)
# 執(zhí)行點(diǎn)擊命令
self.run_adb_shell(r'input swipe %s %s %s %s' % (point_start.x, point_start.y, point_end.x, point_end.y))
按鈕在屏幕中的位置查找
查找按鈕的坐標(biāo)位置翘瓮,是整個(gè)輔助軟件的重中之重,它直接關(guān)系到了整個(gè)軟件的可用性裤翩。這里采用了圖像的模版匹配方法资盅,也就是將我們的目標(biāo)按鈕圖片與游戲當(dāng)前的圖像進(jìn)行匹配,找一個(gè)重合率最高的位置踊赠。
安裝 python-opencv
pip install python-opencv
游戲畫面截圖
# 截屏
def screen_shot(self):
self.run_adb_shell(r'screencap -p /sdcard/screen.png')
self.run_cmd(r'adb pull /sdcard/screen.png')
按鈕位置匹配
# 匹配圖片
def match_img(self, origin_uri, target_uri, threshold=0.9):
methods = ['TM_CCOEFF', 'TM_CCOEFF_NORMED', 'TM_CCORR', 'TM_CCORR_NORMED', 'TM_SQDIFF', 'TM_SQDIFF_NORMED']
result = self._match_img(origin_uri, target_uri, methods[3], threshold)
return result
# 匹配圖片
def _match_img(self, origin_uri, target_uri, method, threshold=0.9):
origin_img = cv2.imread(origin_uri, 0)
target_img = cv2.imread(target_uri, 0)
method = eval('cv2.%s' % method)
w, h = target_img.shape[::-1]
origin_img_copy = origin_img.copy()
res = cv2.matchTemplate(origin_img_copy, target_img, method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
log.d(u"最小匹配值:%s 最大匹配值:%s" % (min_val, max_val))
if max_val < threshold:
log.d(u'未查詢到配置位置')
return None
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
return Box(top_left[0], top_left[1], bottom_right[0], bottom_right[1])
這樣我們就可以通過(guò) match_img
方法找到我們需要的按鈕位置呵扛,并進(jìn)而使用 click
方法來(lái)模擬點(diǎn)擊屏幕,從而擁有使用輔助軟件消滅重復(fù)操作的能力筐带。
完整代碼
class.py
"""
點(diǎn)
"""
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
"""
方框
"""
class Box:
def __init__(self, left=0, top=0, right=0, bottom=0):
self.top = top
self.left = left
self.bottom = bottom
self.right = right
adb.py
import os
import time
import log
HOST = '127.0.0.1'
PORT = 7555
class ADB:
def __init__(self):
self.connect()
# adb 連接
def connect(self):
for x in range(1, 10):
result = self.run_cmd('adb connect %s:%s' % (HOST, PORT))
if 'already connected' in result:
log.d(u'已連接')
break
elif 'connected' in result:
log.d(u'連接成功')
break
time.sleep(1)
log.d(u'adb 正在重試連接 %s ...' % x)
# adb 斷開(kāi)連接
def disconnect(self):
result = self.run_cmd('adb disconnect %s:%s' % (HOST, PORT))
log.d(u'adb 已斷開(kāi)連接')
# 運(yùn)行命令
def run_cmd(self, cmd):
time.sleep(0.1)
log.d(u'執(zhí)行命令:%s' % cmd)
execute = os.popen(cmd, 'r')
result = execute.read()
log.w(result)
return result
# 運(yùn)行 adb shell 命令
def run_adb_shell(self, cmd):
adb_cmd = 'adb shell %s' % cmd
result = self.run_cmd(adb_cmd)
return result
# 點(diǎn)擊屏幕 point
def click(self, point, delay=2):
# 延遲執(zhí)行今穿,避免被不確定因素影響
time.sleep(delay)
# 執(zhí)行點(diǎn)擊命令
self.run_adb_shell(r'input tap %s %s' % (point.x, point.y))
# 滑動(dòng)
def swipe(self, point_start, point_end, delay=1):
# 延遲執(zhí)行,避免被不確定因素影響
time.sleep(delay)
# 執(zhí)行點(diǎn)擊命令
self.run_adb_shell(r'input swipe %s %s %s %s' % (point_start.x, point_start.y, point_end.x, point_end.y))
# 截屏
def screen_shot(self):
self.run_adb_shell(r'screencap -p /sdcard/screen.png')
self.run_cmd(r'adb pull /sdcard/screen.png')
image.py
import log
from clazz import Box
import cv2
import os
import numpy as np
class ImgHelper:
def __init__(self):
pass
# 獲取灰度值
def get_gray_level(self, img_uri, point):
img = cv2.imread(img_uri)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
log.d('點(diǎn)(%s,%s)灰度值為:%s' % (point.x, point.y, gray[point.y][point.x]))
return gray[point.y][point.x]
# 圖片裁剪
def cut(self, origin_uri, box, save_name):
origin_img = cv2.imread(origin_uri)
cropped = origin_img[box.top:box.bottom, box.left:box.right]
save_dir = 'tmp'
if not os.path.exists(save_dir):
os.makedirs(save_dir)
save_path = save_dir + os.path.sep + save_name
cv2.imwrite(save_path, cropped)
return save_path
# 匹配圖片
def match_img(self, origin_uri, target_uri, threshold=0.9):
methods = ['TM_CCOEFF', 'TM_CCOEFF_NORMED', 'TM_CCORR', 'TM_CCORR_NORMED', 'TM_SQDIFF', 'TM_SQDIFF_NORMED']
result = self._match_img(origin_uri, target_uri, methods[3], threshold)
return result
# 匹配圖片
def _match_img(self, origin_uri, target_uri, method, threshold=0.9):
origin_img = cv2.imread(origin_uri, 0)
target_img = cv2.imread(target_uri, 0)
method = eval('cv2.%s' % method)
w, h = target_img.shape[::-1]
origin_img_copy = origin_img.copy()
res = cv2.matchTemplate(origin_img_copy, target_img, method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
log.d(u"最小匹配值:%s 最大匹配值:%s" % (min_val, max_val))
if max_val < threshold:
log.d(u'未查詢到配置位置')
return None
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
return Box(top_left[0], top_left[1], bottom_right[0], bottom_right[1])