void FAnimNode_StateMachine::SetState(const FAnimationBaseContext& Context, int32 NewStateIndex) { if (NewStateIndex != CurrentState) { const int32 PrevStateIndex = CurrentState; if(CurrentState != INDEX_NONE && CurrentState < OnGraphStatesExited.Num()) { OnGraphStatesExited[CurrentState].ExecuteIfBound(*this, CurrentState, NewStateIndex); } // Determine if the new state is active or not const bool bAlreadyActive = GetStateWeight(NewStateIndex) > 0.0f; SetStateInternal(NewStateIndex); if (!bAlreadyActive && !IsAConduitState(NewStateIndex)) { // Initialize the new state since it's not part of an active transition (and thus not still initialized) FAnimationInitializeContext InitContext(Context.AnimInstance); StatePoseLinks[NewStateIndex].Initialize(InitContext); // Also update BoneCaching. FAnimationCacheBonesContext CacheBoneContext(Context.AnimInstance); StatePoseLinks[NewStateIndex].CacheBones(CacheBoneContext); } if(CurrentState != INDEX_NONE && CurrentState < OnGraphStatesEntered.Num()) { OnGraphStatesEntered[CurrentState].ExecuteIfBound(*this, PrevStateIndex, CurrentState); } } }
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 { 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(); }