Hey, after a quick chat with our Technical Director, even 5.7 seems compromised so upgrading doesn’t seems possible. It might not be necessary though as I found something interesting.
The visible animation hiccup is not due to anything with the ControlRig evaluation, but with the Montage played within the Sequencer. I noticed that the montages were updated twice each frame, and one of those updates produced exactly the hiccup we see.
Tracking in UAnimInstance::UpdateMontageEvaluationData(). Each frame is separated by a new line.
FAnimMontageInstance::Position: _1.707186_
FAnimMontageInstance::Position: _1.748000_
FAnimMontageInstance::Position: _1.710813_
FAnimMontageInstance::Position: _1.801498_
FAnimMontageInstance::Position: _1.712444_
FAnimMontageInstance::Position: _1.753211_
FAnimMontageInstance::Position: _1.714118_
FAnimMontageInstance::Position: _1.755984_
FAnimMontageInstance::Position: _1.715774_
FAnimMontageInstance::Position: _1.757160_
We can see in non-bold that we have a normal update, linear and with standard deltas.
And we can see a second update in bold that has the infamous 2nd frame hiccup.
So now how to find the problematic updates. The Montage updates happen in UAnimInstance::UpdateAnimation(). Below details on how it is called.
Setup #1 :
- An AActor with a SkeletalMeshComponent with an ABP
- A LevelSequence with this AActor as a Possessable
- Add a SkeletalAnimationTrack with an AnimationSection
- ( no control rig )
The SkeletalMeshComponent has one AnimInstance, which is the ABP. It is updated in :
- USkeletalMeshComponent::TickComponent(..)
- USkinnedMeshComponent::TickComponent(..)
- USkeletalMeshComponent::TickPose(..)
- USkeletalMeshComponent::TickAnimation(..)
- USkeletalMeshComponent::TickAnimInstances(..)
void USkeletalMeshComponent::TickAnimInstances(float DeltaTime, bool bNeedsValidRootMotion)
{
[...]
// We update linked instances first incase we're using either root motion or non-threaded update.
// This ensures that we go through the pre update process and initialize the proxies correctly.
for (UAnimInstance* LinkedInstance : LinkedInstances)
{
// Sub anim instances are always forced to do a parallel update
LinkedInstance->UpdateAnimation(DeltaTime * GlobalAnimRateScale, false, UAnimInstance::EUpdateAnimationFlag::ForceParallelUpdate);
}
if (AnimScriptInstance != nullptr)
{
// Tick the animation
AnimScriptInstance->UpdateAnimation(DeltaTime * GlobalAnimRateScale, bNeedsValidRootMotion);
}
[...]
}
As you can see in this TickAnimInstances(..) we first update LinkedAnimInstances, then the main AnimInstance. In this first setup, we don’t have any LinkedAnimInstances so it’s fine.
Setup #2 :
- Use Setup#1
- Add a ControlRigTrack in Layered mode
Adding a ControlRigTrack creates a UControlRigLayerInstance and assigns it to the SkeletalMeshComponent. The ABP AnimInstance is added as a LinkedAnimInstance.
So now the previous callstack also happens but on the ControlRigLayerInstance which calls the ABP AnimInstance in the LinkedAnimInstances. This is fine.
But in the ControlRigLayerInstance, we then find this bit :
void UControlRigLayerInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
if (UAnimInstance* SourceAnimInstance = GetSourceAnimInstance())
{
if (UControlRig* ControlRig = GetFirstAvailableControlRig())
{
if (ControlRig->IsAdditive())
{
SourceAnimInstance->UpdateAnimation(DeltaSeconds, true, EUpdateAnimationFlag::Default);
}
}
}
}
SourceAnimInstance is the ABP AnimInstance. So the ABP AnimInstance is updated through the usual way, but also another time with this call, leading to 2 AnimMontage updates, creating our problem.
Commenting this fixes our issue. This code is there since 5.4. Looking at Unreal’s GitHub, it is still there. Is it still relevant ? Can we get rid of it safely ? Do you have any tips/knowledge to share about this ?
Relevant commit adding it : https://github.com/EpicGames/UnrealEngine/commit/c01e888073a912988bbda178914f01cd4849faae
Thank you !