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

649 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimGraphNode_BlendSpaceGraphBase.h"
#include "GraphEditorActions.h"
#include "Kismet2/CompilerResultsLog.h"
#include "BlueprintNodeSpawner.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "Animation/AimOffsetBlendSpace.h"
#include "Animation/AimOffsetBlendSpace1D.h"
#include "Animation/AnimSync.h"
#include "IAnimBlueprintCopyTermDefaultsContext.h"
#include "IAnimBlueprintGeneratedClassCompiledData.h"
#include "AnimationGraph.h"
#include "AnimationGraphSchema.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "BlendSpaceGraph.h"
#include "BlueprintEditorModule.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "DetailLayoutBuilder.h"
#include "DetailCategoryBuilder.h"
#include "EdGraphUtilities.h"
#include "IAnimBlueprintCompilationContext.h"
#include "AnimationBlendSpaceSampleGraph.h"
#include "AnimBlueprintExtension.h"
#include "AnimGraphNode_BlendSpaceSampleResult.h"
#include "AnimGraphNode_SequencePlayer.h"
#include "Kismet2/Kismet2NameValidators.h"
#include "AnimNodes/AnimNode_BlendSpaceGraphBase.h"
#include "BlueprintEditor.h"
#include "Animation/AnimSequence.h"
#include "AnimBlueprintExtension_BlendSpaceGraph.h"
#include "AnimGraphNode_RandomPlayer.h"
#include "BlueprintNodeTemplateCache.h"
#include "Animation/AnimLayerInterface.h"
#define LOCTEXT_NAMESPACE "UAnimGraphNode_BlendSpaceGraphBase"
UAnimGraphNode_BlendSpaceGraphBase::UAnimGraphNode_BlendSpaceGraphBase()
{
bCanRenameNode = true;
}
FText UAnimGraphNode_BlendSpaceGraphBase::GetMenuCategory() const
{
return LOCTEXT("BlendSpaceCategory_Label", "Animation|Blend Spaces");
}
FLinearColor UAnimGraphNode_BlendSpaceGraphBase::GetNodeTitleColor() const
{
return FLinearColor(0.8f, 0.8f, 0.8f);
}
FSlateIcon UAnimGraphNode_BlendSpaceGraphBase::GetIconAndTint(FLinearColor& OutColor) const
{
return FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.BlendSpace");
}
FText UAnimGraphNode_BlendSpaceGraphBase::GetTooltipText() const
{
bool const bIsTemplateNode = GetGraph() == nullptr || FBlueprintNodeTemplateCache::IsTemplateOuter(GetGraph());
if(bIsTemplateNode)
{
return FText::GetEmpty();
}
else
{
return GetNodeTitle(ENodeTitleType::ListView);
}
}
UAnimGraphNode_Base* UAnimGraphNode_BlendSpaceGraphBase::ExpandGraphAndProcessNodes(UEdGraph* SourceGraph, UAnimGraphNode_Base* SourceRootNode, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData)
{
// Clone the nodes from the source graph
// Note that we outer this graph to the ConsolidatedEventGraph to allow ExpansionStep to
// correctly retrieve the context for any expanded function calls (custom make/break structs etc.)
UEdGraph* ClonedGraph = FEdGraphUtilities::CloneGraph(SourceGraph, InCompilationContext.GetConsolidatedEventGraph(), &InCompilationContext.GetMessageLog(), true);
// Grab all the animation nodes and find the corresponding root node in the cloned set
UAnimGraphNode_Base* TargetRootNode = nullptr;
TArray<UAnimGraphNode_Base*> AnimNodeList;
for (auto NodeIt = ClonedGraph->Nodes.CreateIterator(); NodeIt; ++NodeIt)
{
UEdGraphNode* ClonedNode = *NodeIt;
if (UAnimGraphNode_Base* TestNode = Cast<UAnimGraphNode_Base>(ClonedNode))
{
AnimNodeList.Add(TestNode);
//@TODO: There ought to be a better way to determine this
if (InCompilationContext.GetMessageLog().FindSourceObject(TestNode) == InCompilationContext.GetMessageLog().FindSourceObject(SourceRootNode))
{
TargetRootNode = TestNode;
}
}
}
check(TargetRootNode);
// Run another expansion pass to catch the graph we just added (this is slightly wasteful)
InCompilationContext.ExpansionStep(ClonedGraph, false);
// Validate graph now we have expanded/pruned
InCompilationContext.ValidateGraphIsWellFormed(ClonedGraph);
// Move the cloned nodes into the consolidated event graph
const bool bIsLoading = InCompilationContext.GetBlueprint()->bIsRegeneratingOnLoad || IsAsyncLoading();
const bool bIsCompiling = InCompilationContext.GetBlueprint()->bBeingCompiled;
ClonedGraph->MoveNodesToAnotherGraph(InCompilationContext.GetConsolidatedEventGraph(), bIsLoading, bIsCompiling);
// Process any animation nodes
{
TArray<UAnimGraphNode_Base*> RootSet;
RootSet.Add(TargetRootNode);
InCompilationContext.PruneIsolatedAnimationNodes(RootSet, AnimNodeList);
InCompilationContext.ProcessAnimationNodes(AnimNodeList);
}
// Returns the processed cloned version of SourceRootNode
return TargetRootNode;
}
void UAnimGraphNode_BlendSpaceGraphBase::OnProcessDuringCompilation(IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData)
{
auto ProcessGraph = [this, &OutCompiledData](UEdGraph* Graph, TArray<UAnimGraphNode_AssetPlayerBase*> AssetPlayerNodes, TArray<UAnimGraphNode_RandomPlayer*> AssetRandomPlayerPlayerNodes)
{
FString FunctionGraphName = Graph->GetName();
// Also make sure we do not process any empty stub graphs
if (!FunctionGraphName.Contains(ANIM_FUNC_DECORATOR))
{
if (Graph->Nodes.ContainsByPredicate([this, &OutCompiledData](UEdGraphNode* Node) { return Node && Node->NodeGuid == NodeGuid; }))
{
for (UAnimGraphNode_AssetPlayerBase* Node : AssetPlayerNodes)
{
if (int32* IndexPtr = OutCompiledData.GetAnimBlueprintDebugData().NodeGuidToIndexMap.Find(Node->NodeGuid))
{
FGraphAssetPlayerInformation& Info = OutCompiledData.GetGraphAssetPlayerInformation().FindOrAdd(FName(*FunctionGraphName));
Info.PlayerNodeIndices.AddUnique(*IndexPtr);
}
}
for (UAnimGraphNode_RandomPlayer* Node : AssetRandomPlayerPlayerNodes)
{
if (int32* IndexPtr = OutCompiledData.GetAnimBlueprintDebugData().NodeGuidToIndexMap.Find(Node->NodeGuid))
{
FGraphAssetPlayerInformation& Info = OutCompiledData.GetGraphAssetPlayerInformation().FindOrAdd(FName(*FunctionGraphName));
Info.PlayerNodeIndices.AddUnique(*IndexPtr);
}
}
}
}
};
FStructProperty* NodeProperty = GetFNodeProperty();
FArrayProperty* PoseLinksProperty = CastFieldChecked<FArrayProperty>(NodeProperty->Struct->FindPropertyByName(GET_MEMBER_NAME_CHECKED(FAnimNode_BlendSpaceGraphBase, SamplePoseLinks)));
// Resize pose links to match graphs
FAnimNode_BlendSpaceGraphBase* AnimNode = NodeProperty->ContainerPtrToValuePtr<FAnimNode_BlendSpaceGraphBase>(this);
AnimNode->SamplePoseLinks.SetNum(Graphs.Num());
TArray<UAnimGraphNode_AssetPlayerBase*> AssetPlayerNodes;
TArray<UAnimGraphNode_RandomPlayer*> AssetRandomPlayerPlayerNodes;
for(int32 PoseIndex = 0; PoseIndex < Graphs.Num(); ++PoseIndex)
{
UAnimationBlendSpaceSampleGraph* SampleGraph = CastChecked<UAnimationBlendSpaceSampleGraph>(Graphs[PoseIndex]);
UAnimGraphNode_Base* RootNode = ExpandGraphAndProcessNodes(SampleGraph, SampleGraph->ResultNode, InCompilationContext, OutCompiledData);
InCompilationContext.AddPoseLinkMappingRecord(FPoseLinkMappingRecord::MakeFromArrayEntry(this, RootNode, PoseLinksProperty, PoseIndex));
SampleGraph->GetNodesOfClass(AssetPlayerNodes);
SampleGraph->GetNodesOfClass(AssetRandomPlayerPlayerNodes);
}
// Append player nodes to the owning graph's list
UBlueprint* Blueprint = GetBlueprint();
for (UEdGraph* FunctionGraph : Blueprint->FunctionGraphs)
{
ProcessGraph(FunctionGraph, AssetPlayerNodes, AssetRandomPlayerPlayerNodes);
}
// Now do the same for implemented layer interfaces
for (FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces)
{
// Only process Anim Layer interfaces
if (InterfaceDesc.Interface->IsChildOf<UAnimLayerInterface>())
{
for (UEdGraph* Graph : InterfaceDesc.Graphs)
{
ProcessGraph(Graph, AssetPlayerNodes, AssetRandomPlayerPlayerNodes);
}
}
}
}
void UAnimGraphNode_BlendSpaceGraphBase::OnCopyTermDefaultsToDefaultObject(IAnimBlueprintCopyTermDefaultsContext& InCompilationContext, IAnimBlueprintNodeCopyTermDefaultsContext& InPerNodeContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData)
{
FAnimNode_BlendSpaceGraphBase* DestinationNode = reinterpret_cast<FAnimNode_BlendSpaceGraphBase*>(InPerNodeContext.GetDestinationPtr());
UAnimBlueprintExtension_BlendSpaceGraph* Extension = UAnimBlueprintExtension::GetExtension<UAnimBlueprintExtension_BlendSpaceGraph>(GetAnimBlueprint());
DestinationNode->BlendSpace = Extension->AddBlendSpace(BlendSpace);
}
void UAnimGraphNode_BlendSpaceGraphBase::SetupFromAsset(const FAssetData& InAssetData, bool bInIsTemplateNode)
{
if(InAssetData.IsValid())
{
InAssetData.GetTagValue("Skeleton", SkeletonName);
if(SkeletonName == TEXT("None"))
{
SkeletonName.Empty();
}
if(!bInIsTemplateNode)
{
UBlendSpace* Asset = CastChecked<UBlendSpace>(InAssetData.GetAsset());
UAnimBlueprint* AnimBlueprint = GetAnimBlueprint();
const UAnimationGraphSchema* AnimationGraphSchema = GetDefault<UAnimationGraphSchema>();
BlendSpaceGraph = CastChecked<UBlendSpaceGraph>(FBlueprintEditorUtils::CreateNewGraph(this, Asset->GetFName(), UBlendSpaceGraph::StaticClass(), UEdGraphSchema::StaticClass()));
BlendSpaceGraph->BlendSpace = BlendSpace = DuplicateObject(Asset, BlendSpaceGraph);
BlendSpaceGraph->BlendSpace->ClearFlags(RF_Public | RF_Standalone | RF_Transient);
BlendSpaceGraph->BlendSpace->SetFlags(RF_Transactional);
BlendSpaceGraph->BlendSpace->SetSkeleton(nullptr);
BlendSpaceGraph->BlendSpace->EmptyMetaData();
BlendSpaceGraph->BlendSpace->RemoveUserDataOfClass(UAssetUserData::StaticClass());
BlendSpaceGraph->bAllowDeletion = false;
BlendSpaceGraph->bAllowRenaming = true;
// Add the new graph as a child of our parent graph
UEdGraph* ParentGraph = GetGraph();
if(ParentGraph->SubGraphs.Find(BlendSpaceGraph) == INDEX_NONE)
{
ParentGraph->Modify();
ParentGraph->SubGraphs.Add(BlendSpaceGraph);
}
for(int32 SampleIndex = 0; SampleIndex < BlendSpace->SampleData.Num(); ++SampleIndex)
{
FBlendSample& Sample = BlendSpace->SampleData[SampleIndex];
FName SampleName = Sample.Animation != nullptr ? Sample.Animation->GetFName() : FName("Sample", SampleIndex);
UAnimationBlendSpaceSampleGraph* SampleGraph = AddGraph(SampleName, Sample.Animation);
// Clear the animation now we have created the point
Sample.Animation = nullptr;
}
}
}
}
void UAnimGraphNode_BlendSpaceGraphBase::SetupFromClass(TSubclassOf<UBlendSpace> InBlendSpaceClass, bool bInIsTemplateNode)
{
if(bInIsTemplateNode)
{
BlendSpaceClass = InBlendSpaceClass;
}
else
{
BlendSpaceGraph = CastChecked<UBlendSpaceGraph>(FBlueprintEditorUtils::CreateNewGraph(this, InBlendSpaceClass.Get()->GetFName(), UBlendSpaceGraph::StaticClass(), UEdGraphSchema::StaticClass()));
BlendSpaceGraph->BlendSpace = BlendSpace = NewObject<UBlendSpace>(BlendSpaceGraph, InBlendSpaceClass.Get());
BlendSpaceGraph->BlendSpace->ClearFlags(RF_Public | RF_Standalone | RF_Transient);
BlendSpaceGraph->BlendSpace->SetFlags(RF_Transactional);
BlendSpaceGraph->bAllowDeletion = false;
BlendSpaceGraph->bAllowRenaming = true;
// Add the new graph as a child of our parent graph
UEdGraph* ParentGraph = GetGraph();
if(ParentGraph->SubGraphs.Find(BlendSpaceGraph) == INDEX_NONE)
{
ParentGraph->Modify();
ParentGraph->SubGraphs.Add(BlendSpaceGraph);
}
}
}
UObject* UAnimGraphNode_BlendSpaceGraphBase::GetJumpTargetForDoubleClick() const
{
return BlendSpaceGraph;
}
void UAnimGraphNode_BlendSpaceGraphBase::JumpToDefinition() const
{
TSharedPtr<IBlueprintEditor> BlueprintEditor = FKismetEditorUtilities::GetIBlueprintEditorForObject(BlendSpace, true);
if(BlueprintEditor.IsValid())
{
BlueprintEditor->JumpToHyperlink(BlendSpaceGraph, false);
}
}
void UAnimGraphNode_BlendSpaceGraphBase::CustomizeDetails(IDetailLayoutBuilder& InDetailBuilder)
{
if (BlendSpace)
{
IDetailCategoryBuilder& BlendSpaceCategory = InDetailBuilder.EditCategory("Blend Space");
IDetailPropertyRow* BlendSpaceRow = BlendSpaceCategory.AddExternalObjects( { BlendSpace } );
BlendSpaceRow->ShouldAutoExpand(true);
TSharedRef<IPropertyHandle> XHandle = InDetailBuilder.GetProperty(TEXT("Node.X"), GetClass());
XHandle->SetPropertyDisplayName(FText::FromString(BlendSpace->GetBlendParameter(0).DisplayName));
TSharedRef<IPropertyHandle> YHandle = InDetailBuilder.GetProperty(TEXT("Node.Y"), GetClass());
if (BlendSpace->IsA<UBlendSpace1D>())
{
InDetailBuilder.HideProperty(YHandle);
}
else
{
YHandle->SetPropertyDisplayName(FText::FromString(BlendSpace->GetBlendParameter(1).DisplayName));
}
}
}
TArray<UEdGraph*> UAnimGraphNode_BlendSpaceGraphBase::GetSubGraphs() const
{
return TArray<UEdGraph*>( { BlendSpaceGraph } );
}
void UAnimGraphNode_BlendSpaceGraphBase::DestroyNode()
{
UBlueprint* Blueprint = GetBlueprint();
for (UEdGraph* SampleGraph : Graphs)
{
SampleGraph->Modify();
FBlueprintEditorUtils::RemoveGraph(Blueprint, SampleGraph);
}
UEdGraph* GraphToRemove = BlendSpaceGraph;
BlendSpaceGraph = nullptr;
Graphs.Empty();
Super::DestroyNode();
if (GraphToRemove)
{
GraphToRemove->Modify();
FBlueprintEditorUtils::RemoveGraph(Blueprint, GraphToRemove, EGraphRemoveFlags::Recompile);
}
}
void UAnimGraphNode_BlendSpaceGraphBase::PreloadRequiredAssets()
{
PreloadObject(BlendSpace);
Super::PreloadRequiredAssets();
}
TSharedPtr<INameValidatorInterface> UAnimGraphNode_BlendSpaceGraphBase::MakeNameValidator() const
{
class FNameValidator : public FStringSetNameValidator
{
public:
FNameValidator(const UAnimGraphNode_BlendSpaceGraphBase* InBlendSpaceGraphNode)
: FStringSetNameValidator(FString())
{
TArray<UAnimGraphNode_BlendSpaceGraphBase*> Nodes;
UAnimationGraph* AnimGraph = CastChecked<UAnimationGraph>(InBlendSpaceGraphNode->GetOuter());
AnimGraph->GetNodesOfClass<UAnimGraphNode_BlendSpaceGraphBase>(Nodes);
for (UAnimGraphNode_BlendSpaceGraphBase* Node : Nodes)
{
if (Node != InBlendSpaceGraphNode)
{
Names.Add(Node->GetBlendSpaceGraphName());
}
}
}
};
return MakeShared<FNameValidator>(this);
}
void UAnimGraphNode_BlendSpaceGraphBase::OnRenameNode(const FString& NewName)
{
FBlueprintEditorUtils::RenameGraph(BlendSpaceGraph, NewName);
}
FString UAnimGraphNode_BlendSpaceGraphBase::GetBlendSpaceGraphName() const
{
return (BlendSpaceGraph != nullptr) ? *(BlendSpaceGraph->GetName()) : TEXT("(null)");
}
FString UAnimGraphNode_BlendSpaceGraphBase::GetBlendSpaceName() const
{
return (BlendSpace != nullptr) ? *(BlendSpace->GetName()) : TEXT("(null)");
}
void UAnimGraphNode_BlendSpaceGraphBase::PostPasteNode()
{
Super::PostPasteNode();
if(BlendSpaceGraph != nullptr)
{
// Add the new graph as a child of our parent graph
UEdGraph* ParentGraph = GetGraph();
if(ParentGraph->SubGraphs.Find(BlendSpaceGraph) == INDEX_NONE)
{
ParentGraph->SubGraphs.Add(BlendSpaceGraph);
}
// Refresh sample graphs
for (UEdGraph* SampleGraph : Graphs)
{
for(UEdGraphNode* GraphNode : SampleGraph->Nodes)
{
GraphNode->CreateNewGuid();
GraphNode->PostPasteNode();
GraphNode->ReconstructNode();
}
}
// Find an interesting name
TSharedPtr<INameValidatorInterface> NameValidator = FNameValidatorFactory::MakeValidator(this);
FBlueprintEditorUtils::RenameGraphWithSuggestion(BlendSpaceGraph, NameValidator, BlendSpaceGraph->GetName());
// Restore transactional flag that is lost during copy/paste process
BlendSpaceGraph->SetFlags(RF_Transactional);
}
}
void UAnimGraphNode_BlendSpaceGraphBase::PostPlacedNewNode()
{
Super::PostPlacedNewNode();
// Create a new graph & blendspace if we havent been set up already
if(BlendSpaceGraph == nullptr)
{
check(BlendSpace == nullptr);
BlendSpaceGraph = CastChecked<UBlendSpaceGraph>(FBlueprintEditorUtils::CreateNewGraph(this, NAME_None, UBlendSpaceGraph::StaticClass(), UEdGraphSchema::StaticClass()));
BlendSpaceGraph->BlendSpace = BlendSpace = NewObject<UBlendSpace>();
// Find an interesting name
TSharedPtr<INameValidatorInterface> NameValidator = FNameValidatorFactory::MakeValidator(this);
FBlueprintEditorUtils::RenameGraphWithSuggestion(BlendSpaceGraph, NameValidator, TEXT("New Blend Space"));
// Add the new graph as a child of our parent graph
UEdGraph* ParentGraph = GetGraph();
if(ParentGraph->SubGraphs.Find(BlendSpaceGraph) == INDEX_NONE)
{
ParentGraph->Modify();
ParentGraph->SubGraphs.Add(BlendSpaceGraph);
}
}
}
void UAnimGraphNode_BlendSpaceGraphBase::CustomizePinData(UEdGraphPin* Pin, FName SourcePropertyName, int32 ArrayIndex) const
{
if (BlendSpace != nullptr)
{
if (SourcePropertyName == TEXT("X"))
{
Pin->PinFriendlyName = FText::FromString(BlendSpace->GetBlendParameter(0).DisplayName);
}
else if (SourcePropertyName == TEXT("Y"))
{
Pin->PinFriendlyName = FText::FromString(BlendSpace->GetBlendParameter(1).DisplayName);
Pin->bHidden = BlendSpace->IsA<UBlendSpace1D>() ? 1 : 0;
}
else if (SourcePropertyName == TEXT("Z"))
{
Pin->PinFriendlyName = FText::FromString(BlendSpace->GetBlendParameter(2).DisplayName);
}
}
}
void UAnimGraphNode_BlendSpaceGraphBase::PostProcessPinName(const UEdGraphPin* Pin, FString& DisplayName) const
{
if(Pin->Direction == EGPD_Input)
{
if(BlendSpace != nullptr)
{
if(Pin->PinName == TEXT("X"))
{
DisplayName = BlendSpace->GetBlendParameter(0).DisplayName;
}
else if(Pin->PinName == TEXT("Y"))
{
DisplayName = BlendSpace->GetBlendParameter(1).DisplayName;
}
else if(Pin->PinName == TEXT("Z"))
{
DisplayName = BlendSpace->GetBlendParameter(2).DisplayName;
}
}
}
Super::PostProcessPinName(Pin, DisplayName);
}
int32 UAnimGraphNode_BlendSpaceGraphBase::GetSampleIndex(const UEdGraph* Graph) const
{
for (int32 Index = 0 ; Index != Graphs.Num() ; ++Index)
{
if (Graphs[Index] == Graph)
{
return Index;
}
}
return INDEX_NONE;
}
UAnimationBlendSpaceSampleGraph* UAnimGraphNode_BlendSpaceGraphBase::AddGraph(FName InSampleName, UAnimSequence* InSequence)
{
UAnimationBlendSpaceSampleGraph* NewGraph = AddGraphInternal(InSampleName, InSequence);
Graphs.Add(NewGraph);
return NewGraph;
}
UAnimationBlendSpaceSampleGraph* UAnimGraphNode_BlendSpaceGraphBase::AddGraphInternal(FName InSampleName, UAnimSequence* InSequence)
{
const UAnimationGraphSchema* AnimationGraphSchema = GetDefault<UAnimationGraphSchema>();
Modify();
const FName NewGraphName = FBlueprintEditorUtils::GenerateUniqueGraphName(BlendSpaceGraph, InSampleName.ToString());
UAnimationBlendSpaceSampleGraph* SampleGraph = CastChecked<UAnimationBlendSpaceSampleGraph>(FBlueprintEditorUtils::CreateNewGraph(BlendSpaceGraph, NewGraphName, UAnimationBlendSpaceSampleGraph::StaticClass(), UAnimationGraphSchema::StaticClass()));
FGraphNodeCreator<UAnimGraphNode_BlendSpaceSampleResult> ResultNodeCreator(*SampleGraph);
UAnimGraphNode_BlendSpaceSampleResult* ResultSinkNode = ResultNodeCreator.CreateNode();
ResultNodeCreator.Finalize();
AnimationGraphSchema->SetNodeMetaData(ResultSinkNode, FNodeMetadata::DefaultGraphNode);
SampleGraph->ResultNode = ResultSinkNode;
SampleGraph->bAllowDeletion = false;
SampleGraph->bAllowRenaming = true;
if(InSequence != nullptr)
{
// Attach an asset player if a valid animation is supplied
FGraphNodeCreator<UAnimGraphNode_SequencePlayer> SequencePlayerNodeCreator(*SampleGraph);
UAnimGraphNode_SequencePlayer* SequencePlayer = SequencePlayerNodeCreator.CreateNode();
SequencePlayer->SetAnimationAsset(InSequence);
SequencePlayer->Node.SetGroupMethod(EAnimSyncMethod::Graph);
SequencePlayerNodeCreator.Finalize();
// Offset node in X
SequencePlayer->NodePosX = SampleGraph->ResultNode->NodePosX - 400;
UEdGraphPin* OutputPin = SequencePlayer->FindPinChecked(TEXT("Pose"), EGPD_Output);
UEdGraphPin* InputPin = SampleGraph->ResultNode->FindPinChecked(TEXT("Result"), EGPD_Input);
OutputPin->MakeLinkTo(InputPin);
}
BlendSpaceGraph->Modify();
BlendSpaceGraph->SubGraphs.Add(SampleGraph);
return SampleGraph;
}
void UAnimGraphNode_BlendSpaceGraphBase::RemoveGraph(int32 InSampleIndex)
{
Modify();
check(Graphs.IsValidIndex(InSampleIndex));
UEdGraph* GraphToRemove = Graphs[InSampleIndex];
TSharedPtr<FBlueprintEditor> BlueprintEditor = StaticCastSharedPtr<FBlueprintEditor>(FKismetEditorUtilities::GetIBlueprintEditorForObject(GraphToRemove, false));
check(BlueprintEditor.IsValid());
// 'Asset' blendspace sample deletion uses a RemoveAtSwap, so we must mirror it here to maintain the same indices
Graphs.RemoveAtSwap(InSampleIndex);
FBlueprintEditorUtils::RemoveGraph(GetAnimBlueprint(), GraphToRemove);
BlueprintEditor->CloseDocumentTab(GraphToRemove);
}
void UAnimGraphNode_BlendSpaceGraphBase::ReplaceGraph(int32 InSampleIndex, UAnimSequence* InSequence)
{
Modify();
check(Graphs.IsValidIndex(InSampleIndex));
UEdGraph* GraphToRemove = Graphs[InSampleIndex];
FName GraphName = GraphToRemove->GetFName();
TSharedPtr<FBlueprintEditor> BlueprintEditor = StaticCastSharedPtr<FBlueprintEditor>(FKismetEditorUtilities::GetIBlueprintEditorForObject(GraphToRemove, false));
check(BlueprintEditor.IsValid());
FBlueprintEditorUtils::RemoveGraph(GetAnimBlueprint(), GraphToRemove);
BlueprintEditor->CloseDocumentTab(GraphToRemove);
Graphs[InSampleIndex] = AddGraphInternal(GraphName, InSequence);
}
FName UAnimGraphNode_BlendSpaceGraphBase::GetSyncGroupName() const
{
FStructProperty* NodeProperty = GetFNodeProperty();
const FAnimNode_BlendSpaceGraphBase* AnimNode = NodeProperty->ContainerPtrToValuePtr<FAnimNode_BlendSpaceGraphBase>(this);
return AnimNode->GroupName;
}
void UAnimGraphNode_BlendSpaceGraphBase::SetSyncGroupName(FName InName)
{
FStructProperty* NodeProperty = GetFNodeProperty();
FAnimNode_BlendSpaceGraphBase* AnimNode = NodeProperty->ContainerPtrToValuePtr<FAnimNode_BlendSpaceGraphBase>(this);
AnimNode->GroupName = InName;
}
void UAnimGraphNode_BlendSpaceGraphBase::GetInputLinkAttributes(FNodeAttributeArray& OutAttributes) const
{
if(GetSyncGroupName() != NAME_None)
{
OutAttributes.Add(UE::Anim::FAnimSync::Attribute);
}
}
void UAnimGraphNode_BlendSpaceGraphBase::GetRequiredExtensions(TArray<TSubclassOf<UAnimBlueprintExtension>>& OutExtensions) const
{
OutExtensions.Add(UAnimBlueprintExtension_BlendSpaceGraph::StaticClass());
}
bool UAnimGraphNode_BlendSpaceGraphBase::IsActionFilteredOut(class FBlueprintActionFilter const& Filter)
{
bool bIsFilteredOut = false;
FBlueprintActionContext const& FilterContext = Filter.Context;
for (UBlueprint* Blueprint : FilterContext.Blueprints)
{
if (UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(Blueprint))
{
if(!SkeletonName.IsEmpty())
{
if(AnimBlueprint->TargetSkeleton == nullptr || !AnimBlueprint->TargetSkeleton->IsCompatibleForEditor(SkeletonName))
{
bIsFilteredOut = true;
break;
}
}
}
else
{
// Not an animation Blueprint, cannot use
bIsFilteredOut = true;
break;
}
}
return bIsFilteredOut;
}
#undef LOCTEXT_NAMESPACE