Unity录制回放
本文的目的是为了介绍Unity手游自动化中录制回放功能的简单实现,实际上Unity 有一个官方东西 Automated QA用来实现录制回放,只不外该东西于21年12月遏制维护,官方的理由说是筹备把精力放在CI 管线上。目前该东西对某些自定义的UI是不撑持的,会呈现一些问题。因此在这里聊下录制回放的简单实现。凡是来说用户的输入按输入源可以分为以下两类:
[*]Pointer操作
[*]Pointer Down
[*]Pointer Up
[*]Pointer Move
[*]Pointer Drag
[*]按键操作
如果按接受源分类的话可以分为以下两类:
[*]UI接受
[*]游戏逻辑接受
从输入源的角度分析,如果我们想要获取用户的输入,只需要简单的在Update里面监听Input类的输入即可,记录下时间和输入内容。录制的问题很轻松就能解决了。
如果要实现回放的话,首先我们需要知道输入的传递路径,简单来说就是系统底层接受硬件输入然后将其传递给应用,应用接受到输入信息后再将其分发。那么从接受源的角度来分析,就会有两条路径:
那么我们就能想到几种改削方式:
[*]伪造系统底层输入
[*]改削Input类,在Input这里伪造输入
[*]对StandaloneInputModule.cs进行改削
对于第一种方式我们需要按照平台去分袂实现,目前我调研到的方案斗劲有限,仅供参考:
[*]Windows平台的话可以使用win32 api去伪造输入
[*]Android平台可以使用adb,只不外感觉adb会很慢,在回放拖拽时可能会有精度问题
理论上讲是可以实现的,只不外一看就很麻烦,我们还是考虑从C#的层面去解决。
对于第二种方式,Input类是C++实现的,因此我们无法简单对其进行改削,目前能考虑的手段就是将Input封装一下,之后获取输入的时候都使用封装好的Input类就行,而且还需要把其他处所使用的Input给替换了。实际上Unity的StandaloneInputModule就是这么干的,这个脚本获取到的所有输入都来自于一个叫BaseInput.cs的脚本,这个脚本就是把Input简单封装了下。
如果不想改削代码,可以考虑第三种方式。实际上我们前面也说了是为了解决手游自动化的录制回放,手游有个特点就是只接受触摸输入,也就是说我们可以不用考虑游戏逻辑的按键输入措置,转而存眷Pointer和UI文本输入。
对于Unity来说,Pointer的操作默认是交给StandaloneInputModule.cs去措置,这个脚本凡是会自动挂载在EventSystem上,而且全局独一。这个类的有一个核心方式Process会被EventSystem在Update中不竭调用(当游戏掉去Focus或暂停时不会调用),这个方式简单来说就是按照当前鼠标的信息(这些信息会从Input类获取)来创建一个PointerEvent,并按照条件将其分发给鼠标下方的UI,触发各种回调(OnPointerDown之类的)。下面这段是Process的代码,可以看到核心的部门就是ProcessTouchEvents和ProcessMouseEvent,后面的部门是措置UI Navi的,这个可以不用管。
public override void Process()
{
if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
return;
bool usedEvent = SendUpdateEventToSelectedObject();
// case 1004066 - touch / mouse events should be processed before navigation events in case
// they change the current selected gameobject and the submit button is a touch / mouse button.
// touch needs to take precedence because of the mouse emulation layer
if (!ProcessTouchEvents() && input.mousePresent)
ProcessMouseEvent();
if (eventSystem.sendNavigationEvents)
{
if (!usedEvent)
usedEvent |= SendMoveEventToSelectedObject();
if (!usedEvent)
SendSubmitEventToSelectedObject();
}
}
我们继续看ProcessTouchEvents的代码,简单来说就是遍历所有的Touch数据,这个input就是上文提到过的BaseInput。StandaloneInputModule的父类PointerInputModule会维护一个dic,存储PointerEventData,也就是说点击开始的时候就会创建一个PointerEventData并添加到dic,松开后就会移除掉。获取到Touch数据后会调用GetTouchPointerEventData方式,这个方式就是去dic里面查找。得到PointerEventData后就会走Press、Move、Drag、Release的措置流程。
private bool ProcessTouchEvents()
{
if (State == RecordState.Replay)
return true;
for (int i = 0; i < input.touchCount; ++i)
{
Touch touch = input.GetTouch(i);
if (touch.type == TouchType.Indirect)
continue;
bool released;
bool pressed;
var pointer = GetTouchPointerEventData(touch, out pressed, out released);
ProcessTouchPress(pointer, pressed, released);
if (!released)
{
ProcessMove(pointer);
ProcessDrag(pointer);
}
else
RemovePointerData(pointer);
}
return input.touchCount > 0;
}
顺带一提PointerEventData还会记录当前按压的GameObj,用的是射线检测获取的。在阅读了ProcessDrag这些函数的代码后会发现基本都是使用ExecuteEvents.ExecuteHierarchy这个方式去把PointerEventData传递给Obj对应的措置函数。
在了解这些后,我们很容易就能想到在这部门代码里面去记录PointerEventData的数据,对比起在Update里面去监听输入,好处就是能过滤很多无效的点击。这里需要注意的是不能简单的在ProcessTouchEvent这里记录,应该到PrcoessDrag这些函数里面成功措置了的部门去记录。凡是来说我们需要记录的数据是位置、时间以及操作,并不需要把整个PointerEventData记录下来。时间的话建议直接用Time.time去获取,最后只需要记录每个操作之间的相隔时间即可。
由于StandaloneInputModule是Unity自带的脚本,我们并不便利改削,因此我们需要创建个新的Module,这里建议直接把StandaloneInputModule的代码复制粘贴一份,再做些改削就行。之前我们提到InputModule全局独一,因此我们还需要把场景中现有的Module禁用掉。回放这些操作的时候也只需要重写ProcessTouchEvent即可。这里建议把回放的逻辑放到Update里面,并在回放时把之前的措置逻辑禁用掉。需要注意的是不要在回放的逻辑里面写费事操作,容易影响拖拽精度。
在回放的时候我们要按照记录的数据生成Touch,而不是PointerEventData。在生成Touch的时候填入pos以及TouchPhase即可,fingerId的话默认填0就行,如果有多指操作的话这里需要注意下。
到目前为止我们已经措置触摸操作,接下来还需要措置输入操作。
对于Unity来说,输入操作一般都是由InputField进行措置,那么我们可以监听所有常用按键的输入,在监听到按键按下时就去场景中搜索focused的InputField,凡是全局只会有独一一个InputField被Focused。找到后注册onEndEdit回调,记录最后的输入与InputField的UI路径。我们还可以额外记录InputField的位置以防UI路径变换,到时候就可以按照位置去搜索UI。
回放的时候就简单多了,而且这种文本输入的回放也不像拖拽那样要求精确的时间。
页:
[1]