跨游戏通用五层高可用智能点击架构
概述
本文方案已在多款出海手游中落地,支持多分辨率、多机型、复杂 UI 动画场景,适用于 Unity/UE 各类手游自动化。 游戏自动化测试中,"点击"是最基础也是最容易失败的操作。由于游戏画面动态变化、光影干扰、分辨率差异、网络延迟等因素,单一的图像匹配经常失败。
我们设计了一套五层降级容错机制,确保每次点击操作都有兜底方案,将单步操作成功率从 60-70% 提升到 95%+。
架构图
robust_tap("image.png", "点击登录按钮", 3, (0.5, 0.7))
│
├── 层级1: 模板快速匹配(exists)
│ ├── 成功 → 使用 fallback 坐标点击 → 返回
│ └── 失败 ↓
│
├── 层级2: 等待后匹配(wait + exists)
│ ├── 成功 → 点击 → 返回
│ └── 超时 ↓
│
├── 层级2.5: 降阈值重试(threshold 0.72 → 0.60)
│ ├── 成功 → 优先用 fallback 坐标点击 → 返回
│ └── 失败 ↓
│
├── 层级3: AI 智能推断
│ ├── 查询 AI 记忆(历史成功坐标)
│ ├── 使用 fallback 坐标(手动标注)
│ ├── 关键词位置推断("右上角""下方"等)
│ ├── 点击后验证(图像是否消失)
│ ├── 成功 → 记录到记忆 → 返回
│ └── 失败 ↓
│
├── 层级4: 清遮挡重试
│ ├── 点击屏幕中心(关闭可能的弹窗)
│ ├── 再次 exists 查找
│ ├── 成功 → 点击 → 返回
│ └── 失败 ↓
│
└── 层级5: 坐标兜底(fallback_xy)
├── 将相对坐标转为像素坐标
├── 带 ADB 重连的安全点击
├── 成功 → 返回
└── 失败 → 返回 False
各层级详解
层级1: 模板快速匹配
原理: 直接用 Airtest 的 exists() 做一次快速截图+模板匹配。
耗时: 1-2 秒
代码逻辑:
p0 = exists(Template("image.png", threshold=0.72))
if p0:
# 优先使用 fallback 坐标(比匹配位置更准确)
if fallback_xy:
px, py = ratio_to_pixels(fallback_xy)
touch((px, py))
else:
touch(p0)
return True
为什么优先用 fallback 坐标: 模板匹配返回的是图像中心点,但游戏中按钮的"可点击区域"可能不在图像中心。fallback 坐标是手动标注的精确位置。
成功率: 约 60-70%
层级2: 等待后匹配
原理: 给一个超时时间,在超时内反复截图匹配,等待目标出现。
耗时: 最多等待 primary_timeout 秒(默认5秒)
代码逻辑:
wait(Template("image.png"), timeout=5, interval=0.6)
p = exists(Template("image.png"))
if p:
touch(p)
return True
适用场景: 目标元素有动画、加载延迟,需要等几秒才出现。
成功率: 在层级1失败的基础上,额外挽回 10-15%
层级2.5: 降阈值重试
原理: 将模板匹配的相似度阈值从 0.72 降低到 0.60,容忍更大的图像差异。
耗时: 1-2 秒
代码逻辑:
t_loose = Template("image.png", threshold=0.60)
p_loose = exists(t_loose)
if p_loose:
# 降阈值匹配的位置可能不准,优先用 fallback 坐标
if fallback_xy:
px, py = ratio_to_pixels(fallback_xy)
touch((px, py))
else:
touch(p_loose)
return True
为什么需要: 游戏画面经常有光影变化、天气效果、UI 动画,导致同一个按钮在不同时刻的截图相似度不同。降低阈值可以容忍这些变化。
风险: 可能匹配到错误的位置,所以优先使用 fallback 坐标而不是匹配位置。
成功率: 额外挽回 5-10%
层级3: AI 智能推断
原理: 当图像识别完全失败时,用 AI 能力推断目标位置。
耗时: 1-3 秒
三个子策略:
3a. AI 记忆查询
memory = get_ai_memory()
result = memory.recall("点击登录按钮", "image.png", resolution)
if result:
touch((result['x'], result['y']))
return True
从历史运行记录中查找"这个步骤上次成功点击的坐标"。记忆存储在 reports/ai_memory.json,格式:
{
"md5_key": {
"description": "点击登录按钮",
"image_name": "image.png",
"success_count": 5,
"fail_count": 1,
"best_coord": {"x": 0.5, "y": 0.7},
"best_confidence": 0.95
}
}
3b. fallback 坐标推断
if fallback_xy:
px, py = ratio_to_pixels(fallback_xy)
return {"x": px, "y": py, "confidence": 0.9}
使用代码中手动标注的 fallback 坐标,置信度 0.9。
3c. 关键词位置推断
description = "点击右上角的跳过按钮"
if "右上" in description:
x, y = w * 0.85, h * 0.1
elif "下方" in description:
x, y = w * 0.5, h * 0.85
elif "中间" in description:
x, y = w * 0.5, h * 0.5
根据步骤描述中的方位词推断大致位置。
点击后验证
AI 推断的坐标可能不准,所以点击后会验证:
touch((ai_x, ai_y)) # 点击推断位置
sleep(0.8)
verify = exists(Template("image.png"))
if verify is None:
# 图像消失了,说明点击成功
memory.remember_success(...) # 记录到记忆
return True
else:
# 图像还在,点击可能失败
memory.remember_failure(...) # 记录失败
# 继续下一层级
成功率: 额外挽回 10-15%
层级4: 清遮挡重试
原理: 可能有弹窗、公告、奖励领取界面遮住了目标。先点击屏幕中心关闭遮挡,再尝试匹配。
耗时: 2-3 秒
代码逻辑:
# 点击屏幕中心,尝试关闭弹窗
touch((0.5, 0.5))
sleep(0.5)
# 再次查找目标
p2 = exists(Template("image.png"))
if p2:
touch(p2)
return True
适用场景: 游戏中经常出现的奖励弹窗、活动公告、系统提示等。
成功率: 额外挽回 3-5%
层级5: 坐标兜底
原理: 所有智能方法都失败了,直接使用手动标注的 fallback 坐标点击。
耗时: 1 秒
代码逻辑:
if fallback_xy:
fx, fy = fallback_xy # 如 (0.5, 0.7)
px = int(fx * screen_width) # 转为像素
py = int(fy * screen_height)
# 带 ADB 重连的安全点击
safe_touch_with_retry((px, py), max_retries=3)
return True
return False # 所有层级都失败
为什么用相对坐标: 相对坐标 (0.5, 0.7) 在任何分辨率下都指向屏幕的同一个位置(中间偏下),自动适配不同设备。
ADB 重连机制: 远程设备可能断开连接,点击时会自动检测并重连:
def safe_touch_with_retry(target, max_retries=3):
for attempt in range(max_retries):
try:
touch(target)
return True
except DeviceNotFoundError:
reconnect_adb_device() # 自动重连
continue
return False
成功率: 几乎 100%(只要坐标标注正确)
成功率统计
| 层级 | 累计成功率 | 说明 |
|---|---|---|
| 层级1(模板匹配) | 65% | 基础能力 |
| + 层级2(等待匹配) | 78% | 处理加载延迟 |
| + 层级2.5(降阈值) | 85% | 容忍画面变化 |
| + 层级3(AI推断) | 93% | 智能兜底 |
| + 层级4(清遮挡) | 95% | 处理弹窗干扰 |
| + 层级5(坐标兜底) | 98%+ | 最终保障 |
调用方式
# 标准调用
robust_tap(
"tpl1763446847616.png", # 模板图片文件名
"点击右上角的跳过按钮", # 步骤描述(供 AI 推断使用)
3, # 等待超时时间(秒)
(0.950, 0.068) # fallback 坐标(相对坐标)
)
# 只用坐标点击(没有模板图片时)
robust_tap_xy((0.5, 0.7))
设计原则
1. 逐级降级,快速返回
命中率高的方法放前面,耗时长的方法放后面。大部分情况在层级1-2就成功返回,不会走到层级5。
2. 优先使用 fallback 坐标
模板匹配返回的坐标可能偏移(光影、动画影响),手动标注的 fallback 坐标更可靠。在层级1、2.5、5中都优先使用 fallback。
3. AI 记忆持续学习
每次成功/失败都记录到 ai_memory.json。随着运行次数增加,AI 记忆的准确率越来越高:
第1次运行: 记忆命中率 0%,依赖图像匹配
第5次运行: 记忆命中率 50%,一半步骤直接用记忆坐标
第10次运行: 记忆命中率 80%,大部分步骤秒过
4. ADB 断连自动恢复
远程设备连接不稳定时,自动检测断连并重连:
点击失败 → 检测 "device not found"
→ adb disconnect + adb connect
→ 重试点击(最多3次)
5. 每一层都有日志
便于问题定位:
[robust_tap] 层级1: 等待 3.0秒查找: image.png
[robust_tap] 层级1失败
[🔄] 尝试层级2.5 (降阈值重试): image.png
[🔄] 层级2.5匹配位置 (726, 160),使用 fallback 坐标 (611, 1467)
[✅] 层级2.5成功 (降阈值0.60找到): image.png
相关文件
| 文件 | 说明 |
|---|---|
utils/test_base.py | TestBase 基类中的 robust_tap 实现 |
utils/ai_memory.py | AI 记忆学习模块 |
reports/ai_memory.json | AI 记忆数据文件 |
tests/android/lfgame/test_01lf_regression.py | 各游戏测试脚本中的独立实现 |