You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Added a new animation graph node that hosts its own UBlendSpaceBase. Modified UBlendSpaceBase to allow for pose links to be evaluated as the sample points. The new blend space graphs can be spawned from existing UBlendSpace and UBlendSpace1D assets, or they can be created from scratch, or they can be converted from existing blendspace player nodes via the context menu. Fixed anim node conversion functions so that their transactions work correctly. Updated FBlueprintEditorUtils::IsGraphNameUnique to allow it to work with any object as the outer, not just UBlueprint. UBlueprint still has a special case for functions and events. This is to support GenerateUniqueGraphName within a scope (e.g. an outer graph). Formalized the concept of 'node sub-graphs' (as well as the composite node pattern a little). Previously a number of known node types that contained sub-graphs (e.g. UK2Node_Composite) had special case logic for dealing with node/graph deletion etc. Now any node can opt into this behaviour via the GetSubGraphs() override. Added status bar readouts for the blendspace grid, so we dont have to stuff the prompts into the tooltip any more. Moved anim BP related APIs out of FBlueprintEditor. They are always used via FAnimationBlueprintEditor. Refactored graph title bar widget creation out into a function to allow other document tab factories to create it. Altered breadcrumb trail click callbacks and SMyBlueprint::ExecuteAction to always JumpToHyperLink rather than calling OpenDocument directly. This allows unknown (to FBlueprintEditor) document types that reference objects to be correctly jumped to using the breadcrumb trail. Derived asset editors (i.e. FAnimationBlueprintEditor) can intercept the JumpToHyperlink call to ensure that the correct document is presented (i.e. the correct tab payload is generated). Instead of making yet another bunch of duplicated code for handling the various alpha blend options, refactored this into FAnimGraphNodeAlphaOptions (for editor code) and FAnimNodeAlphaOptions (for runtime code). Added OnCopyTermDefaultsToDefaultObject for per-node copying of default values from editor node to runtime node, rather than another special-case in the compiler. #rb Jurre.deBaare,Phillip.Kavan [CL 15177316 by Thomas Sarkanen in ue5-main branch]
623 lines
24 KiB
C++
623 lines
24 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"
|
|
#include "Animation/AnimNode_Inertialization.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);
|
|
}
|
|
|
|
TArray<UEdGraph*> UAnimGraphNode_StateMachineBase::GetSubGraphs() const
|
|
{
|
|
return TArray<UEdGraph*>( { EditorStateMachineGraph } );
|
|
}
|
|
|
|
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.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;
|
|
TArray<UEdGraph*> SubGraphs = StateNode->GetSubGraphs();
|
|
GraphsToCheck.Append(SubGraphs);
|
|
for(UEdGraph* SubGraph : SubGraphs)
|
|
{
|
|
SubGraph->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();
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|