(转载)虚幻引擎3--Actor 更新(Actor Ticking)
文档概要: 说明了 如何/何时 更新 Actor。
文档变更记录:由Joe Graf创建。
概述
Actors在每帧之间经过的时间中按照它们在World(世界)的actor列表中出现的顺序基于每帧进行更新。现在,Actors在3个阶段进行更新: 在异步处理之前、异步处理过程中以及异步处理后。被更新的Actor所在的组是由TickGroup编号控制的。如果Actors需要在任何异步工作(物理及后面的其它操作)之前进行更新,那么需要把它分配到TG_PreAsyncWork组中。如果操作失败,那么将会导致不正确的行为 和/或 丢掉一帧的问题。可以同物理及其它线程并行更新的Actors被分配给 TG_DuringAsyncWork组。如果在那个组中的任何Actor调用了接触我们的刚体物理数据的函数,那么将会向日志中输出一个错误,并忽略那个函数调用。这可能会导致内存泄露、无效的场景状态等,所以必须对其进行修复。同样该现象也应用于SpawnActor(), MoveActor(), SetLocation(), SetCollision()等。这些函数将会继续执行,而不会影响物理线程。它们也会向日志中输出错误。最后,依赖于先更新物理的Actor设置它们的组为TG_PostAsyncWork。在这个组中的Actors为了呈现精确的世界状态(车辆、布娃娃等),它们依赖于物理仿真的结果。在这个阶段可以调用物理和所有Unreal函数来进行移动、生成对象以及碰撞。
这里是把Actors放到更新组中的优势/劣势列表:
TG_PreAsyncGroup
+可以安全地调用任何Unreal函数或Novodex的物理函数。
+更新物理仿真的位置、旋转度等。
-在最后一帧的物理仿真结果上进行操作。
-在处理过程中没有并行处理。
TG_DuringAsyncGroup
+可以和物理仿真平行发生。
- 不能安全地调用某些Unreal函数及所有的写入到场景数据中的Novodex的物理函数(但是可以读取场景数据)。
TG_PostAsyncGroup
+可以安全地调用任何Unreal函数或Novodex的物理函数。
+ 在当前帧的物理仿真数据上进行操作。
-在处理过程中没有并行处理。
所以把一个Actor放到给定列表中的一般规则是:
- 如果它改变碰撞、产生可碰撞的actors,写入到Novodex数据中,那么它应该放入到TG_PreAsyncWork中。一般这样的Actors是Pawns、Weapons(武器)及某些刚体类。
- 如果Actor不能改变碰撞、可以产生可碰撞的Actors(不可碰撞的Actors也可以)或者可以改变Novodex数据,那么Actor应该放入到TG_DuringAsyncWork组中。这是最好的地方,因为更新Actors所消耗的时间隐藏了任何花费在仿真物理所消耗的时间,比如这里2毫秒的更新时间意味着大约有长达2毫秒的仿真时间是没有的。粒子系统、音频、AI处理可以放入到这个组中。
- 如果Actor需要在更新它的Unreal数据前从Novodex 获得数据,那么它必须放到 TG_PostAsyncWork组中。车辆和布娃娃一般会放入到这个组中。
Actor 产生
产生一个Actor,意味着不管该Actor的更新组是什么,它(及它的所有组件)都会在产生它的那个组中进行更新。在下一帧中,新产生的Actor将会在它的正确的更新组中进行更新监测。其中有一个例外是在异步任务中产生的Actor,它的更新将会延迟,直到TG_PostAsyncWork发生为止。
组件更新
和Actors可以被分割到不同的更新组中一样,组件也可以。前面,Actor在Actor的tick(更新)过程中更新它的所有组件。这个过程仍然会发生,但是需要位于不同组中的组件要放在管理它们的列表中,当更新时, 组件应该使用和分配Actor到一个更新组中一样的标准来分配更新组。
Tick(更新)的代码流程
游戏线程 | 物理线程 |
---|---|
遍历世界的Actor列表,并更新这些是 TG_PreAsyncWork组的Actors,然后延迟所有其它Actors的更新 | 空闲 |
对于每个更新过的Actor,遍历所组件列表来更新TG_PreAsyncWork组中的组件,延迟所有其它组件的更新 | 空闲 |
通知启动物理线程 | 启动仿真 |
更新每个位于TG_DuringAsyncWork列表中的Actor | 仿真物理 |
对于Actor中的每个组件,根据需要更新延迟的组件。 | 仿真物理 |
更新每个延迟直到TG_DuringAsycnWork出现的才进行更新的组件 | 仿真物理 |
直到物理处理完成之前处于阻塞状态 | 返回仿真结果 |
更新位于TG_PostAsyncWork列表中的每个Actor。 | 空闲 |
更新Actor中的每个组件 | 空闲 |
更新每个延迟更新直到TG_PostAsycnWork出现的才进行更新的组件 | 空闲 |
渲染& 重复 | 空闲 |
详细的代码流程 (超出更新之外)
代码流程(每帧)
Object ticking[对象更新] Pre-physics[在物理之前] controller[控制器] (输入) pawn (脚本) Components[组件] SkeletalMeshComponent:更新动画、骨架控制、然后计算矩阵 Physics, async[物理处理过程中、异步] pawn physics[Pawn的物理] Post-physics[物理之后] Camera tick[相机更新] Viewport tick[视口更新] "server travelling[服务器传送数据]" "client travelling[客户端传送数据]" streaming[动态载入] Render[渲染器] Caculate VP matix[计算VP矩阵](在脚本中询问相机,返回缓存的值。) Controller.PreRender[控制器,预渲染] UTPawn.PreRender (对于授权用户使用这个是很好的) Render everything[渲染所有的东西] Audio[音频] Callgraph computation[函数调用图计算]
为什么它如此重要?
理解更新及渲染顺序对于避免延迟一帧的问题以及解决调用顺序依赖的问题是至关重要的。比如,相机在所有更新结束后再进行更新,所以所有相机相关的东西(比如第一人称附加或激光点)都必须在PreRender(预渲染器)中完成。但是有时候,比如,对于依赖于相机位置的 动画 将会有延迟1帧的问题,因为动画在物理处理之前发生,Pawn运动在物理过程中发生,相机(从技术上讲)依赖于Pawn的位置,因此洞眼依赖创建了一个循环。要想解决这些问题,那么则需要在每帧中多次计算某些东西。把相机更新和动画更新移动到后期物理中可以允许使得动画具有正确地更新的相机的位置(相机将会在每帧中更新两次,除非在代码中指定不那么做)。