文章目录
- 崩铁小助手ASR
- 功能计划
- 功能实现
- 操作的模拟
- 窗口的识别
- 游戏窗口识别
- 副本导航
- 和平指南页面识别
- 页面中高亮位置的寻找
- 右侧具体副本的寻找
- 未完待续
- 开源地址
崩铁小助手ASR
天下苦二游上班坐牢久矣。方舟有MAA造福大众,免去日常之苦,能让我专心于关卡,但是米家游戏就不行了,于是就有了这个崩铁小助手——AutoStarRail的想法。
功能计划
目前初步计划就是能够实现每天自动清体力,领日常奖励,让我不用操心每天还得上线清体力的事情。最后实现的界面如下,大概和方舟的maa差不多。
但是为了防止崩铁全屏运行时难以观察运行信息,所以又做了个始终在前台的message窗口,用于实现实时显示自动化脚本的信息。(窗口可放置在任意位置)
经过测试,选择好要刷的本(经验\钱、行迹、突破素材、仪器这些都没问题)后能够自动导航至目标副本,然后识别体力,刷到没体力为止。
演示视频如西瓜视频:从重复劳动中解脱-崩铁自动日常小助手
抖音:从重复劳动中解脱-崩铁自动日常小助手
完整开源代码见 AutoStarRail,欢迎大家star。
功能实现
操作的模拟
操作上使用vgamepad创建虚拟手柄来对游戏进行操作。虽然这样会多一个虚拟设备,但是由于手柄操作时对选中部件的高亮,能够更容易的识别当前选中的东西,并进行精确的操作。
class Gamepad:def __init__(self,pigeon = None):# 初始化一个手柄self.pigeon = pigeonself.gamepad = vgamepad.VX360Gamepad()# 初始化手柄状态self.reset_gamepad()def reset_gamepad(self):self.gamepad.reset()#键位扳机摇杆全部重置成初始状态self.gamepad.update()# gamepad 操作def click_button(self,button,duration=0.15):self.gamepad.press_button(button)self.gamepad.update()time.sleep(duration + random.randint(0,int(0.05*100))/100)self.gamepad.release_button(button)self.gamepad.update()if self.pigeon:self.pigeon("click " + button_mapping[button])def press_button(self,button):self.gamepad.press_button(button)self.gamepad.update()if self.pigeon:self.pigeon("press " + button_mapping[button])def release_button(self,button):self.gamepad.release_button(button)self.gamepad.update()def LEFT_TRIGGER(self,value):self.gamepad.left_trigger_float(value)# 左扳机轴 value改成0.0到1.0之间的浮点值,可以精确到小数点后5位self.gamepad.update()def RIGHT_TRIGGER(self,value): self.gamepad.right_trigger_float(value)# 右扳机轴 value改成0.0到1.0之间的浮点值,可以精确到小数点后5位self.gamepad.update()def LEFT_JOYSTICK(self,theta,amplitude,ran_theta = 2*np.pi/25,ran_amp = 1/25): #x_value, y_value):theta = theta + random.randint(0,ran_theta*100)/100amplitude = amplitude + random.randint(0,ran_amp*100)/100x_value = 1.414*amplitude * np.cos(theta)y_value = 1.414*amplitude * np.sin(theta)self.gamepad.left_joystick_float(x_value, y_value)# 左摇杆XY轴 x_values和y_values改成-1.0到1.0之间的浮点值,可以精确到小数点后5位self.gamepad.update()if self.pigeon:self.pigeon("" + "Left joystick")def RIGHT_JOYSTCIK(self,theta,amplitude,ran_theta = 2*np.pi/25,ran_amp = 1/25):theta = theta + random.randint(0,int(ran_theta*100))/100amplitude = amplitude + random.randint(0,int(ran_amp*100))/100x_value = amplitude * np.cos(theta)y_value = amplitude * np.sin(theta)self.gamepad.right_joystick_float(x_value, y_value)# 右摇杆XY轴 x_values和y_values改成-1.0到1.0之间的浮点值,可以精确到小数点后5位self.gamepad.update()if self.pigeon:self.pigeon("" + "Left joystick")def joystick_movement(self, theta=0, duration=0.5, amplitude=1):start_time = time.time()while time.time() - start_time < duration:# 时间-角度序列theta_time = theta * (time.time() - start_time) / duration# 幅度amplitude_time = amplitude * (time.time() - start_time) / durationself.RIGHT_JOYSTCIK(theta_time, amplitude_time)time.sleep(0.01)
窗口的识别
这部分涉及到图像的一些识别。为了减少计算资源的消耗,本文主要使用paddleocr识别字符来定位。少部分地方用到了矩形框的识别。
游戏窗口识别
脚本启动时应当先识别当前有没有打开启动器、或游戏,再决定是否需要打开游戏。
对于老版本而言由于启动器和游戏名称都为“崩坏:星穹铁道”,因此无法仅从名称上判断窗口是哪个,还需要进一步判断是启动器还是游戏。可以通过窗口上是否有启动器上独有的字符判断是否为游戏。
因此,窗口检测的流程如下图所示。
其相关代码在start_game.py中,该部分代码能够实现自动识别当前是否有游戏窗口,如果无窗口则逐步实现打开游戏。
副本导航
该部分代码放置在daily_tasks.py中。
导航的第一步是打开星际和平指南,该步较为简单,直接使用虚拟手柄打开轮盘,然后拨到对应位置即可。
def open_star_guide(self):# 打开星际和平指南self.gp.press_button(LEFT_SHOULDER)self.gp.joystick_movement(np.pi * 1 / 4, duration= 1) # 移动到指南time.sleep(0.8)self.gp.joystick_movement(amplitude=0)self.gp.release_button(LEFT_SHOULDER)self.pigeon("打开星际和平指南")time.sleep(0.9)
随后需要进一步识别星际和平指南的页面,以及寻找对应的副本。流程如下:
和平指南页面识别
对于和平指南的页面,如每日实训、生存索引的识别,只需通过ocr识别有无对应字符即可找到该页面
def find_page(self,tag = "生存索引"):# 已经打开星际和平指南后,通过手柄切换标签找到对应的pageif TargetDetector(self.window,self.gp).search_button(tag, RIGHT_SHOULDER):self.pigeon("找到" + tag)else:self.pigeon("未找到" + tag)
其中TargetDetect中的search_button为递归寻找,直到满足条件。
def search_button(self, btn_text, action):"""查找相应按钮:param btn_text: 目标按钮名称:param action: 找不到按钮对应操作:return:"""figure, _, _, _, _ = self.win_action.get_screenshot(self.window)find, _ = self.findText(figure,btn_text)if find:print(f'找到{btn_text}')return Trueelse:self.gp.click_button(action)time.sleep(0.2 + random.randint(0, 10) / 100)return self.search_button(btn_text, action)
页面中高亮位置的寻找
在确定找到页面后,我们需要识别出当前高亮的标签(如拟造花萼、侵蚀隧洞)是哪个。
此处我们可以先使用OCR识别有无目标文字,如果没有,说明当前页面不存在目标标签,需要继续翻页。如果存在,通过灰度阈值识别目标文字所在区域的灰度是否是选中的灰度,如果是则退出寻找,否则继续寻找。
def find_highlight(self, btn_text, action=None): """ 寻找按钮的高亮状态。通过截图并查找按钮文本,判断按钮是否处于高亮状态。如果按钮未高亮,则点击按钮并重试。 主要用于自动化测试中对按钮状态的判断和操作。 参数: btn_text (str): 按钮的文本内容,用于查找按钮。 action (function, optional): 当按钮未高亮时执行的操作,默认为None。可以是一个函数,该函数会在按钮未高亮时被调用。 返回: bool: 如果按钮处于高亮状态,则返回True;否则返回False。 """ # 截取窗口的屏幕快照 # 转到灰度上看灰度值。先截取字附近的区域 figure, _, _, _, _ = self.win_action.get_screenshot(self.window) # 在屏幕快照中查找按钮文本 find, pos = self.findText(figure, btn_text) # 如果按钮未找到 if not find: # 没找到self.gp.click_button(action)time.sleep(0.2 + random.randint(0, 10) / 100)return self.find_highlight(btn_text, action)ave_gray = self.get_average_gray_value(figure,pos)if ave_gray < 100: # 高亮-黑色print(f'找到{btn_text}')return Trueelse:self.gp.click_button(action)time.sleep(0.2 + random.randint(0, 10) / 100)return self.find_highlight(btn_text, action)
右侧具体副本的寻找
使用手柄的话,右侧选择的副本会有一个橙色的矩形框作为高亮,因此识别矩形框就知道我们当前选中的是哪个了。
使用HSV颜色空间对橙色进行区分的效果并不理想。因此还是采用了矩形边框识别。
def detect_dungeon_boxes(self,image,text):# 可以找出当前高亮的选择区域gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)edges = cv2.Canny(gray, threshold1=100, threshold2=200)contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)min_area_threshold = 10 # 设定最小面积阈值height, width = image.shape[:2] # 获取图像高度和宽度area_ratio = 0.2target_area = height * width * area_ratio # 计算目标最大面积max_area = 0for contour in contours:area = cv2.contourArea(contour)if area > max_area and area < target_area:max_area = areamax_contour = contourfor contour in contours:area = cv2.contourArea(contour)if area > min_area_threshold:# 计算边界框x, y, w, h = cv2.boundingRect(contour)# 绘制矩形cv2.rectangle(image, (x, y), (x+w, y+h), (0, 255, 0), 2)# 如果找到了符合条件的轮廓if max_contour is not None:# 获取边界框x, y, w, h = cv2.boundingRect(max_contour)# 截取该矩形区域cropped_image = image[y:y+h, x:x+w]find, pos = self.findText(cropped_image,text)# 显示截取的图像# cv2.imshow('Cropped Rectangle', cropped_image)# cv2.waitKey(0)# cv2.destroyAllWindows()return findelse:# print("Not found.")return Falsedef find_dungeon(self,btn_text, action = None):figure, _, _, _, _ = self.win_action.get_screenshot(self.window)# self.gp.click_button(action)if self.detect_dungeon_boxes(figure,btn_text):print('已选中:' + btn_text)else:self.gp.click_button(action)time.sleep(0.2 + random.randint(0, 10) / 100)return self.find_dungeon(btn_text, action)
未完待续
GUI、消息窗口、邮件等功能以及当前版本未实现的功能待后续更新。
开源地址
- AutoStarRail