UE4中Tick使用小结
28 Jul 2018 UE4 Tick
Tick在UE4的逻辑中是最基本的单元之一,不过也有些地方需要注意的。
当前使用的UE4版本为4.18。
并行
Tick是可以打开并行的,对于一些和主要逻辑没有耦合的逻辑,可以打开该Object的并行Tick:bRunOnAnyThread来进行运行优化。
实际并行的执行方式可以到FTickTaskSequencer::ReleaseTickGroup中去查看。
并行只在TickGroup内部执行,但是要小心多线程同步问题,考虑到加锁的成本,还是只对独立的逻辑进行异步比较好。
在优化上,还有一个bAllowTickOnDedicatedServer的选项,不过具体与使用_Server宏来屏蔽代码相比哪个更好些并没有测试过~
ActualTickGroup
每个TickFunc都可以指定TickGroup,但是这个TickGroup并不代表最终实际执行的TickGroup。
根据每个Tick注册的Prerequisite不同,Tick的执行Group会被延迟。
例如,如果TickFunc要求的Prerequisite是在PostPhiscs中的,那么即便它自己是注册为PrePhyiscs,也会被推迟到Post才能执行。
这个时候可以看到TickFunc的ActualStartTickGroup会变成实际的Tick执行组。
自定义Tick
可以通过自定义TickFunc来构建自己的Tick逻辑,相比TimerManager最大的优势就是这样可以在原有的Tick之外获得自己需要的逻辑执行时间。
引擎内部有一些额外定义的TickFunc,以前似乎是有SecondaryTick的,不过后来弃用了。
UWorld
作为PhsxScene的维护者,拥有额外的三个TickFunc:FStartPhysicsTickFunction、FEndPhysicsTickFunction、FStartAsyncSimulationFunction。
都是用于对物理世界进行操作的Tick,其中StartAsyncSimulation是用于cloth的模拟的,注册在EndPhysicsTickFunction之后。而StartPysics是用于启动物理模拟,EndPhysics则是物理模拟结束的。
所以如果要保证物理处理已经完成,最好是注册到PostPhysics。
USkeletalMeshComponent
FSkeletalMeshComponentEndPhysicsTickFunction是注册在EndPhysics的,用于骨骼动画的状态同步等操作,没有仔细的看过,详情参考USkeletalMeshComponent::EndPhysicsTickComponent。
这个Tick是要求在关卡完成物理结算之后的:
EndPhysicsTickFunction.AddPrerequisite(World, World->EndPhysicsTickFunction);
FSkeletalMeshComponentClothTickFunction则是负责对Cloth进行更新的,它被添加到了SkeletalMesh的EndPhysicsTickFunction之后,保证物理更新完成之后再进行衣服的演算更新。
UCharacterMovmentComponent
FCharacterMovementComponentPostPhysicsTickFunction这个是在角色移动中,执行PostPhysicsTickComponent,当bDeferUpdateBasedMovement打开了的话,会在这里进行一次移动更新。
UPrimitiveComponent
FPrimitiveComponentPostPhysicsTickFunction,这个是用来执行每个Component的PostPhysicsTick的,按照官方注释已经弃用。
自定义
参照上面的TickFunc的用法,自定义比较简单
USTRUCT()
struct FMyCustomTick : public FTickFunction
{
GENERATED_USTRUCT_BODY()
class UVehicleSyncComponent* Target;
FMyCustomTick();
virtual void ExecuteTick(float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) override;
/** Abstract function to describe this tick. Used to print messages about illegal cycles in the dependency graph **/
virtual FString DiagnosticMessage() override;
};
template<>
struct TStructOpsTypeTraits<FMyCustomTick> : public TStructOpsTypeTraitsBase2<FMyCustomTick>
{
enum
{
WithCopy = false
};
};
其中,下面的宏是必须的,否则会导致编译出错。因为TickFunc是不允许复制的。之后再对函数进行实现:
FMyCustomTick::FMyCustomTick()
{
TickGroup = ETickingGroup::TG_PostPhysics;
bCanEverTick = true;
}
void FMyCustomTick::ExecuteTick(float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
FActorComponentTickFunction::ExecuteTickHelper(Target, /*bTickInEditor=*/ false, DeltaTime, TickType, [this](float DilatedTime) { Target->PostPhysicsTick(*this); });
}
FString FMyCustomTick::DiagnosticMessage()
{
return Target->GetFullName() + TEXT("[UMyComp::PostPhysxTick]");
}
对于Component,定义了FMyCustomTick的变量PostPhysxComponentTick之后,在其Tick注册函数RegisterComponentTickFunctions中继续注册就可以了,可以使用的帮助函数SetupActorComponentTickFunction来进行注册,以及注意解注册:
if (bRegister)
{
if (SetupActorComponentTickFunction(&PostPhysxComponentTick))
{
PostPhysxComponentTick.Target = this;
UWorld* World = GetWorld();
if (World != nullptr)
{
PostPhysxComponentTick.AddPrerequisite(World, World->EndPhysicsTickFunction);
}
}
}
else
{
if (PostPhysxComponentTick.IsTickFunctionRegistered())
{
PostPhysxComponentTick.UnRegisterTickFunction();
}
}
TickFunc的自定义还是比较简单的 ,不明白的地方可以参考上面那些引擎内部用法。