在构造动画蓝图时,您应牢记一些有效的做法,确保项目动画尽可能流畅运行。 在默认情况下,一些做法是受到系统支持的,而其他做法则要求您考虑动画蓝图的搭建方法。 其他工作可以通过C++执行,这样您也能够控制动画的更新方式和时间,从而提高性能。
多线程动画更新
此功能允许在工作线程中运行更多动画工作,默认为启用状态,您可以在项目设置中找到该选项:
-
在 项目设置(Project Settings) 中的 常规设置(General Settings)> 动画蓝图(Anim Blueprints) 下面,确保启用 允许多线程动画更新(Allow Multi Threaded Animation Update)。
该选项控制默认情况下,是否允许在非游戏线程上执行动画蓝图图形更新。 还允许在动画蓝图编译器中进行一些额外检查,并在尝试执行不安全的操作时发出警告。 在 动画蓝图(Animation Blueprints) 中,也需要确保设置为 使用多线程动画更新(Use Multi Threaded Animation Update)。
-
在动画蓝图(Animation Blueprints)中的 类设置(Class Settings) 下面,确保启用 使用多线程动画更新(Use Multi Threaded Animation Update)。
其主要原因是为了更严密地控制各个线程中的数据访问。为此,大部分动画图形访问的数据已经从UAnimInstance
移至一个新的结构,名为FAnimInstanceProxy
。
该代理结构存放有关UAnimInstance
的大量数据。
一般而言,不能从动画图形节点(Update/Evaluate calls)访问或修改UAnimInstance
,因为它们可以在其他线程上运行。
有一些锁定封装器(GetProxyOnAnyThread
和GetProxyOnGameThread
)可以在任务运行期间阻止访问FAnimInstanceProxy
。
主要想法是在最差的情况下,任务等待完成,然后才允许从代理读取或写入数据。
从动画图形的角度而言,从动画节点只能访问FAnimInstanceProxy
,而不能访问UAnimInstance
。
对于FAnimInstanceProxy::PreUpdate
或FAnimInstaceProxy::PreEvaluateAnimation
中的每次更新,必须与代理交换数据(通过缓冲、复制或其他策略)。
接下来需要被外部对象访问的任何数据应该从FAnimInstanceProxy::PostUpdate
中的代理进行交换/复制。
这与UAnimInstance
的一般用法冲突,在一般用法中,可以在任务运行期间从其他类访问成员变量。
建议最好不要从其他类直接访问动画实例。动画实例应从其他位置拉取数据。
自定义原生动画实例示例
以下代码块示例展示的是如何使用新的FAnimInstanceProxy
来构建自定义原生动画实例类,授予对内部工作线程的访问权,并避免在代理和该实例之间复制共享数据。
USTRUCT()
struct FExampleAnimInstanceProxy : public FAnimInstanceProxy
{
GENERATED_BODY()
FExampleAnimInstanceProxy()
: FAnimInstanceProxy()
{}
FExampleAnimInstanceProxy(UAnimInstance* Instance);
virtual void Update(float DeltaSeconds) override
{
// Update internal variables
MovementAngle += 1.0f * DeltaSeconds;
HorizontalSpeed = FMath::Max(0.0f, HorizontalSpeed - DeltaSeconds);
}
public:
UPROPERTY(Transient, BlueprintReadWrite, EditAnywhere, Category = "Example")
float MovementAngle;
UPROPERTY(Transient, BlueprintReadWrite, EditAnywhere, Category = "Example")
float HorizontalSpeed;
};
UCLASS(Transient, Blueprintable)
class UExampleAnimInstance : public UAnimInstance
{
GENERATED_UCLASS_BODY()
private:
// The AllowPrivateAccess meta flag will allow this to be exposed to Blueprint,
// but only to graphs internal to this class.
UPROPERTY(Transient, BlueprintReadOnly, Category = "Example", meta = (AllowPrivateAccess = "true"))
FExampleAnimInstanceProxy Proxy;
virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override
{
// override this to just return the proxy on this instance
return &Proxy;
}
virtual void DestroyAnimInstanceProxy(FAnimInstanceProxy* InProxy) override
{
}
friend struct FExampleAnimInstanceProxy;
};
动画快速路径
动画快速路径 提供了一种在 动画图形 更新中优化变量访问的方法。 这使引擎能够在内部复制参数,而不是执行蓝图代码(包括调用蓝图虚拟机)。 编译器当前可以优化以下构造函数:成员变量、否定布尔成员变量和嵌套结构成员。
在默认情况下,项目设置(Project Settings) 中会启用动画快速路径选项:
-
在 项目设置(Project Settings) 中的 常规设置(General Settings) 和 动画蓝图(Anim Blueprints) 下面,确保启用 优化动画蓝图成员变量访问(Optimize Anim Blueprint Member Variable Access)。
要使用动画快速路径,在动画蓝图的动画图形内部,确保未在执行蓝图逻辑。 在下图中,我们将读取若干浮点值,以用于驱动多个混合空间资源和产生最终动画姿势的一个混合节点。 右上角带有闪电图标的每个节点都在使用快速路径,因为当前未在执行逻辑。

如果我们将此网络更改为包含任意形式的计算,如以下示例所示,则关联节点将不再使用快速路径。

在上图中,由于我们现在正在执行蓝图逻辑以生成供应给 TEST_Blend2D 节点的值,因此不再使用快速路径(因此也将删除闪电图标)。
快速路径方法
为使动画蓝图能够使用快速路径,请确保它们:
直接访问成员变量
在下图中,我们直接访问和读取布尔变量的值来确定姿势以使用快速路径。

在以下示例中,我们没有使用快速路径,因为我们正在执行逻辑来确定布尔变量是否等于true。

访问否定布尔成员变量
在下图中,我们读取否定布尔变量的值来确定姿势以使用快速路径。

在以下示例中,我们没有使用快速路径,因为我们正在执行逻辑来确定布尔变量是否不等于true。

访问嵌套结构的成员
在下图中,我们将旋转体变量分解,直接访问Pitch和Yaw变量以提供动画偏移值。

使用"分解结构"节点访问成员
在下图中,我们使用"分解结构"节点将旋转体变量拆分为XYZ值以提供动画偏移值。

某些 分解结构 节点(如 分解变换)目前不使用快速路径,因为它们在内部执行转换,而不仅仅是复制数据。
提醒蓝图用法
为确保动画蓝图使用快速路径,您可以启用 提醒蓝图用法(Warn About Blueprint Usage) 选项,这样每当从动画图形向蓝图虚拟机发出调用时,编译器就会向编译器结果日志中发送警告。
-
要启用 提醒蓝图用法(Warn About Blueprint Usage),请在 动画蓝图(Animation Blueprint) 的 类设置(Class Settings) 中的 优化(Optimization) 中启用该选项。
当编译器识别到未在使用快速路径的任何节点时,编译器结果 日志中会显示这些节点。
在上图中,由于我们在动画图形中执行蓝图逻辑,并启用了警告选项,因此在编译器结果中看到警告消息,单击该消息会转至有问题的节点。 这样可以帮助找到需要进行的优化设置,并使您能够识别可能导致出现该问题的节点。
总体提示
在您开始考虑动画使用的性能时,以下是您在执行优化时需要考虑的一些准则。
根据项目的大小和范围,可能需要进行一些更大胆的更改,但总的来说这是一个不错的入手点。
- 确保符合并行更新的条件
- 在
UAnimInstance::NeedsImmediateUpdate
中,您会看到为避免在游戏线程上运行动画更新阶段而必须满足的所有条件。 如果角色运动需要根运动,那么无法执行并行更新,因为角色运动并非多线程任务。
- 在
- 避免调用蓝图虚拟机
- 考虑为C++代码。
- 让动画蓝图中的 事件图形 保持为空。在
FAnimInstanceProxy::Update
或FAnimInstanceProxy::Evaluate
期间,使用自定义UAnimInstance
和FAnimInstanceProxy
派生的类来完成所有工作,因为这些任务在工作线程上执行。 - 在构造动画蓝图 动画图形 中的节点时,确保它们使用快速路径。
- 确保启用 项目设置(Project Settings) 中的 优化动画蓝图成员变量访问(Optimize Anim Blueprint Member Variable Access),因为该选项控制直接访问其类的成员变量的动画蓝图节点是否应使用优化路径,从而避免转换到蓝图虚拟机。
- 一般来说,动画图形执行开销最大的部分是调用虚拟机,因此避免此类调用是实现最大动画蓝图性能的关键。
- 使用更新速度优化(URO)
- 这样可防止动画更新过于频繁。如何控制该设置的应用取决于您的游戏,但我们建议在适当距离内,对多数角色采用不超过15Hz的更新速度,同时禁用内插。
- 要启用该选项,将骨架网格体组件设置为 启用更新速度优化(Enable Update Rate Optimizations) 并引用
AnimUpdateRateTick()
。- 您也可以选择启用 显示调试更新速度优化(Display Debug Update Rate Optimizations),以在应用URO期间,在屏幕上显示调试过程。
- 允许组件使用固定骨架边界
- 在您的骨架网格体组件中,启用 组件使用骨架边界(Component Use Skel Bounds) 选项。
- 这将跳过使用物理资源,改为使用骨架网格体中定义的固定边界。
- 还将跳过重新计算为每一帧选择的包围体,从而提高性能。
其他注意事项
在分析项目时,您可能会看到工作线程完成后,在主线程上为骨架网格体运行FParallelAnimationCompletionTask
。
当并行更新条件得到满足时,您会在分析中看到与此有关的大量主线程工作,根据您的设置,并行更新条件通常包括以下几点:
- 四处移动组件,(例如)更新骨骼的物理对象。
- 尝试避免更新实际不太需要的对象的物理数据,因为这是减少此类工作的关键所在。
- 触发动画通知。
- 这些应该是非蓝图工作,同样是为了避免调用蓝图虚拟机,提高效率。
- 这些工作也需要在游戏线程上执行,因为它们会影响动画对象的寿命。
- 启用URO时的动画内插。
- 使用材质或目标变形曲线时的曲线混合。