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.