Files
UnrealEngineUWP/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_StateMachineBase.cpp
john vanderburg 121a24f7e1 Improve the Blend Profiles by allowing time based profiles.
Users can now select the blend profile mode: Blend Weight Factor based or Time Factor based.
The Blend Weight Factor method is the legacy method, which multiplies the transition blend weight with the per bone factor.
The new Time Factor method allows you to specify a time factor. A value of 0.5 would mean the bone would take half the time of the transition to reach its target state, while a value of 0.1 would mean it takes a tenth of the time, etc. A value of 0 would make the bone instantly transition into the target state.

#jira UE-100994
#review-14530265 @Aaron.Cox, @Thomas.Sarkanen

[CL 14539393 by john vanderburg in ue5-main branch]
2020-10-21 16:54:58 -04:00

599 lines
23 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_LinkedAnimLayer.h"
#include "AnimGraphNode_TransitionPoseEvaluator.h"
#include "AnimGraphNode_CustomTransitionResult.h"
#include "AnimBlueprintCompilerHandler_StateMachine.h"
#include "IAnimBlueprintGeneratedClassCompiledData.h"
#include "IAnimBlueprintCompilationContext.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());
}
}
}
};
/////////////////////////////////////////////////////
// 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", "Add New 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());
}
FString UAnimGraphNode_StateMachineBase::GetNodeCategory() const
{
return TEXT("State Machines");
}
void UAnimGraphNode_StateMachineBase::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();
// 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);
}
void UAnimGraphNode_StateMachineBase::OnProcessDuringCompilation(IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData)
{
struct FMachineCreator
{
public:
int32 MachineIndex;
TMap<UAnimStateNodeBase*, int32> StateIndexTable;
TMap<UAnimStateTransitionNode*, 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 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);
if (UAnimStateNode* SourceStateNode = Cast<UAnimStateNode>(SourceNode))
{
CompiledData.GetAnimBlueprintDebugData().StateGraphToNodeMap.Add(SourceStateNode->BoundGraph, SourceStateNode);
}
return StateIndex;
}
}
int32 FindOrAddTransition(UAnimStateTransitionNode* TransitionNode)
{
if (int32* pResult = TransitionIndexTable.Find(TransitionNode))
{
return *pResult;
}
else
{
FBakedAnimationStateMachine& BakedMachine = GetMachine();
const int32 TransitionIndex = BakedMachine.Transitions.Num();
TransitionIndexTable.Add(TransitionNode, TransitionIndex);
new (BakedMachine.Transitions) FAnimationTransitionBetweenStates();
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)
{
UEdGraphNode* StateNode = GetMachineSpecificDebugData().FindNodeFromStateIndex(BakedMachine.InitialState);
CompilationContext.GetMessageLog().Error(*LOCTEXT("BadStateEntryNode", "A conduit (@@) cannot be used as the entry node for a state machine").ToString(), StateNode);
}
}
}
};
FAnimBlueprintCompilerHandler_StateMachine* CompilerHandler = InCompilationContext.GetHandler<FAnimBlueprintCompilerHandler_StateMachine>("AnimBlueprintCompilerHandler_StateMachine");
check(CompilerHandler);
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 (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 (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 (UAnimStateTransitionNode* TransitionNode = Cast<UAnimStateTransitionNode>(Node))
{
TransitionNode->ValidateNodeDuringCompilation(InCompilationContext.GetMessageLog());
const int32 TransitionIndex = Oven.FindOrAddTransition(TransitionNode);
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.BlendProfileMode = TransitionNode->BlendProfileMode;
BakedTransition.LogicType = TransitionNode->LogicType;
UAnimStateNodeBase* PreviousState = TransitionNode->GetPreviousState();
UAnimStateNodeBase* NextState = TransitionNode->GetNextState();
if ((PreviousState != NULL) && (NextState != NULL))
{
const int32 PreviousStateIndex = Oven.FindOrAddState(PreviousState);
const int32 NextStateIndex = Oven.FindOrAddState(NextState);
if (TransitionNode->Bidirectional)
{
InCompilationContext.GetMessageLog().Warning(*LOCTEXT("BidirectionalTransWarning", "Bidirectional transitions aren't supported yet @@").ToString(), TransitionNode);
}
BakedTransition.PreviousState = PreviousStateIndex;
BakedTransition.NextState = NextStateIndex;
}
else
{
InCompilationContext.GetMessageLog().Warning(*LOCTEXT("BogusTransition", "@@ is incomplete, without a previous and next state").ToString(), TransitionNode);
BakedTransition.PreviousState = INDEX_NONE;
BakedTransition.NextState = INDEX_NONE;
}
}
else if (UAnimStateNode* StateNode = Cast<UAnimStateNode>(Node))
{
StateNode->ValidateNodeDuringCompilation(InCompilationContext.GetMessageLog());
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);
BakedState.StateRootNodeIndex = CompilerHandler->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))
{
ConduitNode->ValidateNodeDuringCompilation(InCompilationContext.GetMessageLog());
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 = CompilerHandler->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]));
}
}
// 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;
GraphsToCheck.Add(StateNode->GetBoundGraph());
StateNode->GetBoundGraph()->GetAllChildrenGraphs(GraphsToCheck);
TArray<UAnimGraphNode_LinkedAnimLayer*> LinkedAnimLayerNodes;
TArray<UAnimGraphNode_AssetPlayerBase*> AssetPlayerNodes;
for (UEdGraph* ChildGraph : GraphsToCheck)
{
ChildGraph->GetNodesOfClass(AssetPlayerNodes);
ChildGraph->GetNodesOfClass(LinkedAnimLayerNodes);
}
for (UAnimGraphNode_AssetPlayerBase* Node : AssetPlayerNodes)
{
if (int32* IndexPtr = OutCompiledData.GetAnimBlueprintDebugData().NodeGuidToIndexMap.Find(Node->NodeGuid))
{
BakedState.PlayerNodeIndices.Add(*IndexPtr);
}
}
for (UAnimGraphNode_LinkedAnimLayer* Node : LinkedAnimLayerNodes)
{
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;
StateNode->GetTransitionList(/*out*/ TransitionList, /*bWantSortedList=*/ true);
for (auto TransitionIt = TransitionList.CreateIterator(); TransitionIt; ++TransitionIt)
{
UAnimStateTransitionNode* TransitionNode = *TransitionIt;
const int32 TransitionIndex = Oven.FindOrAddTransition(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();
Rule.bDesiredTransitionReturnValue = (TransitionNode->GetPreviousState() == 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 = CompilerHandler->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 = CompilerHandler->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();
}
#undef LOCTEXT_NAMESPACE