void FAnimNode_StateMachine::Update(const FAnimationUpdateContext& Context) { // If we just became relevant and haven't been initialized yet, then reinitialize state machine. if (!bFirstUpdate && (UpdateCounter.Get() != INDEX_NONE) && !UpdateCounter.WasSynchronizedInTheLastFrame(Context.AnimInstanceProxy->GetUpdateCounter()) && (CVarAnimStateMachineRelevancyReset.GetValueOnAnyThread() == 1)) { FAnimationInitializeContext InitializationContext(Context.AnimInstanceProxy); Initialize(InitializationContext); } UpdateCounter.SynchronizeWith(Context.AnimInstanceProxy->GetUpdateCounter()); const FBakedAnimationStateMachine* Machine = GetMachineDescription(); if (Machine != nullptr) { if (Machine->States.Num() == 0) { return; } else if(!Machine->States.IsValidIndex(CurrentState)) { // Attempting to catch a crash where the state machine has been freed. // Reported as a symptom of a crash in UE-24732 for 4.10. This log message should not appear given changes to // re-instancing in 4.11 (see CL 2823202). If it does appear we need to spot integrate CL 2823202 (and supporting // anim re-init changes, probably 2799786 & 2801372). UE_LOG(LogAnimation, Warning, TEXT("FAnimNode_StateMachine::Update - Invalid current state, please report. Attempting to use state %d of %d in state machine %d (ptr 0x%x)"), CurrentState, Machine->States.Num(), StateMachineIndexInClass, Machine); UE_LOG(LogAnimation, Warning, TEXT("\t\tWhen updating AnimInstance: %s"), *Context.AnimInstanceProxy->GetAnimInstanceObject()->GetName()) return; } }
void FAnimNode_StateMachine::Evaluate(FPoseContext& Output) { if (FBakedAnimationStateMachine* Machine = GetMachineDescription()) { if (Machine->States.Num() == 0) { Output.Pose.ResetToRefPose(); return; } } else { Output.Pose.ResetToRefPose(); return; } SCOPE_CYCLE_COUNTER(STAT_AnimStateMachineEvaluate); if (ActiveTransitionArray.Num() > 0) { check(Output.AnimInstance->CurrentSkeleton); //each transition stomps over the last because they will already include the output from the transition before it for (int32 Index = 0; Index < ActiveTransitionArray.Num(); ++Index) { // if there is any source pose, blend it here FAnimationActiveTransitionEntry& ActiveTransition = ActiveTransitionArray[Index]; // when evaluating multiple transitions we need to store the pose from previous results // so we can feed the next transitions const bool bIntermediatePoseIsValid = Index > 0; if (ActiveTransition.bActive) { switch (ActiveTransition.LogicType) { case ETransitionLogicType::TLT_StandardBlend: EvaluateTransitionStandardBlend(Output, ActiveTransition, bIntermediatePoseIsValid); break; case ETransitionLogicType::TLT_Custom: EvaluateTransitionCustomBlend(Output, ActiveTransition, bIntermediatePoseIsValid); break; default: break; } } } // Ensure that all of the resulting rotations are normalized Output.Pose.NormalizeRotations(); } else if (!IsAConduitState(CurrentState)) { // Evaluate the current state StatePoseLinks[CurrentState].Evaluate(Output); } }
void FAnimNode_StateMachine::CacheBones(const FAnimationCacheBonesContext& Context) { if (const FBakedAnimationStateMachine* Machine = GetMachineDescription()) { for (int32 StateIndex = 0; StateIndex < Machine->States.Num(); ++StateIndex) { if (GetStateWeight(StateIndex) > 0.f) { ConditionallyCacheBonesForState(StateIndex, Context); } } } // @TODO GetStateWeight is O(N) transitions. }
void FAnimNode_StateMachine::Update(const FAnimationUpdateContext& Context) { if (FBakedAnimationStateMachine* Machine = GetMachineDescription()) { if (Machine->States.Num() == 0) { return; } else if(!Machine->States.IsValidIndex(CurrentState)) { // Attempting to catch a crash where the state machine has been freed. UE_LOG(LogAnimation, Warning, TEXT("FAnimNode_StateMachine::Update - Invalid current state, please report. Attempting to use state %d in state machine %d"), CurrentState, StateMachineIndexInClass); UE_LOG(LogAnimation, Warning, TEXT("\t\tWhen updating AnimInstance: %s"), *Context.AnimInstance->GetName()) return; } }
void FAnimNode_StateMachine::Initialize(const FAnimationInitializeContext& Context) { UAnimBlueprintGeneratedClass* AnimBlueprintClass = Context.GetAnimBlueprintClass(); PRIVATE_MachineDescription = AnimBlueprintClass->BakedStateMachines.IsValidIndex(StateMachineIndexInClass) ? &(AnimBlueprintClass->BakedStateMachines[StateMachineIndexInClass]) : NULL; if (FBakedAnimationStateMachine* Machine = GetMachineDescription()) { ElapsedTime = 0.0f; CurrentState = INDEX_NONE; if (Machine->States.Num() > 0) { // Create a pose link for each state we can reach StatePoseLinks.Empty(Machine->States.Num()); for (int32 StateIndex = 0; StateIndex < Machine->States.Num(); ++StateIndex) { FPoseLink* StatePoseLink = new (StatePoseLinks) FPoseLink(); // because conduits don't contain bound graphs, this link is no longer guaranteed to be valid if (Machine->States[StateIndex].StateRootNodeIndex != INDEX_NONE) { StatePoseLink->LinkID = AnimBlueprintClass->AnimNodeProperties.Num() - 1 - Machine->States[StateIndex].StateRootNodeIndex; //@TODO: Crazysauce } } // Reset transition related variables StatesUpdated.Empty(StatesUpdated.Num()); ActiveTransitionArray.Empty(ActiveTransitionArray.Num()); // Move to the default state SetState(Context, Machine->InitialState); // initialize first update bFirstUpdate = true; // Fire off any entry notifications the default state has Context.AnimInstance->AddAnimNotifyFromGeneratedClass(GetStateInfo(Machine->InitialState).StartNotify); } } }
void FAnimNode_StateMachine::Initialize(const FAnimationInitializeContext& Context) { FAnimNode_Base::Initialize(Context); IAnimClassInterface* AnimBlueprintClass = Context.GetAnimClass(); if (const FBakedAnimationStateMachine* Machine = GetMachineDescription()) { ElapsedTime = 0.0f; CurrentState = INDEX_NONE; if (Machine->States.Num() > 0) { // Create a pose link for each state we can reach StatePoseLinks.Reset(); StatePoseLinks.Reserve(Machine->States.Num()); for (int32 StateIndex = 0; StateIndex < Machine->States.Num(); ++StateIndex) { const FBakedAnimationState& State = Machine->States[StateIndex]; FPoseLink* StatePoseLink = new (StatePoseLinks) FPoseLink(); // because conduits don't contain bound graphs, this link is no longer guaranteed to be valid if (State.StateRootNodeIndex != INDEX_NONE) { StatePoseLink->LinkID = AnimBlueprintClass->GetAnimNodeProperties().Num() - 1 - State.StateRootNodeIndex; //@TODO: Crazysauce } // also initialize transitions if(State.EntryRuleNodeIndex != INDEX_NONE) { if (FAnimNode_TransitionResult* TransitionNode = GetNodeFromPropertyIndex<FAnimNode_TransitionResult>(Context.AnimInstanceProxy->GetAnimInstanceObject(), AnimBlueprintClass, State.EntryRuleNodeIndex)) { TransitionNode->Initialize(Context); } } for(int32 TransitionIndex = 0; TransitionIndex < State.Transitions.Num(); ++TransitionIndex) { const FBakedStateExitTransition& TransitionRule = State.Transitions[TransitionIndex]; if (TransitionRule.CanTakeDelegateIndex != INDEX_NONE) { if (FAnimNode_TransitionResult* TransitionNode = GetNodeFromPropertyIndex<FAnimNode_TransitionResult>(Context.AnimInstanceProxy->GetAnimInstanceObject(), AnimBlueprintClass, TransitionRule.CanTakeDelegateIndex)) { TransitionNode->Initialize(Context); } } } } // Reset transition related variables StatesUpdated.Reset(); ActiveTransitionArray.Reset(); StateCacheBoneCounters.Reset(Machine->States.Num()); StateCacheBoneCounters.AddDefaulted(Machine->States.Num()); // Move to the default state SetState(Context, Machine->InitialState); // initialize first update bFirstUpdate = true; } } }
void FAnimNode_StateMachine::GatherDebugData(FNodeDebugData& DebugData) { FString DebugLine = DebugData.GetNodeName(this); DebugLine += FString::Printf(TEXT("(%s->%s)"), *GetMachineDescription()->MachineName.ToString(), *GetStateInfo().StateName.ToString()); TMap<int32, float> StateWeightMap; if (ActiveTransitionArray.Num() > 0) { for (int32 Index = 0; Index < ActiveTransitionArray.Num(); ++Index) { // if there is any source pose, blend it here FAnimationActiveTransitionEntry& ActiveTransition = ActiveTransitionArray[Index]; if (ActiveTransition.bActive) { switch (ActiveTransition.LogicType) { case ETransitionLogicType::TLT_StandardBlend: { AddStateWeight(StateWeightMap, ActiveTransition.PreviousState, (1.0f - ActiveTransition.Alpha)); AddStateWeight(StateWeightMap, ActiveTransition.NextState, (ActiveTransition.Alpha)); break; } case ETransitionLogicType::TLT_Custom: { if (ActiveTransition.CustomTransitionGraph.LinkID != INDEX_NONE) { for (TArray<FAnimNode_TransitionPoseEvaluator*>::TIterator PoseEvaluatorListIt = ActiveTransition.PoseEvaluators.CreateIterator(); PoseEvaluatorListIt; ++PoseEvaluatorListIt) { FAnimNode_TransitionPoseEvaluator* Evaluator = *PoseEvaluatorListIt; if (Evaluator->InputNodeNeedsUpdate()) { const bool bUsePreviousState = (Evaluator->DataSource == EEvaluatorDataSource::EDS_SourcePose); const int32 EffectiveStateIndex = bUsePreviousState ? ActiveTransition.PreviousState : ActiveTransition.NextState; AddStateWeight(StateWeightMap, EffectiveStateIndex, 1.f); } } } break; } default: { break; } } } } } else if (!IsAConduitState(CurrentState)) { StateWeightMap.Add(CurrentState) = 1.0f; } DebugData.AddDebugItem(DebugLine); for (int32 PoseIndex = 0; PoseIndex < StatePoseLinks.Num(); ++PoseIndex) { float* WeightPtr = StateWeightMap.Find(PoseIndex); float Weight = WeightPtr ? *WeightPtr : 0.f; StatePoseLinks[PoseIndex].GatherDebugData(DebugData.BranchFlow(Weight)); } }
void FAnimNode_StateMachine::Update(const FAnimationUpdateContext& Context) { if (FBakedAnimationStateMachine* Machine = GetMachineDescription()) { if (Machine->States.Num() == 0) { return; } } else { return; } SCOPE_CYCLE_COUNTER(STAT_AnimStateMachineUpdate); bool bFoundValidTransition = false; int32 TransitionCountThisFrame = 0; int32 TransitionIndex = INDEX_NONE; // Look for legal transitions to take; can move across multiple states in one frame (up to MaxTransitionsPerFrame) do { bFoundValidTransition = false; FAnimationPotentialTransition PotentialTransition; { SCOPE_CYCLE_COUNTER(STAT_AnimStateMachineFindTransition); // Evaluate possible transitions out of this state //@TODO: Evaluate if a set is better than an array for the probably low N encountered here TArray<int32, TInlineAllocator<4>> VisitedStateIndices; FindValidTransition(Context, GetStateInfo(), /*Out*/ PotentialTransition, /*Out*/ VisitedStateIndices); } // If transition is valid and not waiting on other conditions if (PotentialTransition.IsValid()) { bFoundValidTransition = true; // let the latest transition know it has been interrupted if ((ActiveTransitionArray.Num() > 0) && ActiveTransitionArray[ActiveTransitionArray.Num()-1].bActive) { Context.AnimInstance->AddAnimNotifyFromGeneratedClass(ActiveTransitionArray[ActiveTransitionArray.Num()-1].InterruptNotify); } const int32 PreviousState = CurrentState; const int32 NextState = PotentialTransition.TargetState; // Fire off Notifies for state transition if (!bFirstUpdate) { Context.AnimInstance->AddAnimNotifyFromGeneratedClass(GetStateInfo(PreviousState).EndNotify); Context.AnimInstance->AddAnimNotifyFromGeneratedClass(GetStateInfo(NextState).StartNotify); } // Get the current weight of the next state, which may be non-zero const float ExistingWeightOfNextState = GetStateWeight(NextState); // Push the transition onto the stack const FAnimationTransitionBetweenStates& ReferenceTransition = GetTransitionInfo(PotentialTransition.TransitionRule->TransitionIndex); FAnimationActiveTransitionEntry* NewTransition = new (ActiveTransitionArray) FAnimationActiveTransitionEntry(NextState, ExistingWeightOfNextState, PreviousState, ReferenceTransition); NewTransition->InitializeCustomGraphLinks(Context, *(PotentialTransition.TransitionRule)); #if WITH_EDITORONLY_DATA NewTransition->SourceTransitionIndices = PotentialTransition.SourceTransitionIndices; #endif if (!bFirstUpdate) { Context.AnimInstance->AddAnimNotifyFromGeneratedClass(NewTransition->StartNotify); } SetState(Context, NextState); TransitionCountThisFrame++; } } while (bFoundValidTransition && (TransitionCountThisFrame < MaxTransitionsPerFrame)); if (bFirstUpdate) { //Handle enter notify for "first" (after initial transitions) state Context.AnimInstance->AddAnimNotifyFromGeneratedClass(GetStateInfo().StartNotify); // in the first update, we don't like to transition from entry state // so we throw out any transition data at the first update ActiveTransitionArray.Reset(); bFirstUpdate = false; } StatesUpdated.Empty(StatesUpdated.Num()); // Tick the individual state/states that are active if (ActiveTransitionArray.Num() > 0) { for (int32 Index = 0; Index < ActiveTransitionArray.Num(); ++Index) { // The custom graph will tick the needed states bool bFinishedTrans = false; // The custom graph will tick the needed states ActiveTransitionArray[Index].Update(Context, CurrentState, /*out*/ bFinishedTrans); if (bFinishedTrans) { // only play these events if it is the last transition (most recent, going to current state) if (Index == (ActiveTransitionArray.Num() - 1)) { Context.AnimInstance->AddAnimNotifyFromGeneratedClass(ActiveTransitionArray[Index].EndNotify); Context.AnimInstance->AddAnimNotifyFromGeneratedClass(GetStateInfo().FullyBlendedNotify); } } else { // transition is still active, so tick the required states UpdateTransitionStates(Context, ActiveTransitionArray[Index]); } } // remove finished transitions here, newer transitions ending means any older ones must complete as well for (int32 Index = (ActiveTransitionArray.Num()-1); Index >= 0; --Index) { // if we find an inactive one, remove all older transitions and break out if (!ActiveTransitionArray[Index].bActive) { ActiveTransitionArray.RemoveAt(0, Index+1); break; } } } //@TODO: StatesUpdated.Contains is a linear search // Update the only active state if there are no transitions still in flight if (ActiveTransitionArray.Num() == 0 && !IsAConduitState(CurrentState) && !StatesUpdated.Contains(CurrentState)) { StatePoseLinks[CurrentState].Update(Context); } ElapsedTime += Context.GetDeltaTime(); }