1. 项目概述:为什么用Python和TagUI来“挂”《咸鱼之王》?
如果你玩过《咸鱼之王》这类放置类手游,肯定对“挂机”这个核心玩法又爱又恨。爱的是它解放了双手,恨的是为了最大化收益,你依然需要时不时点开游戏,领取离线奖励、完成日常任务、挑战BOSS,甚至半夜还得爬起来收个菜。这种重复、定时、规律的操作,简直就是为自动化量身定做的场景。手动操作不仅枯燥,还容易忘记,影响游戏进度和资源积累。
这时候,RPA(机器人流程自动化)技术就能派上大用场。RPA的核心思想是模拟人在电脑前的操作——移动鼠标、点击、输入文字、识别屏幕内容。我们这次要做的,就是用Python驱动一个轻量级但功能强大的RPA工具:TagUI,来打造一个专属的《咸鱼之王》桌面端挂机脚本。你可能会问,为什么是Python+TagUI,而不是纯Python图像识别或者更复杂的Selenium?这里面的选择有讲究。
首先,Python是胶水语言,生态丰富,但纯用PyAutoGUI这类库做图像识别,对新手来说调试复杂,容错率低。而TagUI本身就是一个用JavaScript写的RPA工具,但它提供了Python接口(tagui包),让我们可以用Python的逻辑来组织和控制自动化流程,结合了两者的优势。TagUI内置了视觉识别(通过SikuliX引擎)和OCR(光学字符识别)功能,这意味着我们不需要自己训练复杂的AI模型,就能让脚本“看懂”屏幕上哪里是“领取”按钮,哪里显示了“金币数量”。对于游戏界面这种元素相对固定、但可能因缩放或主题略有变化的场景,视觉识别比基于元素坐标的绝对点击要稳定得多。
这个项目的目标很明确:编写一个脚本,能自动完成《咸鱼之王》中的一系列日常挂机操作,比如登录、领取各种奖励、挑战副本、使用道具等。最终实现“一键启动,全天托管”,让你从重复劳动中彻底解放出来,把时间和精力花在更有趣的游戏策略或现实生活中。下面,我们就从环境搭建开始,一步步拆解如何实现这个自动化挂机机器人。
2. 环境准备与核心工具链搭建
工欲善其事,必先利其器。在开始写代码之前,我们需要把整个自动化环境搭建起来。这个过程稍微有点繁琐,但每一步都是后续稳定运行的基础。
2.1 Python环境与TagUI安装
首先确保你的电脑上安装了Python。推荐使用Python 3.7到3.9版本,兼容性最好。你可以从Python官网下载安装包,记得安装时勾选“Add Python to PATH”,这样可以在命令行中直接使用python命令。
安装好Python后,我们通过pip安装TagUI的Python包。打开你的命令行终端(CMD或PowerShell),输入以下命令:
pip install tagui这个命令会安装tagui这个Python库,它本质上是TagUI核心工具的一个封装器。但是,仅仅安装这个Python包是不够的,因为它只是一个接口。TagUI的运行依赖于其原生环境,包括Java和SikuliX。
接下来是关键的一步:安装TagUI原生环境。我们需要从TagUI的GitHub发布页面下载其预打包的完整版本。你可以手动去GitHub搜索“tagui releases”,下载对应你操作系统(Windows/macOS/Linux)的ZIP包。这里我提供一个更自动化的方法,在Python脚本里完成下载和设置。不过,最稳妥的方式还是手动下载并解压到一个你容易找到的路径,比如C:\tagui或D:\tools\tagui。
注意:TagUI的运行依赖Java运行时环境(JRE)。请确保你的系统已经安装了Java 8或更高版本。可以在命令行输入
java -version来检查。如果没有安装,需要先去Oracle官网或Adoptium网站下载安装。
假设你已经将TagUI解压到了D:\tagui目录。接下来,需要设置一个重要的环境变量TAGUI_HOME,指向这个目录。同时,将%TAGUI_HOME%添加到系统的PATH变量中,以便在任意位置都能调用tagui命令。
Windows系统设置环境变量步骤:
- 右键点击“此电脑” -> “属性” -> “高级系统设置” -> “环境变量”。
- 在“系统变量”部分,点击“新建”,变量名输入
TAGUI_HOME,变量值输入D:\tagui(你的实际路径)。 - 在“系统变量”中找到
Path变量,双击编辑,点击“新建”,添加一行%TAGUI_HOME%。 - 一路点击“确定”保存。
完成这些后,打开一个新的命令行窗口,输入tagui并回车。如果看到TagUI的版本信息和帮助提示,说明环境配置成功了。这一步至关重要,后续Python脚本会调用这个命令行工具。
2.2 辅助工具:截图与坐标获取
在编写视觉自动化脚本时,我们经常需要知道屏幕上某个按钮或区域的坐标,或者需要截取某个图像作为识别的模板。这里推荐两个小工具:
- Windows自带截图工具:按
Win + Shift + S可以快速进行区域截图,保存下来作为TagUI识别的参考图像。 - 获取鼠标坐标工具:对于需要精确定位点击的场景(作为视觉识别的备选方案),一个能实时显示鼠标坐标的小软件很有用。例如开源工具“MousePoint”或者一些编程IDE自带的工具。在Python中,我们也可以用
pyautogui库的pyautogui.position()函数来实时打印坐标,但需要先写一小段代码。
为了简化,我们可以准备一个简单的Python脚本来辅助定位。创建一个叫mouse_locator.py的文件,内容如下:
import pyautogui import time print(‘按Ctrl+C终止程序’) try: while True: x, y = pyautogui.position() print(f‘X: {x:4d} Y: {y:4d}’, end=‘\r’) time.sleep(0.1) except KeyboardInterrupt: print(‘\n程序结束’)运行这个脚本,移动鼠标到游戏界面的“领取奖励”按钮上,终端里就会实时显示该点的坐标。记下这个坐标,可以在TagUI脚本中使用click (x, y)的方式进行绝对坐标点击,作为视觉识别失败时的降级方案。但强烈建议以视觉识别为主,因为游戏窗口位置可能会变动,绝对坐标很容易失效。
2.3 项目结构与初始化脚本
建立一个清晰的项目文件夹,管理你的脚本和资源文件。建议的目录结构如下:
xianyu_auto_bot/ ├── src/ │ ├── main.py # 主程序入口 │ ├── core/ │ │ ├── __init__.py │ │ ├── game_operator.py # 游戏操作核心类 │ │ └── image_templates/ # 存放所有截图模板图片 │ │ ├── login_button.png │ │ ├── receive_reward.png │ │ └── ... │ └── utils/ │ ├── __init__.py │ └── tools.py # 工具函数,如日志、等待 ├── config.ini # 配置文件(可选) └── requirements.txt # Python依赖列表在requirements.txt中,目前只需要一行:
tagui在项目根目录下,运行pip install -r requirements.txt来安装依赖。
现在,我们先创建一个最简单的测试脚本test_connection.py,验证TagUI能否正常工作,并尝试与游戏窗口交互:
import tagui as t def test_basic_operation(): # 初始化TagUI,verbose=True可以看到详细的执行日志 t.init(verbose=True) # 尝试在屏幕上寻找“记事本”图标(假设你桌面有) # 这里只是演示find API,实际用游戏截图 if t.find(‘notepad_icon.png’): t.click(‘notepad_icon.png’) print(“找到并点击了记事本图标”) else: print(“未找到指定图片”) # 模拟按键:Alt+F4关闭窗口 t.keyboard(‘[alt]f4’) # 结束TagUI进程 t.close() if __name__ == ‘__main__’: test_basic_operation()运行这个脚本,观察输出。如果看到TagUI启动并尝试寻找图片,说明基础环境打通了。接下来,我们就进入核心部分:分析游戏界面和设计自动化流程。
3. 游戏操作分析与自动化流程设计
在动手写代码之前,我们必须像产品经理一样,仔细分析《咸鱼之王》的手游在桌面模拟器(如雷电、夜神、MuMu模拟器)上的操作流程。我们的脚本不是盲目点击,而是要有清晰的逻辑判断和状态感知。
3.1 核心挂机循环与状态判断
一个完整的日常挂机循环,可能包含以下模块,我们需要为每个模块设计子流程:
- 登录与断线重连:检测是否在登录界面,执行登录操作。检测网络断线或异常弹窗,并进行处理。
- 领取离线与每日奖励:进入游戏主城后,检测并领取各种邮箱奖励、每日签到、离线挂机收益。
- 挑战副本与BOSS:导航到副本界面,根据体力或挑战券情况,选择指定副本进行挑战,并等待战斗结束。
- 使用道具与加速:自动使用经验药水、金币袋等道具,使用快速战斗加速券。
- 循环与异常处理:设定循环周期,并在每个循环结束后,检查是否有未处理的弹窗或任务,然后等待一段时间后开始下一轮。
这里最大的挑战是**“状态判断”**。脚本如何知道当前界面是主城还是副本?如何知道奖励是否可领取?我们主要依靠两种手段:
- 视觉模板匹配:提前截取关键状态下的界面元素图片,如灰色的“已领取”按钮和亮色的“可领取”按钮。通过
t.find()函数返回的置信度或是否找到来判断。 - OCR文字识别:读取屏幕特定区域的文字,例如金币数量、体力值、倒计时等。TagUI内置了Tesseract OCR引擎,可以通过
t.read()函数实现。
3.2 图像模板的采集与处理技巧
图像模板是我们脚本的“眼睛”。截取模板的好坏直接决定了自动化的成功率。有几个关键原则:
- 区域精准:只截取按钮或标志性图标本身,尽量去掉多余的背景。例如,只截取“领取”两个字所在的按钮区域,而不是整个奖励框。
- 多状态备份:同一个按钮,截取“可点击状态”(亮色)和“不可点击状态”(灰色)两种模板。脚本先找亮色模板,如果找不到,再判断是否找到了灰色模板,从而得知状态。
- 分辨率适配:确保你的游戏模拟器窗口分辨率是固定的,并且截取模板时的分辨率与运行脚本时一致。最好以“窗口化”模式运行模拟器,并固定窗口大小。
- 命名规范:图片文件名要清晰,如
receive_reward_active.png(可领取),receive_reward_inactive.png(已领取)。
处理技巧上,TagUI的find函数可以设置匹配阈值(threshold),默认是0.9。如果游戏画面有轻微色差或抖动,可以适当调低阈值,比如0.8,但过低会增加误识别风险。一个更稳健的做法是,不仅判断是否找到,还判断找到的位置是否在预期区域内。例如,你知道“领取”按钮总是在屏幕右下角某个范围内出现,那么即使find在其他地方找到了相似图案,也可以忽略。
3.3 流程的容错与等待机制
自动化脚本最怕的就是“跑飞了”。因为网络延迟、游戏加载速度不同,脚本必须要有足够的耐心和容错能力。
- 显式等待:在关键操作后,比如点击进入副本后,一定要用
t.wait()等待足够长时间让界面加载完成。等待时间不宜用固定值,最好配合视觉确认。例如,等待直到“战斗开始”按钮出现,或者最多等待10秒。 - 循环查找与超时:对于重要的元素,使用
while循环配合超时机制进行查找。下面是一个通用的安全点击函数示例:
def safe_click(image_path, timeout=10, interval=0.5): “”“安全地查找并点击图片,超时则放弃”“” start_time = time.time() while time.time() - start_time < timeout: if t.find(image_path): t.click(image_path) t.wait(1) # 点击后等待一下 return True t.wait(interval) print(f“在{timeout}秒内未找到图片:{image_path}”) return False- 异常状态恢复:在流程中穿插一些“状态校准”操作。例如,在每个循环开始时,都先尝试回到游戏主城界面。可以设计一个
back_to_home()函数,其逻辑是:连续点击“返回”按钮直到某个只有主城才有的标志性元素(如“挂机”按钮)出现为止。 - 日志记录:在关键步骤和发生异常时,打印日志到文件或控制台。这有助于后期调试和排查脚本为何在某一步停止了。
设计好这些框架性的思路和工具函数后,我们就可以开始编写具体的游戏操作模块了。
4. 核心模块代码实现与详解
我们将把游戏操作封装到一个类中,这样结构更清晰,也便于维护和扩展。我们创建一个GameOperator类,它将是整个自动化脚本的大脑。
4.1 游戏操作类(GameOperator)的骨架
首先在core/game_operator.py中搭建类的基本结构:
import tagui as t import time import os from pathlib import Path class GameOperator: def __init__(self, template_dir=‘image_templates/’): “”“ 初始化游戏操作器 :param template_dir: 图像模板存放的目录路径 “”“ self.template_dir = Path(__file__).parent / template_dir self.running = False # 初始化TagUI,设置可视化模式便于调试 t.init(visual_automation=True, chrome_browser=False) print(“游戏操作器初始化完成。”) def wait_and_find(self, image_name, timeout=10, interval=0.5): “”“等待直到找到某个图片,返回找到的坐标,超时返回None”“” image_path = str(self.template_dir / image_name) start_time = time.time() while time.time() - start_time < timeout: location = t.find(image_path) if location: return location time.sleep(interval) print(f“等待{timeout}秒未找到图像: {image_name}”) return None def safe_click(self, image_name, timeout=10): “”“安全点击,找到才点”“” if self.wait_and_find(image_name, timeout): t.click(str(self.template_dir / image_name)) # 点击后给一个基础等待时间,让游戏响应 t.wait(1) return True return False def run(self): “”“启动主循环”“” self.running = True print(“开始挂机循环...”) while self.running: try: self.daily_loop() # 每个大循环完成后,等待一段时间(例如5分钟)再进行下一次 print(“一个日常循环完成,等待5分钟后开始下一轮。”) t.wait(300) # 等待300秒,即5分钟 except KeyboardInterrupt: print(“\n用户中断,退出挂机。”) self.stop() except Exception as e: print(f“循环执行中出现未知错误: {e}”) # 可以选择记录日志、尝试恢复或停止 self.stop() def stop(self): “”“停止挂机”“” self.running = False t.close() print(“游戏操作器已停止。”) def daily_loop(self): “”“定义一个日常挂机循环,由子步骤组成”“” # 这里调用各个子功能模块 self.check_and_reconnect() self.claim_all_rewards() self.challenge_dungeon() self.use_items() # ... 其他功能这个类提供了基础框架:初始化、安全的查找和点击方法、一个主运行循环和停止机制。visual_automation=True参数会让TagUI在执行时显示一个红色的鼠标轨迹和点击提示,对于调试非常有用,正式运行时可以关闭。
4.2 登录与断线重连模块
这是脚本的起点,也是最需要鲁棒性的地方。我们需要处理几种情况:游戏完全关闭、停留在登录界面、游戏中但断线重连弹窗出现。
class GameOperator: # ... 接上文代码 ... def check_and_reconnect(self): “”“检查游戏状态并确保处于登录后的主界面”“” print(“[状态检查] 检查游戏连接状态...”) # 情况1:检测是否在登录界面(例如有“进入游戏”按钮) if self.wait_and_find(‘login_button.png’, timeout=5): print(“检测到登录界面,执行登录...”) self.safe_click(‘login_button.png’) # 登录后等待长时间加载 t.wait(15) # 等待直到进入主城(出现主城特有标志,如‘home_marker.png’) if not self.wait_and_find(‘home_marker.png’, timeout=30): print(“登录后未能进入主界面,可能登录失败。”) # 这里可以加入重试逻辑或发送通知 return False # 情况2:检测是否有断线重连弹窗(例如“重新连接”按钮) elif self.wait_and_find(‘reconnect_button.png’, timeout=3): print(“检测到断线重连弹窗,尝试重连...”) self.safe_click(‘reconnect_button.png’) t.wait(10) # 等待重连 # 重连后再次确认是否回到主界面 if not self.wait_and_find(‘home_marker.png’, timeout=20): print(“重连后未返回主界面。”) return False # 情况3:假设已在主界面,尝试点击一下屏幕中央,消除可能的意外弹窗 else: print(“未发现登录/断线界面,尝试清理弹窗...”) t.click(‘500, 300‘) # 点击一个相对安全的位置(坐标需根据你的窗口调整) t.wait(1) # 再次确认主界面标志 if self.wait_and_find(‘home_marker.png’, timeout=5): print(“确认已在游戏主界面。”) return True else: print(“无法确认主界面状态,尝试返回主页。”) self.force_back_to_home() return self.wait_and_find(‘home_marker.png’, timeout=15) is not None def force_back_to_home(self, max_retries=10): “”“强制返回主页,通过连续点击返回按钮”“” print(“尝试强制返回主页...”) for i in range(max_retries): # 点击返回按钮,图片‘back_button.png’需要你截取游戏的返回键 if t.find(str(self.template_dir / ‘back_button.png’)): t.click(str(self.template_dir / ‘back_button.png’)) t.wait(1) # 每点一次,检查是否已回到主页 if self.wait_and_find(‘home_marker.png’, timeout=3): print(f“点击{i+1}次返回键后回到主页。”) return True # 如果找不到返回按钮,也可能已经在主页,或者被其他界面遮挡 t.wait(1) print(“强制返回主页失败。”) return False这个模块体现了“防御式编程”的思想:不假设任何状态,而是主动检测并尝试恢复到一个已知的稳定状态(游戏主界面)。这是整个脚本稳定运行的基石。
4.3 自动领取奖励模块
奖励领取是挂机收益的主要来源。我们需要遍历所有可能的奖励入口。
class GameOperator: # ... 接上文代码 ... def claim_all_rewards(self): “”“领取所有可领取的奖励”“” print(“[奖励领取] 开始领取奖励...”) # 1. 领取离线挂机奖励(通常在主界面有明显按钮) self._claim_reward_by_image(‘offline_reward_button.png’, ‘离线奖励’) # 2. 打开邮箱/邮件界面 if self.safe_click(‘mailbox_button.png’, timeout=5): t.wait(3) # 等待邮箱界面加载 # 在邮箱界面内,点击“一键领取” self._claim_reward_by_image(‘claim_all_mail_button.png’, ‘邮件奖励’) # 领取后关闭邮箱界面 t.wait(2) self.safe_click(‘mail_close_button.png’) # 3. 领取每日签到奖励(可能需要导航到活动界面) if self.safe_click(‘activity_button.png’, timeout=5): t.wait(3) self._claim_reward_by_image(‘daily_checkin_button.png’, ‘每日签到’) # 签到界面可能有点击关闭或自动关闭,这里加一个通用返回 self.force_back_to_home() # 4. 领取其他活动奖励(例如七日登录、在线礼包等) # 思路类似:导航到对应界面 -> 查找领取按钮 -> 点击 -> 返回 # 这里可以用一个列表来配置需要检查的活动 activity_list = [‘seven_day_button.png’, ‘online_gift_button.png’] for activity_img in activity_list: if self.safe_click(activity_img, timeout=3): t.wait(2) # 假设活动界面内的领取按钮有通用命名 self._claim_reward_by_image(‘claim_in_activity.png’, f‘活动{activity_img}’) self.force_back_to_home() t.wait(1) print(“[奖励领取] 所有奖励领取流程结束。”) def _claim_reward_by_image(self, image_name, reward_name): “”“内部方法:通过图片识别领取特定奖励”“” if self.safe_click(image_name, timeout=5): print(f“ 成功领取:{reward_name}”) t.wait(2) # 领取奖励后的通用等待 # 有时领取会有二次确认弹窗 if self.wait_and_find(‘confirm_button.png’, timeout=2): self.safe_click(‘confirm_button.png’) t.wait(1) return True else: print(f“ 未找到或无法领取:{reward_name}”) return False这个模块采用了“配置化”的思想,将需要检查的活动按钮图片放在一个列表里循环处理。这样以后要增加新的奖励领取项,只需要更新这个列表和对应的图片模板即可,不需要修改核心逻辑。
4.4 副本挑战与战斗循环模块
挑战副本是消耗体力、获取资源的核心。这里逻辑稍复杂,因为涉及界面导航、次数判断和战斗等待。
class GameOperator: # ... 接上文代码 ... def challenge_dungeon(self, dungeon_type=‘normal’, times=3): “”“ 挑战指定类型的副本 :param dungeon_type: 副本类型,如‘normal’(普通副本),‘boss’(BOSS战) :param times: 挑战次数 “”“ print(f“[副本挑战] 准备挑战{dungeon_type}副本{times}次...”) # 步骤1:导航到副本界面 if not self.safe_click(‘dungeon_entrance_button.png’, timeout=5): print(“ 无法进入副本界面,跳过此模块。”) return t.wait(3) # 等待副本界面加载 # 步骤2:根据类型选择副本标签页 if dungeon_type == ‘boss’: self.safe_click(‘boss_tab_button.png’) t.wait(2) # 假设挑战当前显示的BOSS target_image = ‘challenge_boss_button.png’ else: # 普通副本 self.safe_click(‘normal_tab_button.png’) t.wait(2) # 这里简化处理:挑战第一个可挑战的副本 target_image = ‘challenge_normal_button.png’ # 步骤3:循环挑战指定次数 for i in range(times): print(f“ 开始第{i+1}次挑战...”) if not self.safe_click(target_image, timeout=5): print(f“ 第{i+1}次挑战失败,可能体力不足或次数用完。”) break # 步骤4:处理挑战确认(如果有) if self.wait_and_find(‘challenge_confirm_button.png’, timeout=3): self.safe_click(‘challenge_confirm_button.png’) # 步骤5:等待战斗开始并完成 self._wait_for_battle_complete() # 步骤6:战斗结束,处理结果界面(点击“继续”或“返回”) t.wait(3) # 等待结算界面出现 self.safe_click(‘battle_continue_button.png’) # 点击继续 # 或者点击返回,取决于游戏设计 # self.safe_click(‘battle_back_button.png’) t.wait(2) # 战斗间隔等待 # 步骤7:每次挑战后检查是否还在副本界面,如果不在则返回 if not t.find(str(self.template_dir / ‘dungeon_entrance_button.png’)): print(“ 意外退出副本界面,尝试重新进入...”) self.force_back_to_home() if not self.safe_click(‘dungeon_entrance_button.png’, timeout=5): break # 如果连入口都找不到了,就终止循环 # 步骤8:无论完成与否,最后都尝试返回主界面 print(“[副本挑战] 挑战流程结束,返回主界面。”) self.force_back_to_home() def _wait_for_battle_complete(self, timeout=60): “”“等待一场战斗结束,通过检测战斗界面消失或‘胜利’标志出现”“” print(“ 进入战斗等待...”) start_time = time.time() # 方法1:检测战斗界面特有的元素(如暂停按钮)是否消失 battle_marker = str(self.template_dir / ‘battle_pause_button.png’) # 方法2:检测战斗结束标志(如胜利图标)是否出现 victory_marker = str(self.template_dir / ‘victory_flag.png’) while time.time() - start_time < timeout: # 如果战斗结束标志出现,说明打完了 if t.find(victory_marker): print(“ 检测到战斗胜利。”) t.wait(2) # 等待胜利动画 return True # 如果战斗界面标志消失了,也可能说明打完了(比如快速跳过) if not t.find(battle_marker): print(“ 战斗界面已消失,判断战斗结束。”) t.wait(1) return True time.sleep(2) # 每2秒检查一次 print(f“ 战斗等待超时({timeout}秒),强制跳出。”) return False这个模块的关键在于_wait_for_battle_complete函数。等待战斗结束不能使用固定的sleep,因为战斗时长可能因阵容、关卡而变化。这里采用了混合判断:既检测“胜利”标志的出现,也检测“战斗界面”元素的消失,双重保险,并设置了超时机制防止脚本卡死。
4.5 使用道具与加速模块
自动使用经验药水、金币袋,以及使用快速战斗加速券,可以进一步提升挂机效率。
class GameOperator: # ... 接上文代码 ... def use_items(self): “”“使用背包中的消耗品”“” print(“[使用道具] 开始检查并使用道具...”) # 打开背包界面 if not self.safe_click(‘bag_button.png’, timeout=5): print(“ 无法打开背包,跳过道具使用。”) return t.wait(2) # 切换到消耗品标签页(图片需要根据游戏截取) self.safe_click(‘consumable_tab_button.png’) t.wait(1) # 定义需要使用的道具列表(图片文件名, 使用后确认按钮图片) items_to_use = [ (‘exp_potion.png’, ‘use_confirm_button.png‘), # 经验药水 (‘gold_bag.png’, ‘use_confirm_button.png‘), # 金币袋 (‘quick_battle_ticket.png’, ‘use_confirm_button.png‘), # 加速券 ] for item_img, confirm_img in items_to_use: print(f“ 尝试使用道具:{item_img}”) # 点击道具图标 if self.safe_click(item_img, timeout=3): t.wait(1) # 点击使用按钮(假设使用按钮是固定的‘use_button.png’) if self.safe_click(‘use_button.png’, timeout=2): t.wait(1) # 处理使用确认弹窗 if self.wait_and_find(confirm_img, timeout=2): self.safe_click(confirm_img) print(f“ 成功使用。”) t.wait(1) # 使用后,界面可能还在道具详情,点击空白处关闭 t.click(‘400, 300‘) # 点击一个空白区域坐标 t.wait(1) else: print(f“ 未找到使用按钮,可能该道具无法使用或已用完。”) else: print(f“ 未找到道具或道具数量为0。”) # 关闭背包界面 self.safe_click(‘bag_close_button.png’) print(“[使用道具] 道具使用流程结束。”)这个模块的实现相对直接,核心是遍历一个预定义的道具列表。这里有一个细节:使用道具后,游戏可能会弹出确认窗口,也可能直接使用。我们的代码处理了这两种情况。点击空白处关闭详情窗口是一个实用的技巧,可以避免界面堆叠。
5. 主程序整合、调度与优化
各个功能模块已经就绪,现在我们需要一个主程序来把它们串联起来,并加入更智能的调度和健壮性处理。
5.1 主程序入口与配置化
我们在src/main.py中创建主程序。为了让脚本更灵活,我们可以引入一个简单的配置文件(比如config.ini)来控制行为。
# main.py import time import signal import sys from core.game_operator import GameOperator def main(): print(“=== 《咸鱼之王》自动化挂机脚本启动 ===”) print(“提示:请确保游戏模拟器窗口已打开并置于前台。”) print(“按 Ctrl+C 可以安全停止脚本。\n”) # 创建游戏操作实例 bot = GameOperator(template_dir=‘core/image_templates/’) # 注册信号处理,使Ctrl+C能优雅退出 def signal_handler(sig, frame): print(‘\n接收到中断信号,正在停止...’) bot.stop() sys.exit(0) signal.signal(signal.SIGINT, signal_handler) try: # 首先,尝试将游戏恢复到主界面状态 if not bot.check_and_reconnect(): print(“初始状态恢复失败,脚本退出。”) bot.stop() return # 启动主循环 bot.run() except Exception as e: print(f“主程序运行出错: {e}”) bot.stop() if __name__ == ‘__main__’: main()这个主程序非常简单:初始化、设置优雅退出、执行一次状态恢复,然后启动无限循环。真正的调度逻辑在GameOperator.run()和daily_loop()方法里。
我们可以让循环更智能。比如,不是固定每5分钟执行全套日常,而是根据游戏内活动的冷却时间动态调整。这需要OCR读取屏幕上的时间信息。这里提供一个扩展思路:在daily_loop()末尾,使用OCR识别游戏内某个日常活动的刷新倒计时,然后根据这个时间来计算下一次执行循环的等待时间。
5.2 异常处理与日志增强
目前的异常处理还比较基础。在生产环境中,我们需要更完善的日志记录,以便排查问题。 在GameOperator类中,我们可以增加一个日志记录器:
import logging class GameOperator: def __init__(self, template_dir=‘image_templates/’, log_file=‘bot.log’): # ... 其他初始化 ... self.setup_logging(log_file) def setup_logging(self, log_file): “”“配置日志记录”“” logging.basicConfig( level=logging.INFO, format=‘%(asctime)s - %(name)s - %(levelname)s - %(message)s’, handlers=[ logging.FileHandler(log_file, encoding=‘utf-8’), logging.StreamHandler() # 同时输出到控制台 ] ) self.logger = logging.getLogger(‘XianYuBot’) # 然后将所有print替换为self.logger.info/warning/error # 例如:print(“开始挂机循环...”) -> self.logger.info(“开始挂机循环...”)同时,在daily_loop和各个子函数中,用try...except包裹可能出错的部分,记录错误但不轻易让整个脚本崩溃,而是尝试恢复或跳过当前任务。
def daily_loop(self): tasks = [ (‘状态检查’, self.check_and_reconnect), (‘领取奖励’, self.claim_all_rewards), (‘副本挑战’, lambda: self.challenge_dungeon(‘normal’, 3)), (‘使用道具’, self.use_items), ] for task_name, task_func in tasks: self.logger.info(f“开始执行任务: {task_name}”) try: task_func() except Exception as e: self.logger.error(f“任务[{task_name}]执行失败: {e}”, exc_info=True) # 失败后尝试回到主界面,避免状态混乱 self.force_back_to_home() self.logger.info(f“任务[{task_name}]执行完毕”) t.wait(2) # 任务间短暂间隔5.3 性能优化与稳定性技巧
运行时间长了,可能会遇到一些性能或稳定性问题。这里分享几个优化点:
- 图像识别加速:TagUI的
find函数默认搜索全屏,比较耗时。如果知道目标大致的屏幕区域,可以使用t.find(‘image.png’, region=(x, y, width, height))来限定搜索范围,大幅提升速度。 - 降低CPU占用:在等待循环中(如
wait_and_find),使用time.sleep(interval)而不是密集循环。interval可以设为0.5秒或1秒,避免不必要的CPU消耗。 - 模板图片管理:图片模板不要太多、太大。定期检查并更新模板,因为游戏更新可能会导致界面变化。可以写一个简单的脚本,在启动时检查所有模板图片是否存在。
- 模拟器窗口聚焦:确保脚本运行时,游戏模拟器窗口是激活状态。可以配合使用
pygetwindow这样的库来查找并聚焦模拟器窗口,但注意不要和TagUI的视觉操作冲突。 - 计划任务:如果你希望电脑在特定时间(比如凌晨)自动运行脚本,可以使用Windows的“任务计划程序”或macOS/Linux的
cron来定时启动你的Python脚本。
6. 常见问题排查与实战心得
即使代码写得再严谨,在实际运行中还是会遇到各种意想不到的问题。下面是我在开发和长期运行这类脚本中积累的一些常见问题与解决思路。
6.1 图像识别失败问题排查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 始终找不到图片 | 1. 模板图片路径错误或不存在。 2. 游戏画面与截图时状态不同(如亮度、主题)。 3. 游戏窗口被遮挡、最小化或不在前台。 4. 屏幕分辨率/缩放比例与截图时不一致。 | 1. 打印image_path确认路径正确,检查文件是否存在。2. 重新截取模板,确保游戏状态一致(如关闭特效)。 3. 确保游戏窗口最大化且未被遮挡。运行脚本前手动点击一下窗口。 4. 固定模拟器窗口大小和系统显示缩放设置为100%。 |
| 偶尔识别失败,时好时坏 | 1. 游戏画面有动态特效(如光效、闪烁)。 2. 网络延迟导致界面加载慢。 3. TagUI匹配阈值过高。 | 1. 截取模板时避开动态区域,或等待特效消失后再操作。 2. 在关键操作前增加 t.wait()等待时间。3. 在 t.find()中尝试调低threshold参数,例如t.find(‘img.png’, threshold=0.8)。 |
| 点击位置偏移 | 1. 识别到的坐标不是按钮中心。 2. 屏幕缩放导致坐标计算错误。 | 1. TagUI默认点击找到区域的中心。如果偏移,检查模板图片是否包含了非按钮区域。 2. 确保TagUI和Python脚本在相同的DPI感知环境下运行。对于高分辨率屏,可能需要调整。 |
| 误识别(点错地方) | 1. 模板特征不够独特,与其他界面元素相似。 2. 阈值设置过低。 | 1. 重新截取更具独特性的模板,比如包含按钮上的文字和部分背景。 2. 适当提高 threshold值,或结合region参数限定搜索范围。 |
6.2 TagUI与Python交互的坑
t.init()失败:最常见的原因是TAGUI_HOME环境变量未正确设置,或者TagUI原生包缺失文件。请确保按照第二部分的环境配置步骤操作,并在命令行中直接运行tagui命令能成功。- 脚本执行后无反应:检查是否以管理员身份运行?某些系统设置可能阻止自动化工具控制鼠标键盘。尝试以管理员身份运行你的Python脚本或IDE。
t.keyboard()输入中文无效:TagUI对中文输入支持不佳。如果需要在游戏内输入文字,一个变通方法是:先用t.click()点击输入框,然后使用pyperclip库将中文复制到剪贴板,再用t.keyboard(‘[ctrl]v’)进行粘贴。- 脚本在循环中内存缓慢增长:长时间运行后,如果发现内存占用越来越高,可能是TagUI的Java进程没有完全释放。确保在脚本结束或异常退出时,都调用了
t.close()来清理资源。在主程序中使用try...finally块是个好习惯。
6.3 针对《咸鱼之王》的特定技巧
- 弹窗处理:这类游戏经常有各种突然弹出的活动公告、奖励提示、网络重连框。我们的
check_and_reconnect模块处理了部分,但可能不够。一个更激进但有效的方法是:在每次关键操作(如点击)前,都先扫描屏幕中上部是否存在常见的弹窗关闭按钮(比如一个小的“X”),如果存在就立刻点击关闭。可以把这个逻辑写成一个dismiss_popups()函数,在循环中频繁调用。 - “跳过”与“加速”:很多战斗有跳过动画或加速的按钮。在
_wait_for_battle_complete函数中,可以加入检测并点击“跳过”按钮的逻辑,能节省大量时间。 - 体力与资源监控:通过OCR读取体力值、金币数量,可以实现更智能的决策。例如,当体力大于100时,才去挑战副本;当金币少于某个数值时,优先使用金币袋。这需要你截取显示数字的区域图片,并调试
t.read()函数的准确性(可能需要调整Tesseract的语言包或预处理图片)。
6.4 我的实战心得与建议
- 从小功能开始,逐步集成:不要一开始就想做一个全自动的完美脚本。先实现“自动领取离线奖励”这一个小功能,跑通了,再增加“领取邮件”,像搭积木一样慢慢完善。这样调试起来目标明确,容易成功。
- 准备两套操作逻辑:以视觉识别为主,以绝对坐标点击为辅。对于位置极其固定、且没有替代元素的按钮(比如模拟器窗口右上角的关闭按钮),可以记录其坐标,当视觉识别连续失败时,用坐标点击作为保底。在
safe_click函数里可以加入这个降级逻辑。 - 给脚本加上“眼睛”和“大脑”:“眼睛”就是截图对比,但“大脑”更重要。脚本应该能判断当前处于什么状态,而不是机械地执行一系列点击。多利用
find函数的返回值(是否找到)来做条件判断,这是实现智能化的关键。 - 做好“失控”的准备:自动化脚本可能会因为游戏更新、网络卡顿而“跑飞”,比如不停点击一个错误的地方。在开发阶段,可以把鼠标移到屏幕角落(比如
(10,10))作为紧急停止的热点,并在脚本里周期性检查鼠标是否在那个位置,如果是就暂停脚本。或者更简单,随时准备按Ctrl+C终止程序。 - 尊重游戏规则:最后也是最重要的,了解游戏的使用条款。自动挂机脚本可能违反某些游戏的服务协议,存在封号风险。本项目主要用于学习和研究RPA自动化技术,请合理使用,控制挂机时长,避免影响游戏公平性或自身账号安全。
这个项目从环境搭建到核心模块实现,再到优化排错,完整地展示了一个实用RPA脚本的诞生过程。它不仅仅适用于《咸鱼之王》,其设计思路和代码框架经过调整,可以迁移到很多具有固定界面的桌面应用自动化任务上。希望这份超详细的实战指南,能帮你打开桌面自动化的大门。