Files
UnrealEngine/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_StateMachineBase.cpp
2025-05-18 13:04:45 +08:00

786 lines
30 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimGraphNode_StateMachineBase.h"
#include "EdGraph/EdGraph.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/Kismet2NameValidators.h"
#include "AnimationGraph.h"
#include "AnimationStateMachineGraph.h"
#include "AnimationStateMachineSchema.h"
#include "AnimGraphNode_StateMachine.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "AnimBlueprintCompiler.h"
#include "AnimStateNode.h"
#include "AnimStateTransitionNode.h"
#include "AnimGraphNode_TransitionResult.h"
#include "AnimGraphNode_StateResult.h"
#include "AnimStateEntryNode.h"
#include "AnimationStateGraph.h"
#include "AnimationTransitionGraph.h"
#include "AnimationCustomTransitionGraph.h"
#include "AnimGraphNode_SequencePlayer.h"
#include "AnimStateConduitNode.h"
#include "AnimGraphNode_LinkedAnimGraphBase.h"
#include "AnimGraphNode_RandomPlayer.h"
#include "AnimGraphNode_TransitionPoseEvaluator.h"
#include "AnimGraphNode_CustomTransitionResult.h"
#include "AnimBlueprintExtension_StateMachine.h"
#include "IAnimBlueprintGeneratedClassCompiledData.h"
#include "IAnimBlueprintCompilationContext.h"
#include "Animation/AnimNode_Inertialization.h"
#include "AnimStateAliasNode.h"
/////////////////////////////////////////////////////
// FAnimStateMachineNodeNameValidator
class FAnimStateMachineNodeNameValidator : public FStringSetNameValidator
{
public:
FAnimStateMachineNodeNameValidator(const UAnimGraphNode_StateMachineBase* InStateMachineNode)
: FStringSetNameValidator(FString())
{
Names.Add(TEXT("None"));
TArray<UAnimGraphNode_StateMachineBase*> Nodes;
UAnimationGraph* StateMachine = CastChecked<UAnimationGraph>(InStateMachineNode->GetOuter());
StateMachine->GetNodesOfClassEx<UAnimGraphNode_StateMachine, UAnimGraphNode_StateMachineBase>(Nodes);
for (auto Node : Nodes)
{
if (Node != InStateMachineNode)
{
Names.Add(Node->GetStateMachineName());
}
}
// Include the name of animation layers
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(StateMachine);
if (Blueprint)
{
UClass* TargetClass = *Blueprint->SkeletonGeneratedClass;
if (TargetClass)
{
IAnimClassInterface* AnimClassInterface = IAnimClassInterface::GetFromClass(TargetClass);
for (const FAnimBlueprintFunction& AnimBlueprintFunction : AnimClassInterface->GetAnimBlueprintFunctions())
{
if (AnimBlueprintFunction.Name != UEdGraphSchema_K2::GN_AnimGraph)
{
Names.Add(AnimBlueprintFunction.Name.ToString());
}
}
}
}
}
};
/////////////////////////////////////////////////////
// UAnimGraphNode_StateMachineBase
#define LOCTEXT_NAMESPACE "A3Nodes"
UAnimGraphNode_StateMachineBase::UAnimGraphNode_StateMachineBase(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
FLinearColor UAnimGraphNode_StateMachineBase::GetNodeTitleColor() const
{
return FLinearColor(0.8f, 0.8f, 0.8f);
}
FText UAnimGraphNode_StateMachineBase::GetTooltipText() const
{
return LOCTEXT("StateMachineTooltip", "Animation State Machine");
}
FText UAnimGraphNode_StateMachineBase::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
if ((TitleType == ENodeTitleType::MenuTitle || TitleType == ENodeTitleType::ListView) && (EditorStateMachineGraph == nullptr))
{
return LOCTEXT("AddNewStateMachine", "State Machine");
}
else if (EditorStateMachineGraph == nullptr)
{
if (TitleType == ENodeTitleType::FullTitle)
{
return LOCTEXT("NullStateMachineFullTitle", "Error: No Graph\nState Machine");
}
else
{
return LOCTEXT("ErrorNoGraph", "Error: No Graph");
}
}
else if (TitleType == ENodeTitleType::FullTitle)
{
if (CachedFullTitle.IsOutOfDate(this))
{
FFormatNamedArguments Args;
Args.Add(TEXT("Title"), FText::FromName(EditorStateMachineGraph->GetFName()));
// FText::Format() is slow, so we cache this to save on performance
CachedFullTitle.SetCachedText(FText::Format(LOCTEXT("StateMachineFullTitle", "{Title}\nState Machine"), Args), this);
}
return CachedFullTitle;
}
return FText::FromName(EditorStateMachineGraph->GetFName());
}
FText UAnimGraphNode_StateMachineBase::GetMenuCategory() const
{
return LOCTEXT("StateMachineCategory", "Animation|State Machines");
}
void UAnimGraphNode_StateMachineBase::PostPlacedNewNode()
{
Super::PostPlacedNewNode();
// Create a new animation graph
check(EditorStateMachineGraph == NULL);
EditorStateMachineGraph = CastChecked<UAnimationStateMachineGraph>(FBlueprintEditorUtils::CreateNewGraph(this, NAME_None, UAnimationStateMachineGraph::StaticClass(), UAnimationStateMachineSchema::StaticClass()));
check(EditorStateMachineGraph);
EditorStateMachineGraph->OwnerAnimGraphNode = this;
// Find an interesting name
TSharedPtr<INameValidatorInterface> NameValidator = FNameValidatorFactory::MakeValidator(this);
FBlueprintEditorUtils::RenameGraphWithSuggestion(EditorStateMachineGraph, NameValidator, TEXT("New State Machine"));
// Initialize the anim graph
const UEdGraphSchema* Schema = EditorStateMachineGraph->GetSchema();
Schema->CreateDefaultNodesForGraph(*EditorStateMachineGraph);
// Add the new graph as a child of our parent graph
UEdGraph* ParentGraph = GetGraph();
if(ParentGraph->SubGraphs.Find(EditorStateMachineGraph) == INDEX_NONE)
{
ParentGraph->Modify();
ParentGraph->SubGraphs.Add(EditorStateMachineGraph);
}
}
UObject* UAnimGraphNode_StateMachineBase::GetJumpTargetForDoubleClick() const
{
// Open the state machine graph
return EditorStateMachineGraph;
}
void UAnimGraphNode_StateMachineBase::JumpToDefinition() const
{
if (UObject* HyperlinkTarget = GetJumpTargetForDoubleClick())
{
FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(HyperlinkTarget);
}
}
void UAnimGraphNode_StateMachineBase::DestroyNode()
{
UEdGraph* GraphToRemove = EditorStateMachineGraph;
EditorStateMachineGraph = NULL;
Super::DestroyNode();
if (GraphToRemove)
{
UBlueprint* Blueprint = GetBlueprint();
GraphToRemove->Modify();
FBlueprintEditorUtils::RemoveGraph(Blueprint, GraphToRemove, EGraphRemoveFlags::Recompile);
}
}
void UAnimGraphNode_StateMachineBase::PostPasteNode()
{
Super::PostPasteNode();
if(EditorStateMachineGraph)
{
// Add the new graph as a child of our parent graph
UEdGraph* ParentGraph = GetGraph();
if(ParentGraph->SubGraphs.Find(EditorStateMachineGraph) == INDEX_NONE)
{
ParentGraph->SubGraphs.Add(EditorStateMachineGraph);
}
for (UEdGraphNode* GraphNode : EditorStateMachineGraph->Nodes)
{
GraphNode->CreateNewGuid();
GraphNode->PostPasteNode();
GraphNode->ReconstructNode();
}
// Find an interesting name
TSharedPtr<INameValidatorInterface> NameValidator = FNameValidatorFactory::MakeValidator(this);
FBlueprintEditorUtils::RenameGraphWithSuggestion(EditorStateMachineGraph, NameValidator, EditorStateMachineGraph->GetName());
//restore transactional flag that is lost during copy/paste process
EditorStateMachineGraph->SetFlags(RF_Transactional);
}
}
FString UAnimGraphNode_StateMachineBase::GetStateMachineName()
{
return (EditorStateMachineGraph != NULL) ? *(EditorStateMachineGraph->GetName()) : TEXT("(null)");
}
TSharedPtr<class INameValidatorInterface> UAnimGraphNode_StateMachineBase::MakeNameValidator() const
{
return MakeShareable(new FAnimStateMachineNodeNameValidator(this));
}
FString UAnimGraphNode_StateMachineBase::GetDocumentationLink() const
{
return TEXT("Shared/GraphNodes/AnimationStateMachine");
}
void UAnimGraphNode_StateMachineBase::OnRenameNode(const FString& NewName)
{
FBlueprintEditorUtils::RenameGraph(EditorStateMachineGraph, NewName);
}
TArray<UEdGraph*> UAnimGraphNode_StateMachineBase::GetSubGraphs() const
{
return TArray<UEdGraph*>( { EditorStateMachineGraph } );
}
void UAnimGraphNode_StateMachineBase::OnProcessDuringCompilation(IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData)
{
struct FMachineCreator
{
public:
// A transition node with a state alias as source may have multiple transitions belonging to the same node. The referenced aliased state to differentiates them during compilation.
using FUniqueTransition = TPair<UAnimStateTransitionNode*, UAnimStateNodeBase*>;
int32 MachineIndex;
TMap<UAnimStateNodeBase*, int32> StateIndexTable;
TMultiMap<int32, int32> StateIndexToStateAliasNodeIndices;
TArray<const UAnimStateAliasNode*> StateAliasNodes;
TMap<FUniqueTransition, int32> TransitionIndexTable;
UAnimGraphNode_StateMachineBase* StateMachineInstance;
TArray<FBakedAnimationStateMachine>& BakedMachines;
IAnimBlueprintGeneratedClassCompiledData& CompiledData;
IAnimBlueprintCompilationContext& CompilationContext;
public:
FMachineCreator(UAnimGraphNode_StateMachineBase* InStateMachineInstance, int32 InMachineIndex, TArray<FBakedAnimationStateMachine>& InBakedMachines, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& InCompiledData)
: MachineIndex(InMachineIndex)
, StateMachineInstance(InStateMachineInstance)
, BakedMachines(InBakedMachines)
, CompiledData(InCompiledData)
, CompilationContext(InCompilationContext)
{
FStateMachineDebugData& MachineInfo = GetMachineSpecificDebugData();
MachineInfo.MachineIndex = MachineIndex;
MachineInfo.MachineInstanceNode = CompilationContext.GetMessageLog().FindSourceObjectTypeChecked<UAnimGraphNode_StateMachineBase>(InStateMachineInstance);
StateMachineInstance->GetNode().StateMachineIndexInClass = MachineIndex;
FBakedAnimationStateMachine& BakedMachine = GetMachine();
BakedMachine.MachineName = StateMachineInstance->EditorStateMachineGraph->GetFName();
BakedMachine.InitialState = INDEX_NONE;
}
FBakedAnimationStateMachine& GetMachine()
{
return BakedMachines[MachineIndex];
}
FStateMachineDebugData& GetMachineSpecificDebugData()
{
UAnimationStateMachineGraph* SourceGraph = CompilationContext.GetMessageLog().FindSourceObjectTypeChecked<UAnimationStateMachineGraph>(StateMachineInstance->EditorStateMachineGraph);
return CompiledData.GetAnimBlueprintDebugData().StateMachineDebugData.FindOrAdd(SourceGraph);
}
int32 FindState(UAnimStateNodeBase* StateNode)
{
if (int32* pResult = StateIndexTable.Find(StateNode))
{
return *pResult;
}
return INDEX_NONE;
}
int32 FindOrAddState(UAnimStateNodeBase* StateNode)
{
if (int32* pResult = StateIndexTable.Find(StateNode))
{
return *pResult;
}
else
{
FBakedAnimationStateMachine& BakedMachine = GetMachine();
const int32 StateIndex = BakedMachine.States.Num();
StateIndexTable.Add(StateNode, StateIndex);
new (BakedMachine.States) FBakedAnimationState();
UAnimStateNodeBase* SourceNode = CompilationContext.GetMessageLog().FindSourceObjectTypeChecked<UAnimStateNodeBase>(StateNode);
GetMachineSpecificDebugData().NodeToStateIndex.Add(SourceNode, StateIndex);
GetMachineSpecificDebugData().StateIndexToNode.Add(StateIndex, SourceNode);
if (UAnimStateNode* SourceStateNode = Cast<UAnimStateNode>(SourceNode))
{
CompiledData.GetAnimBlueprintDebugData().StateGraphToNodeMap.Add(SourceStateNode->BoundGraph, SourceStateNode);
}
return StateIndex;
}
}
void AddStateAliasTransitionMapping(UAnimStateAliasNode* AliasNode, const FStateMachineDebugData::FStateAliasTransitionStateIndexPair& TransitionStateIndexPair)
{
UAnimStateAliasNode* SourceNode = CompilationContext.GetMessageLog().FindSourceObjectTypeChecked<UAnimStateAliasNode>(AliasNode);
GetMachineSpecificDebugData().StateAliasNodeToTransitionStatePairs.Add(SourceNode, TransitionStateIndexPair);
}
int32 FindOrAddTransition(FUniqueTransition UniqueTransition)
{
if (int32* pResult = TransitionIndexTable.Find(UniqueTransition))
{
return *pResult;
}
else
{
FBakedAnimationStateMachine& BakedMachine = GetMachine();
const int32 TransitionIndex = BakedMachine.Transitions.Num();
TransitionIndexTable.Add(UniqueTransition, TransitionIndex);
new (BakedMachine.Transitions) FAnimationTransitionBetweenStates();
UAnimStateTransitionNode* TransitionNode = UniqueTransition.Get<0>();
UAnimStateTransitionNode* SourceTransitionNode = CompilationContext.GetMessageLog().FindSourceObjectTypeChecked<UAnimStateTransitionNode>(TransitionNode);
GetMachineSpecificDebugData().NodeToTransitionIndex.Add(SourceTransitionNode, TransitionIndex);
CompiledData.GetAnimBlueprintDebugData().TransitionGraphToNodeMap.Add(SourceTransitionNode->BoundGraph, SourceTransitionNode);
if (SourceTransitionNode->CustomTransitionGraph != NULL)
{
CompiledData.GetAnimBlueprintDebugData().TransitionBlendGraphToNodeMap.Add(SourceTransitionNode->CustomTransitionGraph, SourceTransitionNode);
}
return TransitionIndex;
}
}
void Validate()
{
FBakedAnimationStateMachine& BakedMachine = GetMachine();
// Make sure there is a valid entry point
if (BakedMachine.InitialState == INDEX_NONE)
{
CompilationContext.GetMessageLog().Warning(*LOCTEXT("NoEntryNode", "There was no entry state connection in @@").ToString(), StateMachineInstance);
BakedMachine.InitialState = 0;
}
else
{
// Make sure the entry node is a state and not a conduit
if (BakedMachine.States[BakedMachine.InitialState].bIsAConduit && !StateMachineInstance->GetNode().bAllowConduitEntryStates)
{
UEdGraphNode* StateNode = GetMachineSpecificDebugData().FindNodeFromStateIndex(BakedMachine.InitialState);
CompilationContext.GetMessageLog().Error(*LOCTEXT("BadStateEntryNode",
"A conduit (@@) cannot be used as the entry node for a state machine. To enable this, check the 'Allow conduit entry states' checkbox for StateMachine. Warning, if a valid entry state cannot be found at runtime then this will generate a reference pose!"
).ToString(), StateNode);
}
}
}
};
UAnimBlueprintExtension_StateMachine* Extension = UAnimBlueprintExtension::GetExtension<UAnimBlueprintExtension_StateMachine>(GetAnimBlueprint());
check(Extension);
if (EditorStateMachineGraph == NULL)
{
InCompilationContext.GetMessageLog().Error(*LOCTEXT("BadStateMachineNoGraph", "@@ does not have a corresponding graph").ToString(), this);
return;
}
TMap<UAnimGraphNode_TransitionResult*, int32> AlreadyMergedTransitionList;
TArray<FBakedAnimationStateMachine>& BakedStateMachines = OutCompiledData.GetBakedStateMachines();
const int32 MachineIndex = BakedStateMachines.AddDefaulted();
FMachineCreator Oven(this, MachineIndex, BakedStateMachines, InCompilationContext, OutCompiledData);
// Map of states that contain a single player node (from state root node index to associated sequence player)
TMap<int32, UObject*> SimplePlayerStatesMap;
// Process all the states/transitions
for (auto StateNodeIt = EditorStateMachineGraph->Nodes.CreateIterator(); StateNodeIt; ++StateNodeIt)
{
UEdGraphNode* Node = *StateNodeIt;
if (UAnimStateNodeBase* StateNodeBase = Cast<UAnimStateNodeBase>(Node))
{
StateNodeBase->ValidateNodeDuringCompilation(InCompilationContext.GetMessageLog());
}
if (UAnimStateEntryNode* EntryNode = Cast<UAnimStateEntryNode>(Node))
{
// Handle the state graph entry
FBakedAnimationStateMachine& BakedMachine = Oven.GetMachine();
if (BakedMachine.InitialState != INDEX_NONE)
{
InCompilationContext.GetMessageLog().Error(*LOCTEXT("TooManyStateMachineEntryNodes", "Found an extra entry node @@").ToString(), EntryNode);
}
else if (UAnimStateAliasNode* AliasState = Cast<UAnimStateAliasNode>(EntryNode->GetOutputNode()))
{
InCompilationContext.GetMessageLog().Error(*LOCTEXT("AliasAsEntryState", "An alias (@@) cannot be used as the entry node for a state machine").ToString(), AliasState);
}
else if (UAnimStateNodeBase* StartState = Cast<UAnimStateNodeBase>(EntryNode->GetOutputNode()))
{
BakedMachine.InitialState = Oven.FindOrAddState(StartState);
}
else
{
InCompilationContext.GetMessageLog().Warning(*LOCTEXT("NoConnection", "Entry node @@ is not connected to state").ToString(), EntryNode);
}
}
else if (UAnimStateNode* StateNode = Cast<UAnimStateNode>(Node))
{
const int32 StateIndex = Oven.FindOrAddState(StateNode);
FBakedAnimationState& BakedState = Oven.GetMachine().States[StateIndex];
if (StateNode->BoundGraph != NULL)
{
BakedState.StateName = StateNode->BoundGraph->GetFName();
BakedState.StartNotify = OutCompiledData.FindOrAddNotify(StateNode->StateEntered);
BakedState.EndNotify = OutCompiledData.FindOrAddNotify(StateNode->StateLeft);
BakedState.FullyBlendedNotify = OutCompiledData.FindOrAddNotify(StateNode->StateFullyBlended);
BakedState.bIsAConduit = false;
BakedState.bAlwaysResetOnEntry = StateNode->bAlwaysResetOnEntry;
// Process the inner graph of this state
if (UAnimGraphNode_StateResult* AnimGraphResultNode = CastChecked<UAnimationStateGraph>(StateNode->BoundGraph)->GetResultNode())
{
InCompilationContext.ValidateGraphIsWellFormed(StateNode->BoundGraph);
AnimGraphResultNode->Node.SetStateIndex(StateIndex);
BakedState.StateRootNodeIndex = Extension->ExpandGraphAndProcessNodes(StateNode->BoundGraph, AnimGraphResultNode, InCompilationContext, OutCompiledData);
// See if the state consists of a single sequence player node, and remember the index if so
for (UEdGraphPin* TestPin : AnimGraphResultNode->Pins)
{
if ((TestPin->Direction == EGPD_Input) && (TestPin->LinkedTo.Num() == 1))
{
if (UAnimGraphNode_SequencePlayer* SequencePlayer = Cast<UAnimGraphNode_SequencePlayer>(TestPin->LinkedTo[0]->GetOwningNode()))
{
SimplePlayerStatesMap.Add(BakedState.StateRootNodeIndex, InCompilationContext.GetMessageLog().FindSourceObject(SequencePlayer));
}
}
}
}
else
{
BakedState.StateRootNodeIndex = INDEX_NONE;
InCompilationContext.GetMessageLog().Error(*LOCTEXT("StateWithNoResult", "@@ has no result node").ToString(), StateNode);
}
}
else
{
BakedState.StateName = NAME_None;
InCompilationContext.GetMessageLog().Error(*LOCTEXT("StateWithBadGraph", "@@ has no bound graph").ToString(), StateNode);
}
// If this check fires, then something in the machine has changed causing the states array to not
// be a separate allocation, and a state machine inside of this one caused stuff to shift around
checkSlow(&BakedState == &(Oven.GetMachine().States[StateIndex]));
}
else if (UAnimStateConduitNode* ConduitNode = Cast<UAnimStateConduitNode>(Node))
{
const int32 StateIndex = Oven.FindOrAddState(ConduitNode);
FBakedAnimationState& BakedState = Oven.GetMachine().States[StateIndex];
BakedState.StateName = ConduitNode->BoundGraph ? ConduitNode->BoundGraph->GetFName() : TEXT("OLD CONDUIT");
BakedState.bIsAConduit = true;
if (ConduitNode->BoundGraph != NULL)
{
if (UAnimGraphNode_TransitionResult* EntryRuleResultNode = CastChecked<UAnimationTransitionGraph>(ConduitNode->BoundGraph)->GetResultNode())
{
BakedState.EntryRuleNodeIndex = Extension->ExpandGraphAndProcessNodes(ConduitNode->BoundGraph, EntryRuleResultNode, InCompilationContext, OutCompiledData);
}
}
// If this check fires, then something in the machine has changed causing the states array to not
// be a separate allocation, and a state machine inside of this one caused stuff to shift around
checkSlow(&BakedState == &(Oven.GetMachine().States[StateIndex]));
}
else if (UAnimStateAliasNode* StateAliasNode = Cast<UAnimStateAliasNode>(Node))
{
Oven.StateAliasNodes.Add(StateAliasNode);
}
}
int32 NumStateAlias = Oven.StateAliasNodes.Num();
for (auto AliasIndex = 0; AliasIndex < NumStateAlias; ++AliasIndex)
{
const UAnimStateAliasNode* StateAliasNode = Oven.StateAliasNodes[AliasIndex];
auto MapAlias = [&](const UAnimStateNodeBase* StateNode)
{
if (StateNode)
{
if (int32* StateIndexPtr = Oven.StateIndexTable.Find(StateNode))
{
Oven.StateIndexToStateAliasNodeIndices.Add(*StateIndexPtr, AliasIndex);
}
}
};
if (StateAliasNode->bGlobalAlias)
{
for (auto StateNodeIt = Oven.StateIndexTable.CreateConstIterator(); StateNodeIt; ++StateNodeIt)
{
MapAlias(StateNodeIt->Key);
}
}
else
{
for (auto StateNodeWeakIt = StateAliasNode->GetAliasedStates().CreateConstIterator(); StateNodeWeakIt; ++StateNodeWeakIt)
{
MapAlias(StateNodeWeakIt->Get());
}
}
}
// Process transitions after all the states because getters within custom graphs may want to
// reference back to other states, which are only valid if they have already been baked
for (auto StateNodeIt = Oven.StateIndexTable.CreateIterator(); StateNodeIt; ++StateNodeIt)
{
UAnimStateNodeBase* StateNode = StateNodeIt.Key();
const int32 StateIndex = StateNodeIt.Value();
FBakedAnimationState& BakedState = Oven.GetMachine().States[StateIndex];
// Add indices to all player and layer nodes
TArray<UEdGraph*> GraphsToCheck;
TArray<UEdGraph*> SubGraphs = StateNode->GetSubGraphs();
GraphsToCheck.Append(SubGraphs);
for(UEdGraph* SubGraph : SubGraphs)
{
SubGraph->GetAllChildrenGraphs(GraphsToCheck);
}
TArray<UAnimGraphNode_LinkedAnimGraphBase*> LinkedAnimGraphNodes;
TArray<UAnimGraphNode_AssetPlayerBase*> AssetPlayerNodes;
TArray<UAnimGraphNode_RandomPlayer*> AssetRandomPlayerPlayerNodes;
for (UEdGraph* ChildGraph : GraphsToCheck)
{
ChildGraph->GetNodesOfClass(AssetPlayerNodes);
ChildGraph->GetNodesOfClass(AssetRandomPlayerPlayerNodes);
ChildGraph->GetNodesOfClass(LinkedAnimGraphNodes);
}
for (UAnimGraphNode_AssetPlayerBase* Node : AssetPlayerNodes)
{
if (int32* IndexPtr = OutCompiledData.GetAnimBlueprintDebugData().NodeGuidToIndexMap.Find(Node->NodeGuid))
{
BakedState.PlayerNodeIndices.Add(*IndexPtr);
}
}
for (UAnimGraphNode_RandomPlayer* Node : AssetRandomPlayerPlayerNodes)
{
if (int32* IndexPtr = OutCompiledData.GetAnimBlueprintDebugData().NodeGuidToIndexMap.Find(Node->NodeGuid))
{
BakedState.PlayerNodeIndices.Add(*IndexPtr);
}
}
for (UAnimGraphNode_LinkedAnimGraphBase* Node : LinkedAnimGraphNodes)
{
if (int32* IndexPtr = OutCompiledData.GetAnimBlueprintDebugData().NodeGuidToIndexMap.Find(Node->NodeGuid))
{
BakedState.LayerNodeIndices.Add(*IndexPtr);
}
}
// Handle all the transitions out of this node
TArray<class UAnimStateTransitionNode*> TransitionList;
// Add aliased state transitions to transition list
TArray<int32> StateAliasIndices;
Oven.StateIndexToStateAliasNodeIndices.MultiFind(StateIndex, StateAliasIndices);
for (const int32 AliasIndex : StateAliasIndices)
{
// Let the final transition list do the sort.
Oven.StateAliasNodes[AliasIndex]->GetTransitionList(TransitionList, /*bWantSortedList=*/ false);
}
StateNode->GetTransitionList(/*out*/ TransitionList, /*bWantSortedList=*/ true);
for (auto TransitionIt = TransitionList.CreateIterator(); TransitionIt; ++TransitionIt)
{
UAnimStateTransitionNode* TransitionNode = *TransitionIt;
if (TransitionNode->bDisabled)
{
continue;
}
TransitionNode->BlendProfileWrapper.UpdateCachedBlendProfile();
const int32 TransitionIndex = Oven.FindOrAddTransition(FMachineCreator::FUniqueTransition(TransitionNode, StateNode));
FAnimationTransitionBetweenStates& BakedTransition = Oven.GetMachine().Transitions[TransitionIndex];
BakedTransition.CrossfadeDuration = TransitionNode->CrossfadeDuration;
BakedTransition.StartNotify = OutCompiledData.FindOrAddNotify(TransitionNode->TransitionStart);
BakedTransition.EndNotify = OutCompiledData.FindOrAddNotify(TransitionNode->TransitionEnd);
BakedTransition.InterruptNotify = OutCompiledData.FindOrAddNotify(TransitionNode->TransitionInterrupt);
BakedTransition.BlendMode = TransitionNode->BlendMode;
BakedTransition.CustomCurve = TransitionNode->CustomBlendCurve;
BakedTransition.BlendProfile = TransitionNode->BlendProfileWrapper.GetBlendProfile();
BakedTransition.LogicType = TransitionNode->LogicType;
UAnimStateNodeBase* PreviousState = StateNode;
UAnimStateNodeBase* NextState = TransitionNode->GetNextState();
UAnimStateAliasNode* NextAliasNode = Cast<UAnimStateAliasNode>(NextState);
if (NextAliasNode)
{
NextState = NextAliasNode->GetAliasedState();
}
if ((PreviousState != nullptr) && (NextState != nullptr))
{
const int32 PreviousStateIndex = Oven.FindState(PreviousState);
const int32 NextStateIndex = Oven.FindState(NextState);
if (NextAliasNode)
{
Oven.AddStateAliasTransitionMapping(NextAliasNode, { TransitionIndex, NextStateIndex });
}
if (TransitionNode->Bidirectional)
{
InCompilationContext.GetMessageLog().Warning(*LOCTEXT("BidirectionalTransWarning", "Bidirectional transitions aren't supported yet @@").ToString(), TransitionNode);
}
BakedTransition.PreviousState = PreviousStateIndex;
BakedTransition.NextState = NextStateIndex;
}
if((BakedTransition.PreviousState == INDEX_NONE) || (BakedTransition.NextState == INDEX_NONE))
{
InCompilationContext.GetMessageLog().Error(*LOCTEXT("BogusTransition", "@@ is incomplete, without a previous or next state").ToString(), TransitionNode);
}
// Validate the blend profile for this transition - incase the skeleton of the node has
// changed or the blend profile no longer exists.
TransitionNode->ValidateBlendProfile();
FBakedStateExitTransition& Rule = *new (BakedState.Transitions) FBakedStateExitTransition();
UAnimStateNodeBase* TransitionPrevNode = TransitionNode->GetPreviousState();
if (UAnimStateAliasNode* PrevAliasNode = Cast<UAnimStateAliasNode>(TransitionPrevNode))
{
Rule.bDesiredTransitionReturnValue = PrevAliasNode->bGlobalAlias ? true : PrevAliasNode->GetAliasedStates().Contains(StateNode);
Oven.AddStateAliasTransitionMapping(PrevAliasNode, { TransitionIndex, StateIndex });
}
else
{
Rule.bDesiredTransitionReturnValue = (TransitionPrevNode == StateNode);
}
Rule.TransitionIndex = TransitionIndex;
if (UAnimGraphNode_TransitionResult* TransitionResultNode = CastChecked<UAnimationTransitionGraph>(TransitionNode->BoundGraph)->GetResultNode())
{
if (int32* pIndex = AlreadyMergedTransitionList.Find(TransitionResultNode))
{
Rule.CanTakeDelegateIndex = *pIndex;
}
else
{
Rule.CanTakeDelegateIndex = Extension->ExpandGraphAndProcessNodes(TransitionNode->BoundGraph, TransitionResultNode, InCompilationContext, OutCompiledData, TransitionNode);
AlreadyMergedTransitionList.Add(TransitionResultNode, Rule.CanTakeDelegateIndex);
}
if (TransitionNode->bAutomaticRuleBasedOnSequencePlayerInState)
{
if (UAnimationTransitionGraph* TransGraph = Cast<UAnimationTransitionGraph>(TransitionNode->BoundGraph))
{
if (UAnimGraphNode_TransitionResult* ResultNode = TransGraph->GetResultNode())
{
if (UEdGraphPin* CanExecPin = ResultNode->FindPin(TEXT("bCanEnterTransition")))
{
if (CanExecPin->LinkedTo.Num() > 0)
{
InCompilationContext.GetMessageLog().Note(*LOCTEXT("TransitionWithAutomaticRuleBased", "@@ has an automatic Rule Based Transition that will override graph exit rule.").ToString(), TransitionNode);
}
}
}
}
}
}
else
{
Rule.CanTakeDelegateIndex = INDEX_NONE;
InCompilationContext.GetMessageLog().Error(*LOCTEXT("TransitionWithNoResult", "@@ has no result node").ToString(), TransitionNode);
}
// Handle automatic time remaining rules
Rule.bAutomaticRemainingTimeRule = TransitionNode->bAutomaticRuleBasedOnSequencePlayerInState;
Rule.AutomaticRuleTriggerTime = TransitionNode->AutomaticRuleTriggerTime;
Rule.SyncGroupNameToRequireValidMarkersRule = TransitionNode->SyncGroupNameToRequireValidMarkersRule;
// Handle custom transition graphs
Rule.CustomResultNodeIndex = INDEX_NONE;
if (UAnimationCustomTransitionGraph* CustomTransitionGraph = Cast<UAnimationCustomTransitionGraph>(TransitionNode->CustomTransitionGraph))
{
TArray<UEdGraphNode*> ClonedNodes;
if (CustomTransitionGraph->GetResultNode())
{
Rule.CustomResultNodeIndex = Extension->ExpandGraphAndProcessNodes(TransitionNode->CustomTransitionGraph, CustomTransitionGraph->GetResultNode(), InCompilationContext, OutCompiledData, nullptr, &ClonedNodes);
}
// Find all the pose evaluators used in this transition, save handles to them because we need to populate some pose data before executing
TArray<UAnimGraphNode_TransitionPoseEvaluator*> TransitionPoseList;
for (auto ClonedNodesIt = ClonedNodes.CreateIterator(); ClonedNodesIt; ++ClonedNodesIt)
{
UEdGraphNode* Node = *ClonedNodesIt;
if (UAnimGraphNode_TransitionPoseEvaluator* TypedNode = Cast<UAnimGraphNode_TransitionPoseEvaluator>(Node))
{
TransitionPoseList.Add(TypedNode);
}
}
Rule.PoseEvaluatorLinks.Empty(TransitionPoseList.Num());
for (auto TransitionPoseListIt = TransitionPoseList.CreateIterator(); TransitionPoseListIt; ++TransitionPoseListIt)
{
UAnimGraphNode_TransitionPoseEvaluator* TransitionPoseNode = *TransitionPoseListIt;
Rule.PoseEvaluatorLinks.Add( InCompilationContext.GetAllocationIndexOfNode(TransitionPoseNode) );
}
}
}
}
Oven.Validate();
}
void UAnimGraphNode_StateMachineBase::GetOutputLinkAttributes(FNodeAttributeArray& OutAttributes) const
{
for (const UEdGraphNode* Node : EditorStateMachineGraph->Nodes)
{
if (const UAnimStateTransitionNode* TransitionNode = Cast<UAnimStateTransitionNode>(Node))
{
if(TransitionNode->LogicType == ETransitionLogicType::TLT_Inertialization)
{
OutAttributes.Add(UE::Anim::IInertializationRequester::Attribute);
break;
}
}
}
}
void UAnimGraphNode_StateMachineBase::GetRequiredExtensions(TArray<TSubclassOf<UAnimBlueprintExtension>>& OutExtensions) const
{
OutExtensions.Add(UAnimBlueprintExtension_StateMachine::StaticClass());
}
#undef LOCTEXT_NAMESPACE