Animation weights in sequencer not working

Hey guys

My animations in sequencer don’t respond to the weight value:

So when I hit play in the editor with this, the animations don’t blend based on weight.

Second, when I scrub this sequence nothing happens on the actor that’s meant to animate, so it’s behaving differently to the Epic video example from the 4.15 patch examples. The only time the animations play is when I hit PIE and play the sequence. And then they don’t use the weight value to blend.

Despite its name the actor being tracked inherits from Character. Could that be why it’s not working?

Edit: weight only works on skeletal mesh actors, not characters. That’s bad news for us as we have a lot of dynamic stuff going on as well as fixed animations, with the blending there handled by blueprints. We have so many animations and a need for precise timing so sequences are also important and blending the animations in those sequences matters. Does anyone have any suggestions?

Hey buddy! Did you get a solution for this?!

Im having the exact same problem. I need to use a character as it will be a vr game and i want the AI Perception to still work while the sequence is playing, so that enemies can see you at any point during the sequence.

Any help is much appreciated! Best regards

This is pretty much what I ended up with: Rick and Morty — There's a solution here you're not seeing - YouTube

Just kidding. There is no solution yet, Epic will have to implement it. There’s no way to target the skeletal mesh component directly with a sequence, only a skeletal mesh actor. If they fix that then it’s all on.

I recently ran into an issue that seems similar, so maybe my findings will be helpful. I had a blueprint actor that needed to blend two animations using Sequencer. Saw the same issue where the weight keys had no effect.

In my case the issue was that my actor had an animation blueprint assigned. Stepping through the code, it looks like the issue was this:

FSkeletalAnimationTrackData::PreviewSetAnimPosition and FSkeletalAnimationTrackData::SetAnimPosition only use the weight if the SkeletalMeshComponent’s anim instance is a UAnimSequencerInstance. If the actor has an animation blueprint assigned, this won’t be the case. So the weight keys get ignored :frowning:

As a workaround, I ended up removing the animation blueprint from that actor. Not an amazing solution, but short of a code change, I don’t think there’s another way to accomplish this.

Try to right click the keyframe and see if the value is the same as it says on the left side (for me the values were not the same, so setting the value when right clicked solved it). Other thing you could do is go to the graph editor and change the keyframe values there.

This indeed is a buggy feature and should be fixed.

I did confirm that. It also works fine with the same process on a skeletal mesh actor, just not anything that’s redirecting the animation track to a skeletal mesh component.

@noxinima Damnit, that’s a problem. We use animation slots in an animation blueprint to allow sequenced animations to be blended with variable things like AI navigation. I figured it was something like that though. I’ll file a feature request/bug report with all of that info.

News about this? Is this bug being reported already?
Thanks

Hey @Antidamage, news about this? I am using Animation Blueprint therefore the problem continues

I have had no news. Presumably it’s either been fixed or it’s still broken. You can definitely try it out yourself to see which state it’s in.

So the situation is still borked in .17?

I have no idea. You can probably test it easily enough yourself. We do all our animation blending that was intended to be done in a sequence prior to import now.

Hi guys,
we’re on 4.18 and still had to make a few changes in order to make this work.
Here we go :
1 - in /Source/Runtime/Engine/Private/Animation/AnimMontage.cpp, find FAnimMontageInstance::SetMatineeAnimPositionInner() and do the following changes :



// Change Start
UAnimMontage* FAnimMontageInstance::SetMatineeAnimPositionInner(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, UAnimSequenceBase* InAnimSequence, float InPosition, bool bLooping, float Weight/*=1.0f*/)
// Change End
{
    UAnimMontage* PlayingMontage = InitializeMatineeControl(SlotName, SkeletalMeshComponent, InAnimSequence, bLooping);
    UAnimInstance* AnimInst = SkeletalMeshComponent->GetAnimInstance();
    if (UAnimSingleNodeInstance* SingleNodeInst = SkeletalMeshComponent->GetSingleNodeInstance())
    {
        if (SingleNodeInst->GetCurrentTime() != InPosition)
        {
            SingleNodeInst->SetPosition(InPosition);
        }
    }
    else if (PlayingMontage && AnimInst)
    {
        FAnimMontageInstance* AnimMontageInst = AnimInst->GetActiveInstanceForMontage(PlayingMontage);
        if (!AnimMontageInst)
        {
            UE_LOG(LogSkeletalMesh, Warning, TEXT("Unable to set animation position for montage on slot name: %s"), *SlotName.ToString());
            return nullptr;
        }

        // ensure full weighting to this instance
// Change Start
        AnimMontageInst->Blend.SetDesiredValue(Weight);
        AnimMontageInst->Blend.SetAlpha(Weight);
// Change End

        AnimMontageInst->SetNextPositionWithEvents(InPosition);
    }
    else
    {
        UE_LOG(LogSkeletalMesh, Warning, TEXT("Invalid animation configuration when attempting to set animation possition with : %s"), *InAnimSequence->GetName());
    }

    return PlayingMontage;
}



then, find FAnimMontageInstance::PreviewMatineeSetAnimPositionInner() and do the following changes :



// change start
UAnimMontage* FAnimMontageInstance::PreviewMatineeSetAnimPositionInner(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, UAnimSequenceBase* InAnimSequence, float InPosition, bool bLooping, bool bFireNotifies, float DeltaTime, float Weight/*=1.0f*/)
// change end
{
    // Codepath for updating an animation when the skeletal mesh component is not going to be ticked (ie in editor)
    UAnimMontage* PlayingMontage = InitializeMatineeControl(SlotName, SkeletalMeshComponent, InAnimSequence, bLooping);

    UAnimInstance* AnimInst = SkeletalMeshComponent->GetAnimInstance();

    FAnimMontageInstance* MontageInstanceToUpdate = AnimInst && PlayingMontage ? AnimInst->GetActiveInstanceForMontage(PlayingMontage) : nullptr;
    float PreviousPosition = InPosition;

    if (UAnimSingleNodeInstance* SingleNodeInst = SkeletalMeshComponent->GetSingleNodeInstance())
    {
        PreviousPosition = SingleNodeInst->GetCurrentTime();

        // If we're playing a montage, we fire notifies explicitly below (rather than allowing the single node instance to do it)
        const bool bFireNotifiesHere = bFireNotifies && PlayingMontage == nullptr;

        if (DeltaTime == 0.f)
        {
            const float PreviousTime = InPosition;
            SingleNodeInst->SetPositionWithPreviousTime(InPosition, PreviousTime, bFireNotifiesHere);
        }
        else
        {
            SingleNodeInst->SetPosition(InPosition, bFireNotifiesHere);
        }
    }
    else if (MontageInstanceToUpdate)
    {
        // ensure full weighting to this instance
// change start
        MontageInstanceToUpdate->Blend.SetDesiredValue(Weight);
        MontageInstanceToUpdate->Blend.SetAlpha(Weight);
// change end

        PreviousPosition = AnimInst->Montage_GetPosition(PlayingMontage);
        AnimInst->Montage_SetPosition(PlayingMontage, InPosition);
    }
    else
    {
        UE_LOG(LogSkeletalMesh, Warning, TEXT("Invalid animation configuration when attempting to set animation possition with : %s"), *InAnimSequence->GetName());
    }

    // Now force the animation system to update, if we have a montage instance
    if (MontageInstanceToUpdate)
    {
        AnimInst->UpdateAnimation(DeltaTime, false);

        // since we don't advance montage in the tick, we manually have to handle notifies
        MontageInstanceToUpdate->HandleEvents(PreviousPosition, InPosition, NULL);

        if (!bFireNotifies)
        {
            AnimInst->NotifyQueue.Reset(SkeletalMeshComponent);
        }

        // Allow the proxy to update (this also filters unfiltered notifies)
        if (AnimInst->NeedsUpdate())
        {
            AnimInst->ParallelUpdateAnimation();
        }

        // Explicitly call post update (also triggers notifies)
        AnimInst->PostUpdateAnimation();
    }

    // Update space bases so new animation position has an effect.
    SkeletalMeshComponent->RefreshBoneTransforms();
    SkeletalMeshComponent->RefreshSlaveComponents();
    SkeletalMeshComponent->UpdateComponentToWorld();
    SkeletalMeshComponent->FinalizeBoneTransform();
    SkeletalMeshComponent->MarkRenderTransformDirty();
    SkeletalMeshComponent->MarkRenderDynamicDataDirty();

    return PlayingMontage;
}


Also, find FAnimMontageInstance::HandleEvents() and do the following :



void FAnimMontageInstance::HandleEvents(float PreviousTrackPos, float CurrentTrackPos, const FBranchingPointMarker* BranchingPointMarker)
{
    // Skip notifies and branching points if montage has been interrupted.
// change start
    if (bInterrupted || Montage==nullptr)
// change end
    {
        return;
    }

    // now get active Notifies based on how it advanced
    if (AnimInstance.IsValid())
    {
        TArray<const FAnimNotifyEvent*> Notifies;
        TMap<FName, TArray<const FAnimNotifyEvent*>> NotifyMap;

        // We already break up AnimMontage update to handle looping, so we guarantee that PreviousPos and CurrentPos are contiguous.
        Montage->GetAnimNotifiesFromDeltaPositions(PreviousTrackPos, CurrentTrackPos, Notifies);

        // For Montage only, remove notifies marked as 'branching points'. They are not queued and are handled separately.
        Montage->FilterOutNotifyBranchingPoints(Notifies);

        // now trigger notifies for all animations within montage
        // we'll do this for all slots for now
        for (auto SlotTrack = Montage->SlotAnimTracks.CreateIterator(); SlotTrack; ++SlotTrack)
        {
            TArray<const FAnimNotifyEvent*>& SlotTrackNotifies = NotifyMap.FindOrAdd(SlotTrack->SlotName);
            SlotTrack->AnimTrack.GetAnimNotifiesFromTrackPositions(PreviousTrackPos, CurrentTrackPos, SlotTrackNotifies);
        }

        // Queue all these notifies.
        AnimInstance->NotifyQueue.AddAnimNotifies(Notifies, NotifyWeight);
        AnimInstance->NotifyQueue.AddAnimNotifies(NotifyMap, NotifyWeight);
    }

    // Update active state branching points, before we handle the immediate tick marker.
    // In case our position jumped on the timeline, we need to begin/end state branching points accordingly.
    UpdateActiveStateBranchingPoints(CurrentTrackPos);

    // Trigger ImmediateTickMarker event if we have one
    if (BranchingPointMarker)
    {
        BranchingPointEventHandler(BranchingPointMarker);
    }
}


Now, in Engine/Source/Runtime/MovieSceneTracks/Private/Evaluation/MovieSceneSkeletalAnimationTemplate.cpp, find SetAnimPosition() and change as in :



        void SetAnimPosition(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player, USkeletalMeshComponent* SkeletalMeshComponent, FName SlotName, FObjectKey Section, UAnimSequenceBase* InAnimSequence, float InPosition, float Weight, bool bLooping, bool bFireNotifies)
        {
            if (!CanPlayAnimation(SkeletalMeshComponent, InAnimSequence))
            {
                return;
            }

            UAnimSequencerInstance* SequencerInst = Cast<UAnimSequencerInstance>(SkeletalMeshComponent->GetAnimInstance());
            if (SequencerInst)
            {
                FMovieSceneAnimTypeID AnimTypeID = SectionToAnimationIDs.GetAnimTypeID(Section);

                Player.SavePreAnimatedState(*SequencerInst, AnimTypeID, FStatelessPreAnimatedTokenProducer(&ResetAnimSequencerInstance));

                // Set position and weight
                SequencerInst->UpdateAnimTrack(InAnimSequence, GetTypeHash(AnimTypeID), InPosition, Weight, bFireNotifies);
            }
            else
            {
// change start
                TWeakObjectPtr<UAnimMontage> Montage = FAnimMontageInstance::SetMatineeAnimPositionInner(SlotName, SkeletalMeshComponent, InAnimSequence, InPosition, bLooping, Weight);
// change end

                // Ensure the sequence is not stopped
                UAnimInstance* AnimInst = SkeletalMeshComponent->GetAnimInstance();
                if (AnimInst && Montage.IsValid())
                {
                    FMovieSceneAnimTypeID SlotTypeID = MontageSlotAnimationIDs.GetAnimTypeID(SlotName);
                    Player.SavePreAnimatedState(*AnimInst, SlotTypeID, FStopPlayingMontageTokenProducer(Montage));

                    AnimInst->Montage_Resume(Montage.Get());
                }
            }
        }




Do the same modification in PreviewSetAnimPosition().

You’ll also have to alter these functions prototypes so they match the new definitions. And you should be able to animate the weight of the animation slots as expected.

Nice work!

Can you submit this as a pull request to the main branch?