// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimGraphNode_AssetPlayerBase.h" #include "EdGraphSchema_K2.h" #include "Animation/AnimComposite.h" #include "BlueprintActionFilter.h" #include "BlueprintNodeSpawner.h" #include "BlueprintAssetNodeSpawner.h" #include "UObject/UObjectIterator.h" #include "Animation/AnimLayerInterface.h" #include "IAnimBlueprintGeneratedClassCompiledData.h" #include "IAnimBlueprintCompilationContext.h" #include "Animation/AnimSync.h" #include "Animation/AnimAttributes.h" #include "AssetRegistry/AssetRegistryModule.h" #include "UObject/UE5MainStreamObjectVersion.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintNodeTemplateCache.h" #include "Animation/AnimNode_AssetPlayerBase.h" #define LOCTEXT_NAMESPACE "UAnimGraphNode_AssetPlayerBase" void UAnimGraphNode_AssetPlayerBase::Serialize(FArchive& Ar) { Super::Serialize(Ar); Ar.UsingCustomVersion(FUE5MainStreamObjectVersion::GUID); if(Ar.IsLoading() && Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) < FUE5MainStreamObjectVersion::AnimSyncGroupsExplicitSyncMethod) { if(SyncGroup_DEPRECATED.GroupName != NAME_None) { SyncGroup_DEPRECATED.Method = EAnimSyncMethod::SyncGroup; } } if(Ar.IsLoading() && Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) < FUE5MainStreamObjectVersion::AnimNodeConstantDataRefactorPhase0) { FStructProperty* NodeProperty = GetFNodeProperty(); if(NodeProperty->Struct->IsChildOf(FAnimNode_AssetPlayerBase::StaticStruct())) { FAnimNode_AssetPlayerBase* Node = NodeProperty->ContainerPtrToValuePtr(this); Node->SetGroupName(SyncGroup_DEPRECATED.GroupName); Node->SetGroupRole(SyncGroup_DEPRECATED.GroupRole); Node->SetGroupMethod(SyncGroup_DEPRECATED.Method); } } } void UAnimGraphNode_AssetPlayerBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); FStructProperty* NodeProperty = GetFNodeProperty(); if(NodeProperty->Struct->IsChildOf(FAnimNode_AssetPlayerBase::StaticStruct())) { if(PropertyChangedEvent.GetPropertyName() == TEXT("Method")) { FAnimNode_AssetPlayerBase* Node = NodeProperty->ContainerPtrToValuePtr(this); if(Node->GetGroupMethod() != EAnimSyncMethod::SyncGroup) { Node->SetGroupName(NAME_None); Node->SetGroupRole(EAnimGroupRole::CanBeLeader); } } } } void UAnimGraphNode_AssetPlayerBase::PinConnectionListChanged(UEdGraphPin* Pin) { Super::PinConnectionListChanged(Pin); if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object) { // recache visualization now an asset pin's connection is changed if (const UEdGraphSchema* Schema = GetSchema()) { Schema->ForceVisualizationCacheClear(); } } } void UAnimGraphNode_AssetPlayerBase::PinDefaultValueChanged(UEdGraphPin* Pin) { Super::PinDefaultValueChanged(Pin); if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object) { // recache visualization now an asset pin's default value has changed if (const UEdGraphSchema* Schema = GetSchema()) { Schema->ForceVisualizationCacheClear(); } } } FText UAnimGraphNode_AssetPlayerBase::GetTooltipText() const { bool const bIsTemplateNode = GetGraph() == nullptr || FBlueprintNodeTemplateCache::IsTemplateOuter(GetGraph()); if(bIsTemplateNode) { return FText::GetEmpty(); } else { // FText::Format() is slow, so we utilize the cached list title return GetNodeTitle(ENodeTitleType::ListView); } } void UAnimGraphNode_AssetPlayerBase::OnProcessDuringCompilation(IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { UBlueprint* Blueprint = GetBlueprint(); // Process Asset Player nodes to, if necessary cache off their node index for retrieval at runtime (used for evaluating Automatic Rule Transitions when using Layer nodes) auto ProcessGraph = [this, &OutCompiledData](UEdGraph* Graph) { FString GraphName = Graph->GetName(); // Also make sure we do not process any empty stub graphs if (!GraphName.Contains(ANIM_FUNC_DECORATOR)) { if (Graph->Nodes.ContainsByPredicate([this, &OutCompiledData](UEdGraphNode* Node) { return Node && Node->NodeGuid == NodeGuid; })) { if (int32* IndexPtr = OutCompiledData.GetAnimBlueprintDebugData().NodeGuidToIndexMap.Find(NodeGuid)) { FGraphAssetPlayerInformation& Info = OutCompiledData.GetGraphAssetPlayerInformation().FindOrAdd(FName(*GraphName)); Info.PlayerNodeIndices.AddUnique(*IndexPtr); } } } }; // Check for any definition of a layer graph for (UEdGraph* Graph : Blueprint->FunctionGraphs) { ProcessGraph(Graph); } // Check for any implemented AnimLayer interface graphs for (FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) { // Only process Anim Layer interfaces if (InterfaceDesc.Interface->IsChildOf()) { for (UEdGraph* Graph : InterfaceDesc.Graphs) { ProcessGraph(Graph); } } } } void UAnimGraphNode_AssetPlayerBase::ValidateAnimNodeDuringCompilation(USkeleton* ForSkeleton, FCompilerResultsLog& MessageLog) { Super::ValidateAnimNodeDuringCompilation(ForSkeleton, MessageLog); FStructProperty* NodeProperty = GetFNodeProperty(); if(NodeProperty->Struct->IsChildOf(FAnimNode_AssetPlayerBase::StaticStruct())) { FAnimNode_AssetPlayerBase* Node = NodeProperty->ContainerPtrToValuePtr(this); const FName GroupName = Node->GetGroupName(); if(Node->GetGroupMethod() == EAnimSyncMethod::SyncGroup && GroupName == NAME_None) { MessageLog.Error(*LOCTEXT("NoSyncGroupSupplied", "Node @@ is set to use sync groups, but no sync group has been supplied").ToString(), this); } else if(Node->GetGroupMethod() != EAnimSyncMethod::SyncGroup && GroupName != NAME_None) { FText const ErrorFormat = LOCTEXT("InvalidSyncGroupSupplied", "Node @@ is set to not use named sync groups, but a sync group {0} is set."); MessageLog.Error( *FText::Format(ErrorFormat, FText::FromName(GroupName)).ToString(), this ); } } } void UAnimGraphNode_AssetPlayerBase::GetOutputLinkAttributes(FNodeAttributeArray& OutAttributes) const { OutAttributes.Add(UE::Anim::FAttributes::Curves); OutAttributes.Add(UE::Anim::FAttributes::Attributes); FStructProperty* NodeProperty = GetFNodeProperty(); if(NodeProperty->Struct->IsChildOf(FAnimNode_AssetPlayerBase::StaticStruct())) { const FAnimNode_AssetPlayerBase* Node = NodeProperty->ContainerPtrToValuePtr(this); if(Node->GetGroupMethod() == EAnimSyncMethod::Graph) { OutAttributes.Add(UE::Anim::FAnimSync::Attribute); } } } UClass* GetNodeClassForAsset(const UClass* AssetClass) { UClass* NodeClass = nullptr; // Iterate over all classes.. for (TObjectIterator ClassIt; ClassIt; ++ClassIt) { UClass *Class = *ClassIt; // Look for AnimGraphNode classes if (Class->IsChildOf(UAnimGraphNode_Base::StaticClass())) { // See if this node is the 'primary handler' for this asset type const UAnimGraphNode_Base* NodeCDO = Class->GetDefaultObject(); if (NodeCDO->SupportsAssetClass(AssetClass) == EAnimAssetHandlerType::PrimaryHandler) { NodeClass = Class; break; } } } return NodeClass; } bool SupportNodeClassForAsset(const UClass* AssetClass, UClass* NodeClass) { // Get node CDO const UAnimGraphNode_Base* NodeCDO = NodeClass->GetDefaultObject(); // See if this node supports this asset type (primary or not) return (NodeCDO->SupportsAssetClass(AssetClass) != EAnimAssetHandlerType::NotSupported); } void UAnimGraphNode_AssetPlayerBase::SetupNewNode(UEdGraphNode* InNewNode, bool bInIsTemplateNode, const FAssetData InAssetData) { UAnimGraphNode_AssetPlayerBase* GraphNode = CastChecked(InNewNode); if(InAssetData.IsValid()) { InAssetData.GetTagValue("Skeleton", GraphNode->UnloadedSkeletonName); if(GraphNode->UnloadedSkeletonName == TEXT("None")) { GraphNode->UnloadedSkeletonName.Empty(); } if(!bInIsTemplateNode) { GraphNode->SetAnimationAsset(CastChecked(InAssetData.GetAsset())); } } } void UAnimGraphNode_AssetPlayerBase::GetMenuActionsHelper( FBlueprintActionDatabaseRegistrar& InActionRegistrar, TSubclassOf InNodeClass, const TArray>& InAssetTypes, const TArray>& InExcludedAssetTypes, const TFunctionRef& InMenuNameFunction, const TFunctionRef& InMenuTooltipFunction, const TFunction& InSetupNewNodeFromAssetFunction, const TFunction)>& InSetupNewNodeFromClassFunction, const TFunction& InMenuCategoryFunction) { auto MakeActionFromAsset = [&InActionRegistrar, &InMenuNameFunction, &InMenuTooltipFunction, InSetupNewNodeFromAssetFunction, InNodeClass, InMenuCategoryFunction](const FAssetData& InAssetData) { auto AssetSetup = [InSetupNewNodeFromAssetFunction, InAssetData, InMenuCategoryFunction](UEdGraphNode* InNewNode, bool bInIsTemplateNode) { InSetupNewNodeFromAssetFunction(InNewNode, bInIsTemplateNode, InAssetData); }; UBlueprintAssetNodeSpawner* NodeSpawner = UBlueprintAssetNodeSpawner::Create(InNodeClass.Get(), InAssetData); NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateLambda(AssetSetup); NodeSpawner->DefaultMenuSignature.MenuName = InMenuNameFunction(InAssetData, InAssetData.GetClass()); NodeSpawner->DefaultMenuSignature.Category = InMenuCategoryFunction != nullptr ? InMenuCategoryFunction(InAssetData) : FText::GetEmpty(); NodeSpawner->DefaultMenuSignature.Tooltip = InMenuTooltipFunction(InAssetData, InAssetData.GetClass()); InActionRegistrar.AddBlueprintAction(InAssetData, NodeSpawner); }; auto MakeActionFromClass = [&InActionRegistrar, &InMenuNameFunction, &InMenuTooltipFunction, InSetupNewNodeFromClassFunction, InNodeClass](const TSubclassOf& InAssetClass) { if(InSetupNewNodeFromClassFunction != nullptr) { auto AssetSetup = [InSetupNewNodeFromClassFunction, InAssetClass](UEdGraphNode* InNewNode, bool bInIsTemplateNode) { InSetupNewNodeFromClassFunction(InNewNode, bInIsTemplateNode, InAssetClass); }; UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(InNodeClass.Get()); NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateLambda(AssetSetup); NodeSpawner->DefaultMenuSignature.MenuName = InMenuNameFunction(FAssetData(), InAssetClass.Get()); NodeSpawner->DefaultMenuSignature.Tooltip = InMenuTooltipFunction(FAssetData(), InAssetClass.Get()); InActionRegistrar.AddBlueprintAction(InAssetClass, NodeSpawner); } }; const UObject* QueryObject = InActionRegistrar.GetActionKeyFilter(); bool bIsObjectOfAssetType = false; for(const TSubclassOf& AssetType : InAssetTypes) { if(QueryObject && QueryObject->IsA(AssetType.Get())) { bIsObjectOfAssetType = true; break; } } if (QueryObject == nullptr || QueryObject == InNodeClass.Get()) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); FARFilter Filter; for(const TSubclassOf& AssetType : InAssetTypes) { Filter.ClassPaths.Add(AssetType.Get()->GetClassPathName()); } for(const TSubclassOf& ExcludedAssetType : InExcludedAssetTypes) { Filter.RecursiveClassPathsExclusionSet.Add(ExcludedAssetType.Get()->GetClassPathName()); } Filter.bRecursiveClasses = true; TArray Assets; AssetRegistryModule.Get().GetAssets(Filter, Assets); for (const FAssetData& AssetData : Assets) { if(AssetData.IsUAsset()) { MakeActionFromAsset(AssetData); } } if(InSetupNewNodeFromClassFunction != nullptr) { // Add 'class' nodes for(const TSubclassOf& AssetType : InAssetTypes) { MakeActionFromClass(AssetType); } } else { // Add 'empty' asset node MakeActionFromAsset(FAssetData()); } } else if (bIsObjectOfAssetType) { MakeActionFromAsset(FAssetData(QueryObject)); } } bool UAnimGraphNode_AssetPlayerBase::IsActionFilteredOut(class FBlueprintActionFilter const& Filter) { bool bIsFilteredOut = false; FBlueprintActionContext const& FilterContext = Filter.Context; for (UBlueprint* Blueprint : FilterContext.Blueprints) { UAnimBlueprint* AnimBlueprint = Cast(Blueprint); if (AnimBlueprint) { UAnimationAsset* Asset = GetAnimationAsset(); if(Asset) { if (AnimBlueprint->TargetSkeleton == nullptr || !AnimBlueprint->TargetSkeleton->IsCompatibleForEditor(Asset->GetSkeleton())) { // Asset does not use a compatible skeleton with the Blueprint, cannot use bIsFilteredOut = true; break; } } else if(!UnloadedSkeletonName.IsEmpty()) { if(AnimBlueprint->TargetSkeleton == nullptr || !AnimBlueprint->TargetSkeleton->IsCompatibleForEditor(UnloadedSkeletonName)) { bIsFilteredOut = true; break; } } } else { // Not an animation Blueprint, cannot use bIsFilteredOut = true; break; } } return bIsFilteredOut; } FText UAnimGraphNode_AssetPlayerBase::GetNodeTitleHelper(ENodeTitleType::Type InTitleType, UEdGraphPin* InAssetPin, const FText& InAssetDesc, const TFunction InPostFixFunctionRef) const { UAnimationAsset* Asset = GetAnimationAsset(); if (Asset == nullptr) { // Check for bindings bool bHasBinding = false; if(InAssetPin != nullptr) { if (HasBinding(InAssetPin->GetFName())) { bHasBinding = true; } } // Also check for links if (bHasBinding || (InAssetPin && InAssetPin->LinkedTo.Num() > 0)) { return InAssetDesc; } // check for a default value on the pin else if (InAssetPin && InAssetPin->DefaultObject != nullptr) { return GetNodeTitleForAsset(InTitleType, CastChecked(InAssetPin->DefaultObject), InAssetDesc, InPostFixFunctionRef); } else { return InAssetDesc; } } else { return GetNodeTitleForAsset(InTitleType, Asset, InAssetDesc, InPostFixFunctionRef); } } FText UAnimGraphNode_AssetPlayerBase::GetNodeTitleForAsset(ENodeTitleType::Type InTitleType, UAnimationAsset* InAsset, const FText& InAssetDesc, const TFunction InPostFixFunctionRef) const { UAnimationAsset* Asset = GetAnimationAsset(); check(Asset); const FText AssetName = FText::FromString(Asset->GetName()); if (InTitleType == ENodeTitleType::ListView || InTitleType == ENodeTitleType::MenuTitle) { FFormatNamedArguments Args; Args.Add(TEXT("AssetName"), AssetName); Args.Add(TEXT("AssetDesc"), InAssetDesc); if(InPostFixFunctionRef != nullptr) { const FText PostFix = InPostFixFunctionRef(InAsset); if(!PostFix.IsEmpty()) { Args.Add(TEXT("PostFix"), PostFix); static const FTextFormat FormatWithPostFix(LOCTEXT("AssetPlayerTitlewithPostFix", "{AssetDesc} {PostFix} '{AssetName}'")); return FText::Format(FormatWithPostFix, Args); } } static const FTextFormat FormatWithoutPostFix(LOCTEXT("AssetPlayerTitle", "{AssetDesc} '{AssetName}'")); return FText::Format(FormatWithoutPostFix, Args); } else { FFormatNamedArguments TitleArgs; TitleArgs.Add(TEXT("AssetName"), AssetName); TitleArgs.Add(TEXT("AssetDesc"), InAssetDesc); FText Title = FText::Format(LOCTEXT("AssetPlayerFullTitle", "{AssetName}\n{AssetDesc}"), TitleArgs); AddSyncGroupToNodeTitle(InTitleType, Title); return Title; } } void UAnimGraphNode_AssetPlayerBase::AddSyncGroupToNodeTitle(ENodeTitleType::Type InTitleType, FText& InOutTitle) const { if (InTitleType == ENodeTitleType::FullTitle) { FStructProperty* NodeProperty = GetFNodeProperty(); if(NodeProperty->Struct->IsChildOf(FAnimNode_AssetPlayerBase::StaticStruct())) { const FAnimNode_AssetPlayerBase* Node = NodeProperty->ContainerPtrToValuePtr(this); FFormatNamedArguments Args; Args.Add(TEXT("Title"), InOutTitle); if(Node->GetGroupMethod() == EAnimSyncMethod::SyncGroup) { Args.Add(TEXT("SyncGroupName"), FText::FromName(Node->GetGroupName())); static const FTextFormat FormatAssetPlayerNodeSyncGroupSubtitle(LOCTEXT("AssetPlayerNodeSyncGroupSubtitle", "{Title}\nSync group {SyncGroupName}")); InOutTitle = FText::Format(FormatAssetPlayerNodeSyncGroupSubtitle, Args); } else if(Node->GetGroupMethod() == EAnimSyncMethod::Graph) { static const FTextFormat FormatAssetPlayerNodeGraphSyncGroupSubtitle(LOCTEXT("AssetPlayerNodeGraphSyncGroupSubtitle", "{Title}\nGraph sync group")); InOutTitle = FText::Format(FormatAssetPlayerNodeGraphSyncGroupSubtitle, Args); UObject* ObjectBeingDebugged = GetAnimBlueprint()->GetObjectBeingDebugged(); UAnimBlueprintGeneratedClass* GeneratedClass = GetAnimBlueprint()->GetAnimBlueprintGeneratedClass(); if (ObjectBeingDebugged && GeneratedClass) { int32 NodeIndex = GeneratedClass->GetNodeIndexFromGuid(NodeGuid); if(NodeIndex != INDEX_NONE) { if(const FName* SyncGroupNamePtr = GeneratedClass->GetAnimBlueprintDebugData().NodeSyncsThisFrame.Find(NodeIndex)) { Args.Add(TEXT("SyncGroupName"), FText::FromName(*SyncGroupNamePtr)); static const FTextFormat FormatAssetPlayerNodeDynamicGraphSyncGroupSubtitle(LOCTEXT("AssetPlayerNodeDynamicGraphSyncGroupSubtitle", "{Title}\nGraph sync group {SyncGroupName}")); InOutTitle = FText::Format(FormatAssetPlayerNodeDynamicGraphSyncGroupSubtitle, Args); } } } } } } } void UAnimGraphNode_AssetPlayerBase::ValidateAnimNodeDuringCompilationHelper(USkeleton* ForSkeleton, FCompilerResultsLog& MessageLog, UAnimationAsset* InAsset, TSubclassOf InAssetType, UEdGraphPin* InExposedPin, FName InPropertyName) { UAnimationAsset* AssetToCheck = InAsset; if(InExposedPin != nullptr && AssetToCheck == nullptr) { AssetToCheck = Cast(InExposedPin->DefaultObject); } if(AssetToCheck == nullptr) { if(!GetAnimBlueprint()->bIsTemplate) { // Check for bindings bool bHasBinding = false; bool bAlwaysDynamic = false; if(InExposedPin != nullptr) { if(HasBinding(InExposedPin->GetFName())) { bHasBinding = true; } } if(AlwaysDynamicProperties.Contains(InPropertyName)) { bAlwaysDynamic = true; } // we may have a connected node or binding if(!bAlwaysDynamic) { if (InExposedPin == nullptr || (InExposedPin->LinkedTo.Num() == 0 && !bHasBinding)) { MessageLog.Error(*FText::Format(LOCTEXT("MissingAssetFormat", "@@ references an unknown {0}"), InAssetType->GetDisplayNameText()).ToString(), this); } } } } else { USkeleton* AssetSkeleton = AssetToCheck->GetSkeleton(); // if asset doesn't have a skeleton, it might be due to the asset no being not loaded yet if(AssetSkeleton == nullptr) { MessageLog.Error(*FText::Format(LOCTEXT("MissingSkeletonFormat", "@@ references {0} that uses a missing skeleton @@"), InAssetType->GetDisplayNameText()).ToString(), this, AssetSkeleton); } } } void UAnimGraphNode_AssetPlayerBase::PreloadRequiredAssetsHelper(UAnimationAsset* InAsset, UEdGraphPin* InExposedPin) { UAnimationAsset* AssetToLoad = InAsset; if(InExposedPin != nullptr && AssetToLoad == nullptr) { AssetToLoad = Cast(InExposedPin->DefaultObject); } if(AssetToLoad) { // HandleAnimReferenceCollection adds 'this' as well as any recursive dependencies TSet PreloadedAssets; TSet DiscoveredAssets; TArray AssetAndDependencies; AssetToLoad->HandleAnimReferenceCollection(AssetAndDependencies, true); DiscoveredAssets.Append(AssetAndDependencies); while(AssetAndDependencies.Num() != 0) { UAnimationAsset* Asset = AssetAndDependencies.Pop(EAllowShrinking::No); if (!PreloadedAssets.Contains(Asset)) { PreloadedAssets.Add(Asset); PreloadObject(Asset); // catch any extra dependencies found after preloading the object // this will have some redundancy, but that can't be avoided without // reworking HandleAnimReferenceCollection TArray Dependencies; Asset->HandleAnimReferenceCollection(Dependencies, true); for (UAnimationAsset* AssetDependency : Dependencies) { if (!DiscoveredAssets.Contains(AssetDependency)) { DiscoveredAssets.Add(AssetDependency); AssetAndDependencies.Add(AssetDependency); } } } } } } #undef LOCTEXT_NAMESPACE