|
|
碎碎念,可跳过:
因为现在的功能还很不稳定没时间优化,bug非常多。而且这个功能对于龙脉来说应该是极度破坏游戏平衡,说不定会爆出很多这样刷的初始号。所以应该暂时不会发布。后面看这个帖子的数据吧,如果到楼主心理预期再想想发不发。
(追随!血液!疯狂暗示.jpg)
之前看到好多玩家都在骂这个b游戏推图又臭又长,抄明日方舟都抄不明白,偏偏活动关卡要打到差不多底关才能有最大奖励,太恶心了。本人也深有体会。于是灵机一动做了这个东西出来。有自动推图(可选择是否挑战四星),故事(剧情)跳过,集成论坛的 @cpu251(KUMA)大佬的免战斗自动过关功能。顺便补了一个补充没打过的四星关卡功能自用。
活学活用一下编译原理,把关卡状态建模为状态机,使用状态探针检测当前状态并且防止loading状态卡死;操作方面,截取鼠标点击产生的UI响应并且模拟,就可以让游戏自己玩自己了。
用了之后才意识到之前玩这个b游戏推图浪费太多时间了哈哈。已经用这个推了两大关了(笑)
特别鸣谢闺蜜@alepo
下面是正文:
(让ai润色了一下,结果变成人机了,大家将就看看)
【技术分享】官方三年没做出来的自动推图,我自己给它补上了先说背景:这游戏战斗本身已经能自动部署,但“选关—出击—结算—下一关”还是得人一直守着点。关卡一多,刷起来真的很折磨。
所以我做了一个外层流程助手。目前支持:
- 自动推进新关卡;
- STORY 自动确认并 EXIT,也可以选择保留剧情;
- 普通通关后继续挑战四星;
- 遍历当前区域地图,补齐所有缺失的四星;
- 重复刷当前选中的关卡;
- 自动处理编队、Loading、结算和首通奖励;
- F12 急停和断点恢复;
- 可选接入已有的自动部署;
- 实验性的快速结束战斗功能。
项目基于 Python、Frida 和 IL2CPP 运行时观察实现。这里会讲清整体方法,但省略游戏专用类名、内存字段、调用地址和可直接复制的操作入口,避免脚本被拿去批量滥用。
一开始我也想过按键精灵最直观的方案就是截图找按钮,然后模拟鼠标点击。
但实际做起来问题一大堆:
- 游戏窗口移动后坐标就变了;
- 切换全屏、DPI 或分辨率后模板容易失效;
- Loading 时间不固定;
- 有些按钮看起来出现了,实际上还不能点击;
- 游戏使用了对象池,旧页面的按钮对象可能还活着;
- 自动化运行时基本没法正常使用电脑。
所以后来完全放弃了“看图点坐标”作为主方案,改成直接观察 Unity 内部的界面状态,并复用游戏原本的 UI 业务流程。
这样窗口放在哪里、开多大、是否在前台,原则上都不影响运行。
真正难的不是点击,而是判断状态最初的流程写得很像这样:
点击关卡等待 1 秒点击出击等待 2 秒点击编队出击等待战斗结束点击结算看起来没毛病,实际上非常容易炸。
一次网络波动、一次动画卡顿,后面的操作就会全部错位。最严重时,脚本会把章节列表误判成地图,进入一个没有内容的空面板,然后游戏自己也恢复不了。
后来我把整个流程重写成了“探针驱动状态机”。
简化后的状态大概如下:
quest.map 区域地图quest.list 章节列表quest.detail 关卡详情party.ready 编队已就绪loading 加载中battle 战斗中result.ready 结算页可操作reward.ready 首通奖励可领取story 剧情界面unknown 尚未验证的状态每次操作都必须经过完整闭环:
探针确认前置状态 ↓提交一次 UI 业务请求 ↓等待 Loading 或界面变化 ↓探针确认目标状态确实形成 ↓解除操作锁,进入下一步伪代码大概是这样:
state = probe()if state == QUEST_MAP: target = find_next_available_stage() if target: submit_once("open_stage", target.id) expect(QUEST_LIST, field_id=target.id)elif state == QUEST_LIST: wait_until_controls_are_stable() chapter = choose_target_chapter() if chapter: submit_once("select_chapter", chapter.id) expect(QUEST_DETAIL, chapter_id=chapter.id)elif state == LOADING: # 什么都不做,等探针确认加载完成 passelif state == UNKNOWN: emergency_stop()核心思想其实很简单:
“调用没有报错”不代表“游戏已经成功切换页面”。
真正可信的是后置状态。
如何判断页面“真的准备好了”仅仅找到某个 UI 对象还不够,因为 Unity 经常会保留已经隐藏的旧对象。
其实是龙脉屎山代码太垃圾了,AI不说我来说
我的探针还会检查:
- 对象是否处于 activeInHierarchy;
- 页面初始化标志是否完成;
- 显示/隐藏动画的信号量是否为空闲;
- 按钮是否 active 且 interactable;
- 事件监听器是否已经注册;
- 当前选中的关卡 ID 是否与预期一致;
- 章节行的数据对象是否仍然对应当前页面;
- Loading 进度对象是否仍处于激活状态;
- 同一状态是否连续出现多个采样周期。
例如章节列表刚出现时,画面上已经能看到关卡,但按钮的监听器可能还没装好。此时直接调用,就可能触发原生异常。
所以状态机不会立即操作,而是等列表指纹连续稳定:
fingerprint = frames.map(frame => [ frame.objectId, frame.chapterId, frame.listenerId, frame.buttonId]).join("|");if (fingerprint === previousFingerprint) { stableTicks++;} else { stableTicks = 1;}达到稳定阈值后才继续。
这不是传统意义上的固定 delay,而是等待真实的“控件注册完成”事件特征。
自动推进怎么选关区域地图中,每个大关节点都有自己的业务 ID 和状态。
推进模式只考虑:
- 已开放;
- 可交互;
- 标记为 NEW;
- 尚未被本轮判定为完成。
打开大关后,再从章节列表中判断:
- 是 STORY 还是战斗;
- 是否 NEW;
- 是否已经普通通关;
- 是否缺少第四颗星;
- 当前详情是否已经展开。
这里有个很坑的地方:点击 NEW 战斗后,它的 NEW 标记可能立刻消失。如果脚本只根据 NEW 重新搜索,就会误以为刚才的关卡不存在,然后跳到下一项。
解决方法是:一旦提交了选择请求,就记录预期关卡 ID;后续只相信详情页和探针确认结果,不再依赖已经变化的 NEW 标记。
四星补全不是“再点一次”四星模式分成两种。
第一种是自动推进时顺便补四星:
普通通关→ 锁定刚刚的关卡→ 返回同一大关→ 选择四星挑战→ 完成后解除锁定→ 才允许继续下一关第二种是扫描当前区域地图:
遍历当前区域中的所有已开放大关 ↓进入每个大关的章节列表 ↓查找“已普通通关 + 第四星缺失”的战斗关 ↓逐个挑战四星 ↓本大关无目标后返回地图 ↓继续下一个大关它会跳过:
- STORY;
- 尚未普通通关的关卡;
- 已经四星的关卡;
- 未开放或不可交互的大关。
这里的“当前区域”指当前这张地图,不会继续跳到外层的故事/任务目录。否则扫描量太大,也容易失控。
STORY 为什么也折腾了很久故事关不是直接进入剧情,而是:
选择 STORY→ 出现“要观看故事吗?”→ 点击 Yes→ Loading→ 进入剧情系统→ 点击 EXIT→ 可能出现首通奖励→ 返回章节列表任何一步提前都可能误点到残留的出击按钮。
因此脚本提交 Yes 后,会设置一个“故事进入中”锁。在探针真正看到剧情场景或 Loading 前,不允许再执行其他任务页操作。
EXIT 也不是按坐标,而是先识别剧情系统已加载,再沿正常 UI 事件链退出。
结算页是整个项目里最阴间的部分表面上看,结算页点一下就能离开。实际上它有两个业务阶段:
- 角色立绘、经验和奖励演出阶段;
- 演出结束、允许离开阶段。
这两个阶段画面可能几乎一样。
人工点击时,游戏会收到同一种背景点击事件:
第一次点击:结束演出第二次点击:离开结算所以脚本不能看见 Result 就连点两次,而要观察结算控制器的内部阶段:
演出 active→ 提交第一次背景点击→ 等待状态变为 idle→ 提交第二次背景点击→ 确认结算对象消失如果十五秒后仍未离开,脚本会停止,不会无限重放点击。
这个问题排查了很久,因为“调用成功但页面不动”和“第一下只结束动画”看起来完全一样。
为什么还做了被动探针很多游戏 UI 的真实业务事件,仅靠看反编译结果不容易判断。于是我给工具加了一个纯观察模式。
被动探针会:
- 记录页面状态变化;
- 监听人工点击经过的 UI 业务层;
- 输出动作名称和经过脱敏的参数结构;
- 不调用任何按钮;
- 不移动鼠标;
- 不修改游戏状态。
开发时我会先打开被动探针,再手动完成一次正确操作:
人工点击→ 记录真实事件路径→ 对照前后探针状态→ 在状态机中复现同一业务路径这比盲猜某个 OnClick() 安全得多。
有些按钮直接调用 Unity 的 onClick.Invoke() 会抛异常,但人工操作走的其实是更上层的业务分发链。找到这个差异后,空白面板和闪退问题才明显减少。
异常处理也不能太死板有一次调用大关选择事件时,Frida 报了原生断点异常。但紧接着探针发现章节列表已经正常打开,而且 Field ID 完全匹配。
也就是说:
业务操作已经成功→ 游戏继续执行了一段失效的旧回调→ Frida 捕获异常如果看到异常就立即停,会把成功操作误判为失败。
现在的规则是:
- 如果异常后目标状态没有形成:立即停止;
- 如果异常后目标页面已经稳定形成,且业务 ID 匹配:把它视为“事后异常”,记录警告并继续;
- 绝不对同一操作连续重试。
探针是最终裁判,而不是异常信息,也不是函数返回值。
GUI 和安全措施最后给它套了一个简单 GUI,提供三种模式:
此外还有:
- 是否挑战四星;
- 是否保留 STORY;
- 最大执行次数;
- 等待现有自动战斗或使用实验性战斗适配器;
- 被动探针;
- F12 急停;
- 单实例互斥;
- 运行日志;
- 四星待办和重复目标断点恢复。
单实例很重要。调试时如果残留了两个脚本,它们会同时操作游戏,表现得像状态机发疯一样。
一点感想做完以后最大的感受是:自动化系统的核心根本不是“怎么点按钮”。
真正困难的是:
- 我现在到底在哪个页面?
- 这个页面是真的准备好了,还是只有外壳出现了?
- 刚才的操作是否真正生效?
- Loading 结束后去了哪里?
- 出错时能否安全停下?
- 如何避免同一个请求被提交两次?
如果只是写一个能在自己电脑上跑一次的脚本,坐标点击半小时就能完成。
但想让它在窗口移动、网络波动、动画延迟、对象池残留甚至游戏自身偶发异常的情况下仍然可用,就必须把它当成一个异步状态机来设计。
完整实现中的游戏专用类名、字段、运行时定位和具体调用参数这里不公开。不过我觉得“探针 + 单次提交锁 + 后置状态确认”这个思路,对 Unity UI 自动化、软件测试和桌面流程机器人都很有参考价值。
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
评分
-
查看全部评分
|