void UAnimSequenceBase::TickAssetPlayerInstance(const FAnimTickRecord& Instance, class UAnimInstance* InstanceOwner, FAnimAssetTickContext& Context) const { float& CurrentTime = *(Instance.TimeAccumulator); const float PreviousTime = CurrentTime; const float PlayRate = Instance.PlayRateMultiplier * this->RateScale; float MoveDelta = 0.f; if( Context.IsLeader() ) { const float DeltaTime = Context.GetDeltaTime(); MoveDelta = PlayRate * DeltaTime; if( MoveDelta != 0.f ) { // Advance time FAnimationRuntime::AdvanceTime(Instance.bLooping, MoveDelta, CurrentTime, SequenceLength); } Context.SetSyncPoint(CurrentTime / SequenceLength); } else { // Follow the leader CurrentTime = Context.GetSyncPoint() * SequenceLength; //@TODO: NOTIFIES: Calculate AdvanceType based on what the new delta time is if( CurrentTime != PreviousTime ) { // Figure out delta time MoveDelta = CurrentTime - PreviousTime; // if we went against play rate, then loop around. if( (MoveDelta * PlayRate) < 0.f ) { MoveDelta += FMath::Sign<float>(PlayRate) * SequenceLength; } } } OnAssetPlayerTickedInternal(Context, PreviousTime, MoveDelta, Instance, InstanceOwner); // Evaluate Curve data now - even if time did not move, we still need to return curve if it exists EvaluateCurveData(InstanceOwner, CurrentTime, Instance.EffectiveBlendWeight); }
void UBlendSpaceBase::TickAssetPlayerInstance(const FAnimTickRecord& Instance, class UAnimInstance* InstanceOwner, FAnimAssetTickContext& Context) const { const float DeltaTime = Context.GetDeltaTime(); float MoveDelta = Instance.PlayRateMultiplier * DeltaTime; // this happens even if MoveDelta == 0.f. This still should happen if it is being interpolated // since we allow setting position of blendspace, we can't ignore MoveDelta == 0.f // also now we don't have to worry about not following if DeltaTime = 0.f { // first filter input using blend filter const FVector BlendInput = FilterInput(Instance.BlendFilter, Instance.BlendSpacePosition, DeltaTime); EBlendSpaceAxis AxisToScale = GetAxisToScale(); if (AxisToScale != BSA_None) { float FilterMultiplier = 1.f; // first use multiplier using new blendinput // new filtered input is going to be used for sampling animation // so we'll need to change playrate if you'd like to not slide foot if ( !Instance.BlendSpacePosition.Equals(BlendInput) ) { // apply speed change if you want, if (AxisToScale == BSA_X) { if (BlendInput.X != 0.f) { FilterMultiplier = Instance.BlendSpacePosition.X / BlendInput.X; } } else if (AxisToScale == BSA_Y) { if (BlendInput.Y != 0.f) { FilterMultiplier = Instance.BlendSpacePosition.Y / BlendInput.Y; } } } // now find if clamped input is different // if different, then apply scale to fit in FVector ClampedInput = ClampBlendInput(BlendInput); if ( !ClampedInput.Equals(BlendInput) ) { // apply speed change if you want, if (AxisToScale == BSA_X) { if (ClampedInput.X != 0.f) { FilterMultiplier *= BlendInput.X / ClampedInput.X; } } else if (AxisToScale == BSA_Y) { if (ClampedInput.Y != 0.f) { FilterMultiplier *= BlendInput.Y / ClampedInput.Y; } } } MoveDelta *= FilterMultiplier; UE_LOG(LogAnimation, Log, TEXT("BlendSpace(%s) - BlendInput(%s) : FilteredBlendInput(%s), FilterMultiplier(%0.2f)"), *GetName(), *Instance.BlendSpacePosition.ToString(), *BlendInput.ToString(), FilterMultiplier ); } check(Instance.BlendSampleDataCache); // For Target weight interpolation, we'll need to save old data, and interpolate to new data static TArray<FBlendSampleData> OldSampleDataList; static TArray<FBlendSampleData> NewSampleDataList; check(IsInGameThread() && !OldSampleDataList.Num() && !NewSampleDataList.Num()); // this must be called non-recursively on the game thread OldSampleDataList.Append(*Instance.BlendSampleDataCache); // get sample data based on new input // consolidate all samples and sort them, so that we can handle from biggest weight to smallest Instance.BlendSampleDataCache->Reset(); // new sample data that will be used for evaluation TArray<FBlendSampleData> & SampleDataList = *Instance.BlendSampleDataCache; // get sample data from blendspace if (GetSamplesFromBlendInput(BlendInput, NewSampleDataList)) { float NewAnimLength=0; // if target weight interpolation is set if (TargetWeightInterpolationSpeedPerSec > 0.f) { UE_LOG(LogAnimation, Verbose, TEXT("Target Weight Interpolation: Target Samples ")); // recalculate AnimLength based on weight of target animations - this is used for scaling animation later (change speed) float PreInterpAnimLength = GetAnimationLengthFromSampleData(NewSampleDataList); UE_LOG(LogAnimation, Verbose, TEXT("BlendSpace(%s) - BlendInput(%s) : PreAnimLength(%0.5f) "), *GetName(), *BlendInput.ToString(), PreInterpAnimLength); // target weight interpolation if (InterpolateWeightOfSampleData(DeltaTime, OldSampleDataList, NewSampleDataList, SampleDataList)) { // now I need to normalize NormalizeSampleDataWeight(SampleDataList); } else { // if interpolation failed, just copy new sample data tto sample data SampleDataList = NewSampleDataList; } // recalculate AnimLength based on weight of animations UE_LOG(LogAnimation, Verbose, TEXT("Target Weight Interpolation: Interp Samples ")); NewAnimLength = GetAnimationLengthFromSampleData(SampleDataList); // now scale the animation if (NewAnimLength > 0.f) { MoveDelta *= PreInterpAnimLength/NewAnimLength; } } else { // when there is no target weight interpolation, just copy new to target SampleDataList.Append(NewSampleDataList); NewAnimLength = GetAnimationLengthFromSampleData(SampleDataList); } float& NormalizedCurrentTime = *(Instance.TimeAccumulator); const float NormalizedPreviousTime = NormalizedCurrentTime; if (Context.IsLeader()) { // advance current time - blend spaces hold normalized time as when dealing with changing anim length it would be possible to go backwards UE_LOG(LogAnimation, Verbose, TEXT("BlendSpace(%s) - BlendInput(%s) : AnimLength(%0.5f) "), *GetName(), *BlendInput.ToString(), NewAnimLength); // Advance time using current/new anim length float CurrentTime = NormalizedCurrentTime * NewAnimLength; FAnimationRuntime::AdvanceTime(Instance.bLooping, MoveDelta, /*inout*/ CurrentTime, NewAnimLength); NormalizedCurrentTime = NewAnimLength ? (CurrentTime / NewAnimLength) : 0.0f; Context.SetSyncPoint(NormalizedCurrentTime); } else { NormalizedCurrentTime = Context.GetSyncPoint(); } // generate notifies and sets time { TArray<const FAnimNotifyEvent*> Notifies; // now calculate time for each samples const float ClampedNormalizedPreviousTime = FMath::Clamp<float>(NormalizedPreviousTime, 0.f, 1.f); const float ClampedNormalizedCurrentTime = FMath::Clamp<float>(NormalizedCurrentTime, 0.f, 1.f); const bool bGenerateNotifies = Context.ShouldGenerateNotifies() && (NormalizedCurrentTime != NormalizedPreviousTime) && NotifyTriggerMode != ENotifyTriggerMode::None; int32 HighestWeightIndex = 0; // Get the index of the highest weight, assuming that the first is the highest until we find otherwise bool bTriggerNotifyHighestWeightedAnim = NotifyTriggerMode == ENotifyTriggerMode::HighestWeightedAnimation && SampleDataList.Num() > 0; if(bGenerateNotifies && bTriggerNotifyHighestWeightedAnim) { float HighestWeight = SampleDataList[HighestWeightIndex].GetWeight(); for(int32 I = 1 ; I < SampleDataList.Num(); I++) { if(SampleDataList[I].GetWeight() > HighestWeight) { HighestWeightIndex = I; HighestWeight = SampleDataList[I].GetWeight(); } } } for (int32 I = 0; I < SampleDataList.Num(); ++I) { FBlendSampleData& SampleEntry = SampleDataList[I]; const int32 SampleDataIndex = SampleEntry.SampleDataIndex; // Skip SamplesPoints that has no relevant weight if( SampleData.IsValidIndex(SampleDataIndex) && (SampleEntry.TotalWeight > ZERO_ANIMWEIGHT_THRESH) ) { const FBlendSample& Sample = SampleData[SampleDataIndex]; if( Sample.Animation ) { const float SampleNormalizedPreviousTime = Sample.Animation->RateScale >= 0.f ? ClampedNormalizedPreviousTime : 1.f - ClampedNormalizedPreviousTime; const float SampleNormalizedCurrentTime = Sample.Animation->RateScale >= 0.f ? ClampedNormalizedCurrentTime : 1.f - ClampedNormalizedCurrentTime; const float PrevSampleDataTime = SampleNormalizedPreviousTime * Sample.Animation->SequenceLength; float& CurrentSampleDataTime = SampleEntry.Time; CurrentSampleDataTime = SampleNormalizedCurrentTime * Sample.Animation->SequenceLength; // Figure out delta time float DeltaTimePosition = CurrentSampleDataTime - PrevSampleDataTime; const float SampleMoveDelta = MoveDelta * Sample.Animation->RateScale; // if we went against play rate, then loop around. if ((SampleMoveDelta * DeltaTimePosition) < 0.f) { DeltaTimePosition += FMath::Sign<float>(SampleMoveDelta) * Sample.Animation->SequenceLength; } if( bGenerateNotifies && (!bTriggerNotifyHighestWeightedAnim || (I == HighestWeightIndex))) { // Harvest and record notifies Sample.Animation->GetAnimNotifies(PrevSampleDataTime, DeltaTimePosition, Instance.bLooping, Notifies); } if (Context.RootMotionMode == ERootMotionMode::RootMotionFromEverything && Sample.Animation->bEnableRootMotion) { Context.RootMotionMovementParams.AccumulateWithBlend(Sample.Animation->ExtractRootMotion(PrevSampleDataTime, DeltaTimePosition, Instance.bLooping), SampleEntry.GetWeight()); } UE_LOG(LogAnimation, Verbose, TEXT("%d. Blending animation(%s) with %f weight at time %0.2f"), I+1, *Sample.Animation->GetName(), SampleEntry.GetWeight(), CurrentSampleDataTime); } } } if (bGenerateNotifies && Notifies.Num() > 0) { InstanceOwner->AddAnimNotifies(Notifies, Instance.EffectiveBlendWeight); } } } OldSampleDataList.Reset(); NewSampleDataList.Reset(); } }