Description

User reported: when playing an AnimMontage via GameplayAbility with the PlayMontageAndWait ability task that has a custom value for AnimRootMotionTranslationScale, (dedicated) servers can apply an incorrect, already-reset scale value on the last tick of the anim montage playback.

Specifically, this happens when the character's SkeletalMeshComponent is configured as:

VisibilityBasedAnimTickOption = OnlyTickMontagesWhenNotRendered 

meaning animation shouldn't be ticked if the skel mesh isn't rendered, except for montages. This activates a code path in USkeletalMeshComponent::TickAnimation() that dispatches animation events immediately:

if (ShouldOnlyTickMontages(DeltaTime))
{
    ConditionallyDispatchQueuedAnimEvents();
} 

while otherwise the animation events are dispatched on next USkeletalMeshComponent::TickComponent(). Dedicated servers likely enter this code path since they don't render skeletal meshes to begin with.

This results in a wrong root motion translation scale being applied when this is triggered by, and happens mid-UCharacterMovementComponent::TickCharacterPose().

Steps to Reproduce

In ThirdPersonExample:

  • Create an AnimMontage targeting the mannequin that plays an AnimSequence that has EnableRootMotion = true. (For example: modify the Land anim sequence to enable root motion).
  • Create a GameplayAbility with a PlayMontageAndWait node that plays that montage. Set the AnimRootMotionTranslationScale argument to 0.5. Set the GA's NetExecutionPolicy to Server Initiated.
  • Prepare to trigger that GA for the player character on the server. For example in the character blueprint: create an input event that calls a server RPC that then calls AbilitySystemComponent->GiveAbilityAndActivateOnce
  • In the character blueprint, select the CharacterMesh skel mesh comp and set its 
VisibilityBasedAnimTickOption = OnlyTickMontagesWhenNotRendered 
  • Put a breakpoint on this line in UAbilityTask_PlayMontageAndWait::OnMontageBlendingOut
Character->SetAnimRootMotionTranslationScale(1.f); 
  • Start PIE with server + client. Activate the ability from the client. This should trigger the above breakpoint on the server with the callstack listed for this bug.
  • Observe: If this callstack is hit, we are inside UCharacterMovementComponent::TickCharacterPose() which will continue to apply one tick of root motion with the already reset translation scale of 1.0, instead of the desired 0.5.
  • Expected: This last tick of root motion should also be applied with a translation scale of 0.5, or whichever value was set on the PlayMontageAndWait node.
Callstack

     UnrealEditor-Engine.dll!ACharacter::SetAnimRootMotionTranslationScale(float InAnimRootMotionTranslationScale) Line 1656    C++
>    UnrealEditor-GameplayAbilities.dll!UAbilityTask_PlayMontageAndWait::OnMontageBlendingOut(UAnimMontage * Montage, bool bInterrupted) Line 29    C++
     [Inline Frame] UnrealEditor-GameplayAbilities.dll!Invoke(void(UAbilityTask_PlayMontageAndWait::*)(UAnimMontage *, bool)) Line 66    C++
     [Inline Frame] UnrealEditor-GameplayAbilities.dll!UE::Core::Private::Tuple::TTupleBase<TIntegerSequence<unsigned int>>::ApplyAfter(void(UAbilityTask_PlayMontageAndWait::*)(UAnimMontage *, bool) &) Line 311    C++
     UnrealEditor-GameplayAbilities.dll!TBaseUObjectMethodDelegateInstance<0,UAbilityTask_PlayMontageAndWait,void __cdecl(UAnimMontage *,bool),FDefaultDelegateUserPolicy>::ExecuteIfSafe(UAnimMontage * <Params_0>, bool <Params_1>) Line 667    C++
     UnrealEditor-Engine.dll!TDelegate<void __cdecl(UAnimMontage *,bool),FDefaultDelegateUserPolicy>::ExecuteIfBound<void,0>(UAnimMontage * <Params_0>, bool <Params_1>) Line 570    C++
     [Inline Frame] UnrealEditor-Engine.dll!UAnimInstance::TriggerMontageBlendingOutEvent(const FQueuedMontageBlendingOutEvent &) Line 1904    C++
     UnrealEditor-Engine.dll!UAnimInstance::TriggerQueuedMontageEvents() Line 1988    C++
     UnrealEditor-Engine.dll!UAnimInstance::DispatchQueuedAnimEvents() Line 747    C++
     UnrealEditor-Engine.dll!USkeletalMeshComponent::ConditionallyDispatchQueuedAnimEvents() Line 1648    C++
     UnrealEditor-Engine.dll!USkeletalMeshComponent::TickAnimation(float DeltaTime, bool bNeedsValidRootMotion) Line 1309    C++
     UnrealEditor-Engine.dll!USkeletalMeshComponent::TickPose(float DeltaTime, bool bNeedsValidRootMotion) Line 1491    C++
     UnrealEditor-Engine.dll!UCharacterMovementComponent::TickCharacterPose(float DeltaTime) Line 11322    C++
     UnrealEditor-Engine.dll!UCharacterMovementComponent::PerformMovement(float DeltaSeconds) Line 2601    C++
     UnrealEditor-Engine.dll!UCharacterMovementComponent::MoveAutonomous(float ClientTimeStamp, float DeltaTime, unsigned char CompressedFlags, const UE::Math::TVector<double> & NewAccel) Line 10071    C++
     UnrealEditor-Engine.dll!UCharacterMovementComponent::ServerMove_PerformMovement(const FCharacterNetworkMoveData & MoveData) Line 9482    C++
     UnrealEditor-Engine.dll!UCharacterMovementComponent::ServerMove_HandleMoveData(const FCharacterNetworkMoveDataContainer & MoveDataContainer) Line 9395    C++
     UnrealEditor-Engine.dll!UCharacterMovementComponent::ServerMovePacked_ServerReceive(const FCharacterServerMovePackedBits & PackedBits) Line 9357    C++
     [Inline Frame] UnrealEditor-Engine.dll!ACharacter::ServerMovePacked_Implementation(const FCharacterServerMovePackedBits &) Line 1756    C++
     UnrealEditor-Engine.dll!ACharacter::execServerMovePacked(UObject * Context, FFrame & Stack, void * const Z_Param__Result) Line 1168    C++
     UnrealEditor-CoreUObject.dll!UFunction::Invoke(UObject * Obj, FFrame & Stack, void * const Z_Param__Result) Line 6665    C++
     UnrealEditor-CoreUObject.dll!UObject::ProcessEvent(UFunction * Function, void * Parms) Line 2145    C++
     UnrealEditor-Engine.dll!AActor::ProcessEvent(UFunction * Function, void * Parameters) Line 1122    C++
     UnrealEditor-Engine.dll!FObjectReplicator::ReceivedRPC(FNetBitReader & Reader, const FReplicationFlags & RepFlags, const FFieldNetCache * FieldCache, const bool bCanDelayRPC, bool & bOutDelayRPC, TSet<FNetworkGUID,DefaultKeyFuncs<FNetworkGUID,0>,FDefaultSetAllocator> & UnmappedGuids) Line 1380    C++
     UnrealEditor-Engine.dll!FObjectReplicator::ReceivedBunch(FNetBitReader & Bunch, const FReplicationFlags & RepFlags, const bool bHasRepLayout, bool & bOutHasUnmapped) Line 1151    C++
     UnrealEditor-Engine.dll!UActorChannel::ProcessBunch(FInBunch & Bunch) Line 3167    C++
     UnrealEditor-Engine.dll!UActorChannel::ReceivedBunch(FInBunch & Bunch) Line 2987    C++
     UnrealEditor-Engine.dll!UChannel::ReceivedSequencedBunch(FInBunch & Bunch) Line 564    C++
     UnrealEditor-Engine.dll!UChannel::ReceivedNextBunch(FInBunch & Bunch, bool & bOutSkipAck) Line 1026    C++
     UnrealEditor-Engine.dll!UChannel::ReceivedRawBunch(FInBunch & Bunch, bool & bOutSkipAck) Line 674    C++
     UnrealEditor-Engine.dll!UNetConnection::DispatchPacket(FBitReader & Reader, int PacketId, bool & bOutSkipAck, bool & bOutHasBunchErrors) Line 3635    C++
     UnrealEditor-Engine.dll!UNetConnection::ReceivedPacket(FBitReader & Reader, bool bIsReinjectedPacket, bool bDispatchPacket) Line 2999    C++
     UnrealEditor-Engine.dll!UNetConnection::ReceivedRawPacket(void * InData, int Count) Line 1903    C++
     UnrealEditor-OnlineSubsystemUtils.dll!UIpNetDriver::TickDispatch(float DeltaTime) Line 1301    C++
     UnrealEditor-Engine.dll!UNetDriver::InternalTickDispatch(float DeltaSeconds) Line 1597    C++
     [Inline Frame] UnrealEditor-Engine.dll!Invoke(void(UNetDriver::*)(float)) Line 66    C++
     [Inline Frame] UnrealEditor-Engine.dll!UE::Core::Private::Tuple::TTupleBase<TIntegerSequence<unsigned int>>::ApplyAfter(void(UNetDriver::*)(float) &) Line 311    C++
     UnrealEditor-Engine.dll!TBaseUObjectMethodDelegateInstance<0,UNetDriver,void __cdecl(float),FDefaultDelegateUserPolicy>::ExecuteIfSafe(float <Params_0>) Line 667    C++
     [Inline Frame] UnrealEditor-Engine.dll!TMulticastDelegateBase<FDefaultDelegateUserPolicy>::Broadcast(float) Line 254    C++
     UnrealEditor-Engine.dll!TMulticastDelegate<void __cdecl(float),FDefaultDelegateUserPolicy>::Broadcast(float <Params_0>) Line 956    C++
     UnrealEditor-Engine.dll!UWorld::Tick(ELevelTick TickType, float DeltaSeconds) Line 1355    C++
     UnrealEditor-UnrealEd.dll!UEditorEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 1924    C++
     UnrealEditor-UnrealEd.dll!UUnrealEdEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 531    C++
     UnrealEditor.exe!FEngineLoop::Tick() Line 5825    C++
     [Inline Frame] UnrealEditor.exe!EngineTick() Line 61    C++
     UnrealEditor.exe!GuardedMain(const wchar_t * CmdLine) Line 188    C++
     UnrealEditor.exe!LaunchWindowsStartup(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * __formal, int nCmdShow, const wchar_t * CmdLine) Line 247    C++
     UnrealEditor.exe!WinMain(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * pCmdLine, int nCmdShow) Line 298    C++
     [Inline Frame] UnrealEditor.exe!invoke_main() Line 102    C++
     UnrealEditor.exe!__scrt_common_main_seh() Line 288    C++
     kernel32.dll!00007ffd6c30257d()    Unknown
     ntdll.dll!00007ffd6dccaa48()    Unknown

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-211293 in the post.

0
Login to Vote

Fixed
ComponentUE - Gameplay - Gameplay Ability System
Affects Versions5.3
Target Fix5.5
Fix Commit32850596
CreatedApr 3, 2024
ResolvedApr 10, 2024
UpdatedApr 10, 2024