[Unity][Poco]基于 Poco 框架实现 UAutoSDK 的 DebugMode(录制模式)
场景:参考 UAutoSDK UAutoRuner_UGUI.cs 里 DebugMode 的实现原理,在一个基于 Poco(PocoManager + AsyncTcpServer + SimpleProtocolFilter)的 Unity 自动化框架里,落地一套等价的「录制模式」:开启后服务端每帧轮询输入,把用户对 UGUI 控件的操作打包后主动推送给客户端。
DebugMode 实现原理(来自 UAutoSDK)
- 用 4 个指令控制开关:
debugMode / pauseDebugMode / resumeDebugMode / stopDebugMode。
- 开启后启动一个协程,每帧轮询鼠标/触摸输入。
- 按下时通过
EventSystem 射线检测反查真正响应点击的 UI 控件(MockUpPointerInputModule,用 ExecuteHierarchy(pointerDownHandler) 拿到 pointerPress,从而得到 Button 本体而非其内部 Image)。
- 当选中控件发生变化时,把
path / type / 操作耗时 / 坐标 等信息打包后主动推送给客户端(即录制操作流)。
InputField 在切换选中时记录输入文本;IDragHandler 控件记录起止坐标。
deepSearch 模式返回按下点命中的所有 Graphic(path + instanceId)。
- 长按 5 秒或客户端断开时自动退出 DebugMode。
关键差异与适配点
Poco 框架是 RPC 一问一答 模型(PocoManager.Update() 里 rpc.HandleMessage(msg) → formatResponse 回包),而 DebugMode 需要单向流式推送。适配做法:
- 在
Update() 处理消息前记录 activeClient,让 DebugMode 协程知道要把录制数据推给哪个客户端。
- 协程通过复用现有的
AsyncTcpServer.Send(client.TcpClient, prot.pack(json)) 主动推送,与 UAutoSDK 一致。
- 序列化用项目已有的
Newtonsoft.Json(JsonConvert.SerializeObject)替代参考里的 JsonMapper。
- 输入部分:项目用的是旧版 Input Manager(
ProjectSettings.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)、AsyncTcpServer、SimpleProtocolFilter、activeClient。
- 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.meta(uuidgen 生成小写无连字符 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 外。
[Unity][Poco]基于 Poco 框架实现 UAutoSDK 的 DebugMode(录制模式)
DebugMode 实现原理(来自 UAutoSDK)
debugMode/pauseDebugMode/resumeDebugMode/stopDebugMode。EventSystem射线检测反查真正响应点击的 UI 控件(MockUpPointerInputModule,用ExecuteHierarchy(pointerDownHandler)拿到pointerPress,从而得到 Button 本体而非其内部 Image)。path / type / 操作耗时 / 坐标等信息打包后主动推送给客户端(即录制操作流)。InputField在切换选中时记录输入文本;IDragHandler控件记录起止坐标。deepSearch模式返回按下点命中的所有Graphic(path + instanceId)。关键差异与适配点
Poco 框架是 RPC 一问一答 模型(
PocoManager.Update()里rpc.HandleMessage(msg)→formatResponse回包),而 DebugMode 需要单向流式推送。适配做法:Update()处理消息前记录activeClient,让 DebugMode 协程知道要把录制数据推给哪个客户端。AsyncTcpServer.Send(client.TcpClient, prot.pack(json))主动推送,与 UAutoSDK 一致。Newtonsoft.Json(JsonConvert.SerializeObject)替代参考里的JsonMapper。ProjectSettings.activeInputHandler: 0,且未安装新输入系统包),所以直接用Input,去掉了 UAutoSDK 里基于反射的新输入系统兼容代码。改动内容
新增
MockUpPointerInputModule.cs新增
PocoDebugMode.cs(核心)要点:
MonoBehaviour owner(用于StartCoroutine/StopCoroutine)、AsyncTcpServer、SimpleProtocolFilter、activeClient。Start(client, param)/Pause/Resume/Stop。DebugModeCoroutine()每帧轮询,逻辑完整移植自参考实现。try/catch块内不能包含yield,因此yield return null与提前yield break都放在 try 之外。输入辅助(旧版 Input Manager):
按下反查控件 + 推送录制数据(节选):
深度搜索(命中按下点的所有 Graphic):
修改
PocoManager.cs使用方式
客户端发 RPC:
DebugMode/DebugMode("1")(带深度搜索)→ 开启录制,之后服务端持续推送录制到的操作 JSON。PauseDebugMode/ResumeDebugMode→ 暂停/恢复。StopDebugMode→ 关闭。推荐组合 / 经验小结
activeClient缓存下来,供异步协程使用。pack/Send通道,避免另起 socket。ProjectSettings.activeInputHandler)再决定是否需要新输入系统反射兼容层——本项目是旧版,省掉一大段反射代码。try/catch不能包yield,注意把yield return/yield break放在 try 外。