bool ACharacter::CanUseRootMotionRepMove(const FSimulatedRootMotionReplicatedMove& RootMotionRepMove, const FAnimMontageInstance& ClientMontageInstance) const { // Ignore outdated moves. if( GetWorld()->TimeSince(RootMotionRepMove.Time) <= 0.5f ) { // Make sure montage being played matched between client and server. if( RootMotionRepMove.RootMotion.AnimMontage && (RootMotionRepMove.RootMotion.AnimMontage == ClientMontageInstance.Montage) ) { UAnimMontage * AnimMontage = ClientMontageInstance.Montage; const float ServerPosition = RootMotionRepMove.RootMotion.Position; const float ClientPosition = ClientMontageInstance.GetPosition(); const float DeltaPosition = (ClientPosition - ServerPosition); const int32 CurrentSectionIndex = AnimMontage->GetSectionIndexFromPosition(ClientPosition); if( CurrentSectionIndex != INDEX_NONE ) { const int32 NextSectionIndex = (CurrentSectionIndex < ClientMontageInstance.NextSections.Num()) ? ClientMontageInstance.NextSections[CurrentSectionIndex] : INDEX_NONE; // We can only extract root motion if we are within the same section. // It's not trivial to jump through sections in a deterministic manner, but that is luckily not frequent. const bool bSameSections = (AnimMontage->GetSectionIndexFromPosition(ServerPosition) == CurrentSectionIndex); // if we are looping and just wrapped over, skip. That's also not easy to handle and not frequent. const bool bHasLooped = (NextSectionIndex == CurrentSectionIndex) && (FMath::Abs(DeltaPosition) > (AnimMontage->GetSectionLength(CurrentSectionIndex) / 2.f)); // Can only simulate forward in time, so we need to make sure server move is not ahead of the client. const bool bServerAheadOfClient = ((DeltaPosition * ClientMontageInstance.GetPlayRate()) < 0.f); UE_LOG(LogRootMotion, Log, TEXT("\t\tACharacter::CanUseRootMotionRepMove ServerPosition: %.3f, ClientPosition: %.3f, DeltaPosition: %.3f, bSameSections: %d, bHasLooped: %d, bServerAheadOfClient: %d"), ServerPosition, ClientPosition, DeltaPosition, bSameSections, bHasLooped, bServerAheadOfClient); return bSameSections && !bHasLooped && !bServerAheadOfClient; } } } return false; }
/** Play a Montage. Returns Length of Montage in seconds. Returns 0.f if failed to play. */ float UAnimInstance::Montage_Play(UAnimMontage * MontageToPlay, float InPlayRate) { if( MontageToPlay && (MontageToPlay->SequenceLength > 0.f) ) { if( CurrentSkeleton->IsCompatible(MontageToPlay->GetSkeleton()) ) { // when stopping old animations, make sure it does give current new blendintime to blend out StopAllMontages(MontageToPlay->BlendInTime); FAnimMontageInstance * NewInstance = new FAnimMontageInstance(this); check (NewInstance); NewInstance->Initialize(MontageToPlay); NewInstance->Play(InPlayRate); MontageInstances.Add(NewInstance); UpdateRootMotionMontageInstance(); return NewInstance->Montage->SequenceLength; } else { UE_LOG(LogAnimation, Warning, TEXT("Playing a Montage (%s) for the wrong Skeleton (%s) instead of (%s)."), *GetNameSafe(MontageToPlay), *GetNameSafe(MontageToPlay->GetSkeleton()), *GetNameSafe(CurrentSkeleton)); } } else { UE_LOG(LogAnimation, Warning, TEXT("Trying to play invalid Montage (%s)"), *GetNameSafe(MontageToPlay)); } return 0.f; }
void UAnimInstance::Montage_Stop(float InBlendOutTime) { FAnimMontageInstance * CurMontageInstance = GetActiveMontageInstance(); if ( CurMontageInstance ) { CurMontageInstance->Stop(InBlendOutTime); } }
void UAnimInstance::UpdateRootMotionMontageInstance() { FAnimMontageInstance * ActiveMontageInstance = GetActiveMontageInstance(); const bool bValidRootMotionInstance = (ActiveMontageInstance && ActiveMontageInstance->IsValid() && ActiveMontageInstance->Montage && (ActiveMontageInstance->Montage->bEnableRootMotionTranslation || ActiveMontageInstance->Montage->bEnableRootMotionRotation) ); RootMotionMontageInstance = bValidRootMotionInstance ? ActiveMontageInstance : NULL; }
FName UAnimInstance::Montage_GetCurrentSection() { FAnimMontageInstance * CurMontageInstance = GetActiveMontageInstance(); if ( CurMontageInstance ) { return CurMontageInstance->GetCurrentSection(); } return NAME_None; }
void UAnimSingleNodeInstance::Montage_Advance(float DeltaTime) { Super::Montage_Advance(DeltaTime); FAnimMontageInstance * CurMontageInstance = GetActiveMontageInstance(); if ( CurMontageInstance ) { CurrentTime = CurMontageInstance->GetPosition(); } }
void UAnimInstance::Montage_SetNextSection(FName SectionNameToChange, FName NextSection) { FAnimMontageInstance * CurMontageInstance = GetActiveMontageInstance(); if ( CurMontageInstance && CurMontageInstance->ChangeNextSection(SectionNameToChange, NextSection) == false ) { UE_LOG(LogAnimation, Warning, TEXT("Changing section from %s to %s failed for Montage (%s) "), *SectionNameToChange.ToString(), *NextSection.ToString(), *CurMontageInstance->Montage->GetName() ); } else { OnMontagePositionChanged(CurMontageInstance, NextSection); } }
void UAnimInstance::Montage_JumpToSectionsEnd(FName SectionName) { FAnimMontageInstance * CurMontageInstance = GetActiveMontageInstance(); if ( CurMontageInstance && CurMontageInstance->ChangePositionToSection(SectionName, CurMontageInstance->PlayRate >= 0.0f) == false ) { UE_LOG(LogAnimation, Warning, TEXT("Jumping section to %s failed for Montage (%s) "), *SectionName.ToString(), *CurMontageInstance->Montage->GetName() ); } else { OnMontagePositionChanged(CurMontageInstance, SectionName); } }
FAnimMontageInstance* UAnimInstance::GetActiveMontageInstance() { if ( MontageInstances.Num() > 0 ) { FAnimMontageInstance * RetVal = MontageInstances.Last(); if ( RetVal->IsValid() ) { return RetVal; } } return NULL; }
float UAnimInstance::GetSlotWeight(FName SlotNodeName) { float TotalSum = 0.f; #if DEBUGMONTAGEWEIGHT float TotalDesiredWeight = 0.f; #endif // first get all the montage instance weight this slot node has for (int32 I = 0; I < MontageInstances.Num(); ++I) { FAnimMontageInstance* MontageInstance = MontageInstances[I]; if (MontageInstance && MontageInstance->IsValid() && MontageInstance->Montage->IsValidSlot(SlotNodeName)) { TotalSum += MontageInstance->Weight; #if DEBUGMONTAGEWEIGHT TotalDesiredWeight += MontageInstance->DesiredWeight; #endif } } // this can happen when it's blending in OR when newer animation comes in with shorter blendtime // say #1 animation was blending out time with current blendtime 1.0 #2 animation was blending in with 1.0 (old) but got blend out with new blendtime 0.2f // #3 animation was blending in with the new blendtime 0.2f, you'll have sum of #1, 2, 3 exceeds 1.f if (TotalSum > 1.f + ZERO_ANIMWEIGHT_THRESH) { // first get all the montage instance weight this slot node has for (int32 I=0; I<MontageInstances.Num(); ++I) { FAnimMontageInstance * MontageInstance = MontageInstances[I]; if (MontageInstance && MontageInstance->IsValid() && MontageInstance->Montage->IsValidSlot(SlotNodeName)) { MontageInstance->Weight/=TotalSum; } } } #if DEBUGMONTAGEWEIGHT else if (TotalDesiredWeight == 1.f && TotalSum < 1.f - ZERO_ANIMWEIGHT_THRESH) { // this can happen when it's blending in OR when newer animation comes in with longer blendtime // say #1 animation was blending out time with current blendtime 0.2 #2 animation was blending in with 0.2 (old) but got blend out with new blendtime 1.f // #3 animation was blending in with the new blendtime 1.f, you'll have sum of #1, 2, 3 doesn't meet 1.f UE_LOG(LogAnimation, Warning, TEXT("[%s] Montage has less weight. Blending in?(%f)"), *SlotNodeName.ToString(), TotalSum); } #endif return FMath::Clamp<float>(TotalSum, 0.f, 1.f); }
void UAnimInstance::SlotEvaluatePose(FName SlotNodeName, const FA2Pose & SourcePose, FA2Pose & BlendedPose, float SlotNodeWeight) { SCOPE_CYCLE_COUNTER(STAT_AnimNativeEvaluatePoses); if ( SlotNodeWeight > ZERO_ANIMWEIGHT_THRESH ) { // final output of slot pose FA2Pose SlotPose; SlotPose.Bones.AddUninitialized(SourcePose.Bones.Num()); /// blend helper variables TArray<FA2Pose> MontagePoses; TArray<float> MontageWeights; TArray<FAnimMontageInstance *> ValidMontageInstances; // first pass we go through collect weights and valid montages. for (auto Iter = MontageInstances.CreateConstIterator(); Iter; ++Iter) { FAnimMontageInstance * MontageInstance = (*Iter); // @todo make this struct? if (MontageInstance->IsValid() && MontageInstance->Montage->IsValidSlot(SlotNodeName)) { int32 NewIndex = MontageWeights.AddUninitialized(1); MontagePoses.AddZeroed(1); ValidMontageInstances.Add(MontageInstance); MontagePoses[NewIndex].Bones.AddUninitialized(BlendedPose.Bones.Num()); MontageWeights[NewIndex] = MontageInstance->Weight; } } // clean up the MontageWeights to see if they're not summing up correctly float TotalSum = 0.f; for (int32 I=0; I<MontageWeights.Num(); ++I) { // normalize I TotalSum += MontageWeights[I]; } // if it has any valid weight if ( TotalSum > ZERO_ANIMWEIGHT_THRESH ) { // but not 1.f. If 1.f it's useless if (FMath::IsNearlyEqual(TotalSum, 1.f) == false) { for (int32 I=0; I<MontageWeights.Num(); ++I) { // normalize I MontageWeights[I] /= TotalSum; } } } else { FAnimationRuntime::FillWithRefPose(BlendedPose.Bones, RequiredBones); // nothing else to do here, there is no weight return; } // if not, something went wrong. It should have something check (ValidMontageInstances.Num() > 0); // second pass, we fill up MontagePoses with valid data to be full pose for (int32 I=0; I<ValidMontageInstances.Num(); ++I) { FAnimMontageInstance * MontageInstance = ValidMontageInstances[I]; const FAnimTrack * AnimTrack = MontageInstance->Montage->GetAnimationData(SlotNodeName); // find out if this is additive animation EAdditiveAnimationType AdditiveAnimType = AAT_None; if (AnimTrack->IsAdditive()) { AdditiveAnimType = AnimTrack->IsRotationOffsetAdditive()? AAT_RotationOffsetMeshSpace : AAT_LocalSpaceBase; } // get the pose data, temporarily use SlotPose FAnimExtractContext ExtractionContext(MontageInstance->Position, false, MontageInstance->Montage->bEnableRootMotionTranslation, MontageInstance->Montage->bEnableRootMotionRotation, MontageInstance->Montage->RootMotionRootLock); FAnimationRuntime::GetPoseFromAnimTrack(*AnimTrack, RequiredBones, SlotPose.Bones, ExtractionContext); // if additive, we should blend with source to make it fullbody if (AdditiveAnimType == AAT_LocalSpaceBase) { ApplyAdditiveSequence(SourcePose,SlotPose,MontageWeights[I],MontagePoses[I]); } else if (AdditiveAnimType == AAT_RotationOffsetMeshSpace) { BlendRotationOffset(SourcePose,SlotPose,MontageWeights[I],MontagePoses[I]); } else { CopyPose(SlotPose, MontagePoses[I]); } } // allocate for blending FTransformArrayA2** BlendingPoses = new FTransformArrayA2*[MontagePoses.Num()]; for (int32 I=0; I<MontagePoses.Num(); ++I) { BlendingPoses[I] = &MontagePoses[I].Bones; } // now time to blend all montages FAnimationRuntime::BlendPosesTogether(MontagePoses.Num(), (const FTransformArrayA2**)BlendingPoses, (const float*)MontageWeights.GetData(), RequiredBones, SlotPose.Bones); // clean up memory delete[] BlendingPoses; // now blend with Source if ( SlotNodeWeight > 1-ZERO_ANIMWEIGHT_THRESH ) { BlendedPose = SlotPose; } else { // normal non-additive animations BlendSequences(SourcePose, SlotPose, SlotNodeWeight, BlendedPose); } } else { BlendedPose = SourcePose; } }
bool UAnimInstance::Montage_IsPlaying(UAnimMontage * Montage) { FAnimMontageInstance * CurMontageInstance = GetActiveMontageInstance(); return ( CurMontageInstance && CurMontageInstance->Montage == Montage && CurMontageInstance->IsPlaying() ); }
void UAnimSingleNodeInstance::NativeUpdateAnimation(float DeltaTimeX) { float NewPlayRate = PlayRate; UAnimSequence* PreviewBasePose = NULL; if (bPlaying == false) { // we still have to tick animation when bPlaying is false because NewPlayRate = 0.f; } if(CurrentAsset != NULL) { FAnimGroupInstance* SyncGroup; if (UBlendSpaceBase* BlendSpace = Cast<UBlendSpaceBase>(CurrentAsset)) { FAnimTickRecord& TickRecord = CreateUninitializedTickRecord(INDEX_NONE, /*out*/ SyncGroup); MakeBlendSpaceTickRecord(TickRecord, BlendSpace, BlendSpaceInput, BlendSampleData, BlendFilter, bLooping, NewPlayRate, 1.f, /*inout*/ CurrentTime); #if WITH_EDITORONLY_DATA PreviewBasePose = BlendSpace->PreviewBasePose; #endif } else if (UAnimSequence* Sequence = Cast<UAnimSequence>(CurrentAsset)) { FAnimTickRecord& TickRecord = CreateUninitializedTickRecord(INDEX_NONE, /*out*/ SyncGroup); MakeSequenceTickRecord(TickRecord, Sequence, bLooping, NewPlayRate, 1.f, /*inout*/ CurrentTime); // if it's not looping, just set play to be false when reached to end if (!bLooping) { if ((NewPlayRate < 0.f && CurrentTime <= 0.f) || (NewPlayRate > 0.f && CurrentTime >= Sequence->SequenceLength)) { SetPlaying(false); } } } else if(UAnimComposite* Composite = Cast<UAnimComposite>(CurrentAsset)) { FAnimTickRecord& TickRecord = CreateUninitializedTickRecord(INDEX_NONE, /*out*/ SyncGroup); MakeSequenceTickRecord(TickRecord, Composite, bLooping, NewPlayRate, 1.f, /*inout*/ CurrentTime); // if it's not looping, just set play to be false when reached to end if (!bLooping) { if ((NewPlayRate < 0.f && CurrentTime <= 0.f) || (NewPlayRate > 0.f && CurrentTime >= Composite->SequenceLength)) { SetPlaying(false); } } } else if (UAnimMontage * Montage = Cast<UAnimMontage>(CurrentAsset)) { // Full weight , if you don't have slot track, you won't be able to see animation playing if ( Montage->SlotAnimTracks.Num() > 0 ) { UpdateSlotNodeWeight(Montage->SlotAnimTracks[0].SlotName, 1.f); } // get the montage position // @todo anim: temporarily just choose first slot and show the location FAnimMontageInstance * ActiveMontageInstance = GetActiveMontageInstance(); if (ActiveMontageInstance) { CurrentTime = ActiveMontageInstance->GetPosition(); } else if (bPlaying) { SetPlaying(false); } #if WITH_EDITORONLY_DATA PreviewBasePose = Montage->PreviewBasePose; #endif } } else if(CurrentVertexAnim != NULL) { float MoveDelta = DeltaTimeX * NewPlayRate; FAnimationRuntime::AdvanceTime(bLooping, MoveDelta, CurrentTime, CurrentVertexAnim->GetAnimLength()); } #if WITH_EDITORONLY_DATA if(PreviewBasePose) { float MoveDelta = DeltaTimeX * NewPlayRate; const bool bIsPreviewPoseLooping = true; FAnimationRuntime::AdvanceTime(bIsPreviewPoseLooping, MoveDelta, PreviewPoseCurrentTime, PreviewBasePose->SequenceLength); } #endif }
void ACharacter::DisplayDebug(UCanvas* Canvas, const TArray<FName>& DebugDisplay, float& YL, float& YPos) { Super::DisplayDebug(Canvas, DebugDisplay, YL, YPos); float Indent = 0.f; class Indenter { float& Indent; public: Indenter(float& InIndent) : Indent(InIndent) { Indent += 4.0f; } ~Indenter() { Indent -= 4.0f; } }; UFont* RenderFont = GEngine->GetSmallFont(); static FName NAME_Physics = FName(TEXT("Physics")); if (DebugDisplay.Contains(NAME_Physics) ) { Indenter PhysicsIndent(Indent); FString BaseString; if ( CharacterMovement == NULL || MovementBase == NULL ) { BaseString = "Not Based"; } else { BaseString = MovementBase->IsWorldGeometry() ? "World Geometry" : MovementBase->GetName(); BaseString = FString::Printf(TEXT("Based On %s"), *BaseString); } Canvas->DrawText(RenderFont, FString::Printf(TEXT("RelativeLoc: %s RelativeRot: %s %s"), *RelativeMovement.Location.ToString(), *RelativeMovement.Rotation.ToString(), *BaseString), Indent, YPos); YPos += YL; if ( CharacterMovement != NULL ) { CharacterMovement->DisplayDebug(Canvas, DebugDisplay, YL, YPos); } const bool Crouched = CharacterMovement && CharacterMovement->IsCrouching(); FString T = FString::Printf(TEXT("Crouched %i"), Crouched); Canvas->DrawText(RenderFont, T, Indent, YPos ); YPos += YL; } if(Mesh && Mesh->GetAnimInstance()) { static FName NAME_Animation = FName(TEXT("Animation")); if( DebugDisplay.Contains(NAME_Animation) ) { YPos += YL; Canvas->DrawText(RenderFont, TEXT("Animation"), Indent, YPos ); YPos += YL; Indenter AnimIndent(Indent); //Display Sync Groups UAnimInstance* AnimInstance = Mesh->GetAnimInstance(); FString Heading = FString::Printf(TEXT("SyncGroups: %i"), AnimInstance->SyncGroups.Num()); Canvas->DrawText(RenderFont, Heading, Indent, YPos ); YPos += YL; for (int32 GroupIndex = 0; GroupIndex < AnimInstance->SyncGroups.Num(); ++GroupIndex) { Indenter GroupIndent(Indent); FAnimGroupInstance& SyncGroup = AnimInstance->SyncGroups[GroupIndex]; FString GroupLabel = FString::Printf(TEXT("Group %i - Players %i"), GroupIndex, SyncGroup.ActivePlayers.Num()); Canvas->DrawText(RenderFont, GroupLabel, Indent, YPos ); YPos += YL; if (SyncGroup.ActivePlayers.Num() > 0) { const int32 GroupLeaderIndex = FMath::Max(SyncGroup.GroupLeaderIndex, 0); for(int32 PlayerIndex = 0; PlayerIndex < SyncGroup.ActivePlayers.Num(); ++PlayerIndex) { Indenter PlayerIndent(Indent); FAnimTickRecord& Player = SyncGroup.ActivePlayers[PlayerIndex]; Canvas->SetLinearDrawColor( (PlayerIndex == GroupLeaderIndex) ? FLinearColor::Green : FLinearColor::Yellow); FString PlayerEntry = FString::Printf(TEXT("%i) %s W:%.3f"), PlayerIndex, *Player.SourceAsset->GetName(), Player.EffectiveBlendWeight); Canvas->DrawText(RenderFont, PlayerEntry, Indent, YPos ); YPos += YL; } } } Heading = FString::Printf(TEXT("Ungrouped: %i"), AnimInstance->UngroupedActivePlayers.Num()); Canvas->DrawText(RenderFont, Heading, Indent, YPos ); YPos += YL; Canvas->SetLinearDrawColor( FLinearColor::Yellow ); for (int32 PlayerIndex = 0; PlayerIndex < AnimInstance->UngroupedActivePlayers.Num(); ++PlayerIndex) { Indenter PlayerIndent(Indent); const FAnimTickRecord& Player = AnimInstance->UngroupedActivePlayers[PlayerIndex]; FString PlayerEntry = FString::Printf(TEXT("%i) %s W:%.3f"), PlayerIndex, *Player.SourceAsset->GetName(), Player.EffectiveBlendWeight); Canvas->DrawText(RenderFont, PlayerEntry, Indent, YPos ); YPos += YL; } Heading = FString::Printf(TEXT("Montages: %i"), AnimInstance->MontageInstances.Num()); Canvas->DrawText(RenderFont, Heading, Indent, YPos ); YPos += YL; for (int32 MontageIndex = 0; MontageIndex < AnimInstance->MontageInstances.Num(); ++MontageIndex) { Indenter PlayerIndent(Indent); Canvas->SetLinearDrawColor( (MontageIndex == (AnimInstance->MontageInstances.Num()-1)) ? FLinearColor::Green : FLinearColor::Yellow ); FAnimMontageInstance* MontageInstance = AnimInstance->MontageInstances[MontageIndex]; FString MontageEntry = FString::Printf(TEXT("%i) %s Sec: %s W:%.3f DW:%.3f"), MontageIndex, *MontageInstance->Montage->GetName(), *MontageInstance->GetCurrentSection().ToString(), MontageInstance->Weight, MontageInstance->DesiredWeight); Canvas->DrawText(RenderFont, MontageEntry, Indent, YPos ); YPos += YL; } } static FName NAME_SimpleBones = FName(TEXT("SimpleBones")); static FName NAME_Bones = FName(TEXT("Bones")); if( DebugDisplay.Contains(NAME_SimpleBones) ) { Mesh->DebugDrawBones(Canvas,true); } else if( DebugDisplay.Contains(NAME_Bones) ) { Mesh->DebugDrawBones(Canvas,false); } } }