Files
UnrealEngineUWP/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_StateMachineBase.cpp
jaime cifuentes 101097fcd1 Animation State Machine UX update (renamed "Add New State Machine" as "State Machine" and removed trailing "..." from all the context options, to be coherent with other UX options
#Jira UE-156485
#rb lucas.dower
#preflight 630e2c62a416f6df25d92124

[CL 21702009 by jaime cifuentes in ue5-main branch]
2022-08-30 11:32:46 -04:00

748 lines
28 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_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())
{
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;
for (UEdGraph* ChildGraph : GraphsToCheck)
{
ChildGraph->GetNodesOfClass(AssetPlayerNodes);
ChildGraph->GetNodesOfClass(LinkedAnimGraphNodes);
}
for (UAnimGraphNode_AssetPlayerBase* Node : AssetPlayerNodes)
{
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;
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->BlendProfile;
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);
}
}
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.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