Skip to content

[Unity][Poco]基于 Poco 框架实现 UAutoSDK 的 DebugMode 录制模式 #40

@nzcv

Description

@nzcv

[Unity][Poco]基于 Poco 框架实现 UAutoSDK 的 DebugMode(录制模式)

场景:参考 UAutoSDK UAutoRuner_UGUI.cs 里 DebugMode 的实现原理,在一个基于 Poco(PocoManager + AsyncTcpServer + SimpleProtocolFilter)的 Unity 自动化框架里,落地一套等价的「录制模式」:开启后服务端每帧轮询输入,把用户对 UGUI 控件的操作打包后主动推送给客户端。

DebugMode 实现原理(来自 UAutoSDK)

  1. 用 4 个指令控制开关:debugMode / pauseDebugMode / resumeDebugMode / stopDebugMode
  2. 开启后启动一个协程,每帧轮询鼠标/触摸输入。
  3. 按下时通过 EventSystem 射线检测反查真正响应点击的 UI 控件(MockUpPointerInputModule,用 ExecuteHierarchy(pointerDownHandler) 拿到 pointerPress,从而得到 Button 本体而非其内部 Image)。
  4. 当选中控件发生变化时,把 path / type / 操作耗时 / 坐标 等信息打包后主动推送给客户端(即录制操作流)。
  5. InputField 在切换选中时记录输入文本;IDragHandler 控件记录起止坐标。
  6. deepSearch 模式返回按下点命中的所有 Graphic(path + instanceId)。
  7. 长按 5 秒或客户端断开时自动退出 DebugMode。

关键差异与适配点

Poco 框架是 RPC 一问一答 模型(PocoManager.Update()rpc.HandleMessage(msg)formatResponse 回包),而 DebugMode 需要单向流式推送。适配做法:

  • Update() 处理消息前记录 activeClient,让 DebugMode 协程知道要把录制数据推给哪个客户端。
  • 协程通过复用现有的 AsyncTcpServer.Send(client.TcpClient, prot.pack(json)) 主动推送,与 UAutoSDK 一致。
  • 序列化用项目已有的 Newtonsoft.JsonJsonConvert.SerializeObject)替代参考里的 JsonMapper
  • 输入部分:项目用的是旧版 Input ManagerProjectSettings.activeInputHandler: 0,且未安装新输入系统包),所以直接用 Input,去掉了 UAutoSDK 里基于反射的新输入系统兼容代码。

注意:DebugMode 是「流式推送」模型——开启后协程会向客户端额外推送非 RPC-response 的录制帧,需要专门的录制端读取,而不是标准 Poco 的一问一答客户端。这是参考实现的固有特性。

改动内容

新增 MockUpPointerInputModule.cs

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class MockUpPointerInputModule : StandaloneInputModule
{
    public static PointerEventData GetPressPointerEventData(Touch touch, PointerEventData previousData = null)
    {
        if (EventSystem.current == null) return null;

        PointerEventData pointerEventData = new PointerEventData(EventSystem.current)
        {
            position = touch.position,
            delta = touch.deltaPosition,
            button = PointerEventData.InputButton.Left,
            pointerId = touch.fingerId
        };

        List<RaycastResult> raycastResults = new List<RaycastResult>();
        EventSystem.current.RaycastAll(pointerEventData, raycastResults);
        RaycastResult raycastResult = FindFirstRaycast(raycastResults);
        pointerEventData.pointerCurrentRaycast = raycastResult;
        pointerEventData.pointerPressRaycast = pointerEventData.pointerCurrentRaycast;
        pointerEventData.pointerPress = ExecuteEvents.ExecuteHierarchy(
            pointerEventData.pointerCurrentRaycast.gameObject,
            pointerEventData,
            ExecuteEvents.pointerDownHandler);

        return pointerEventData;
    }
}

新增 PocoDebugMode.cs(核心)

要点:

  • 持有 MonoBehaviour owner(用于 StartCoroutine/StopCoroutine)、AsyncTcpServerSimpleProtocolFilteractiveClient
  • RPC handler:Start(client, param) / Pause / Resume / Stop
  • DebugModeCoroutine() 每帧轮询,逻辑完整移植自参考实现。
  • 注意:try/catch 块内不能包含 yield,因此 yield return null 与提前 yield break 都放在 try 之外。

输入辅助(旧版 Input Manager):

private bool IsPressDown()
{
    if (Input.GetMouseButtonDown(0)) return true;
    return Input.touchCount == 1 && _curTouchCount == 0;
}

private bool IsPressUp()
{
    if (Input.GetMouseButtonUp(0)) return true;
    return Input.touchCount == 0 && _curTouchCount == 1;
}

private bool IsPress()
{
    if (Input.GetMouseButton(0)) return true;
    return Input.touchCount == 1;
}

private Vector2 GetPressPos()
{
    if (Input.touchCount > 0) return Input.GetTouch(0).position;
    return Input.mousePosition;
}

按下反查控件 + 推送录制数据(节选):

if (IsPressDown())
{
    Vector2 pos = GetPressPos();
    data["press position"] = pos.ToString();
    data["percent position"] = string.Format("({0}, {1})", pos.x / Screen.width, pos.y / Screen.height);

    Touch touch = new Touch { position = pos };
    PointerEventData ped = MockUpPointerInputModule.GetPressPointerEventData(touch);
    if (ped != null && ped.pointerPress != null) lastPressGameObject = ped.pointerPress;
}
else { lastPressGameObject = null; }

// 选中控件变化时打包推送 name/type/time(IDragHandler 记录起点,InputField 记录文本)

深度搜索(命中按下点的所有 Graphic):

foreach (Graphic graphic in GameObject.FindObjectsOfType<Graphic>())
{
    if (graphic == null || !graphic.raycastTarget) continue;
    RectTransform rect = graphic.rectTransform;
    Vector3[] c = GetScreenCoordinates(rect); // 0=左下 2=右上
    if (c[0].x < mousePos.x && c[2].x > mousePos.x && c[0].y < mousePos.y && c[2].y > mousePos.y)
        hits.Add(new Dictionary<string, string> { { "path", GetGameObjectPath(graphic.gameObject) }, { "id", graphic.gameObject.GetInstanceID().ToString() } });
}

修改 PocoManager.cs

private PocoDebugMode debugMode = null;
private TcpClientState activeClient = null;

// server 启动成功后注册 RPC 方法
debugMode = new PocoDebugMode(this, server, prot);
rpc.addRpcMethod("DebugMode", param => debugMode.Start(activeClient, param));
rpc.addRpcMethod("PauseDebugMode", param => debugMode.Pause(param));
rpc.addRpcMethod("ResumeDebugMode", param => debugMode.Resume(param));
rpc.addRpcMethod("StopDebugMode", param => debugMode.Stop(param));

// Update() 处理消息前记录当前客户端
activeClient = client;
string response = rpc.HandleMessage(msg);

Unity 需要为新 .cs 生成 .meta(含稳定 GUID)。脚本是在 Editor 外创建的,所以手动补了 .cs.metauuidgen 生成小写无连字符 GUID)。

使用方式

客户端发 RPC:

  • DebugMode / DebugMode("1")(带深度搜索)→ 开启录制,之后服务端持续推送录制到的操作 JSON。
  • PauseDebugMode / ResumeDebugMode → 暂停/恢复。
  • StopDebugMode → 关闭。

推荐组合 / 经验小结

  • 在 RPC 一问一答框架上做单向流式推送时,关键是在消息处理入口把 activeClient 缓存下来,供异步协程使用。
  • 复用框架已有的 pack/Send 通道,避免另起 socket。
  • 先确认项目的输入系统(ProjectSettings.activeInputHandler)再决定是否需要新输入系统反射兼容层——本项目是旧版,省掉一大段反射代码。
  • iterator(协程)里 try/catch 不能包 yield,注意把 yield return/yield break 放在 try 外。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions