跨游戏通用五层高可用智能点击架构

44 阅读7分钟

跨游戏通用五层高可用智能点击架构

概述

本文方案已在多款出海手游中落地,支持多分辨率、多机型、复杂 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.720.60)
  │     ├── 成功 → 优先用 fallback 坐标点击 → 返回
  │     └── 失败 ↓
  │
  ├── 层级3: AI 智能推断
  │     ├── 查询 AI 记忆(历史成功坐标)
  │     ├── 使用 fallback 坐标(手动标注)
  │     ├── 关键词位置推断("右上角""下方"等)
  │     ├── 点击后验证(图像是否消失)
  │     ├── 成功 → 记录到记忆 → 返回
  │     └── 失败 ↓
  │
  ├── 层级4: 清遮挡重试
  │     ├── 点击屏幕中心(关闭可能的弹窗)
  │     ├── 再次 exists 查找
  │     ├── 成功 → 点击 → 返回
  │     └── 失败 ↓
  │
  └── 层级5: 坐标兜底(fallback_xy)
        ├── 将相对坐标转为像素坐标
        ├── 带 ADB 重连的安全点击
        ├── 成功 → 返回
        └── 失败 → 返回 False

image.png

各层级详解

层级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.pyTestBase 基类中的 robust_tap 实现
utils/ai_memory.pyAI 记忆学习模块
reports/ai_memory.jsonAI 记忆数据文件
tests/android/lfgame/test_01lf_regression.py各游戏测试脚本中的独立实现