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

592 lines
20 KiB
C++

// 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<FAnimNode_AssetPlayerBase>(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<FAnimNode_AssetPlayerBase>(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<UAnimLayerInterface>())
{
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<FAnimNode_AssetPlayerBase>(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<FAnimNode_AssetPlayerBase>(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<UClass> 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<UAnimGraphNode_Base>();
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<UAnimGraphNode_Base>();
// 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<UAnimGraphNode_AssetPlayerBase>(InNewNode);
if(InAssetData.IsValid())
{
InAssetData.GetTagValue("Skeleton", GraphNode->UnloadedSkeletonName);
if(GraphNode->UnloadedSkeletonName == TEXT("None"))
{
GraphNode->UnloadedSkeletonName.Empty();
}
if(!bInIsTemplateNode)
{
GraphNode->SetAnimationAsset(CastChecked<UAnimationAsset>(InAssetData.GetAsset()));
}
}
}
void UAnimGraphNode_AssetPlayerBase::GetMenuActionsHelper(
FBlueprintActionDatabaseRegistrar& InActionRegistrar,
TSubclassOf<UAnimGraphNode_Base> InNodeClass,
const TArray<TSubclassOf<UObject>>& InAssetTypes,
const TArray<TSubclassOf<UObject>>& InExcludedAssetTypes,
const TFunctionRef<FText(const FAssetData&, UClass*)>& InMenuNameFunction,
const TFunctionRef<FText(const FAssetData&, UClass*)>& InMenuTooltipFunction,
const TFunction<void(UEdGraphNode*, bool, const FAssetData)>& InSetupNewNodeFromAssetFunction,
const TFunction<void(UEdGraphNode*, bool, TSubclassOf<UObject>)>& InSetupNewNodeFromClassFunction,
const TFunction<FText(const FAssetData&)>& 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<UObject>& 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<UObject>& AssetType : InAssetTypes)
{
if(QueryObject && QueryObject->IsA(AssetType.Get()))
{
bIsObjectOfAssetType = true;
break;
}
}
if (QueryObject == nullptr || QueryObject == InNodeClass.Get())
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
FARFilter Filter;
for(const TSubclassOf<UObject>& AssetType : InAssetTypes)
{
Filter.ClassPaths.Add(AssetType.Get()->GetClassPathName());
}
for(const TSubclassOf<UObject>& ExcludedAssetType : InExcludedAssetTypes)
{
Filter.RecursiveClassPathsExclusionSet.Add(ExcludedAssetType.Get()->GetClassPathName());
}
Filter.bRecursiveClasses = true;
TArray<FAssetData> 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<UObject>& 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<UAnimBlueprint>(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<FText(UAnimationAsset*)> 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<UAnimationAsset>(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<FText(UAnimationAsset*)> 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<FAnimNode_AssetPlayerBase>(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<UAnimationAsset> InAssetType, UEdGraphPin* InExposedPin, FName InPropertyName)
{
UAnimationAsset* AssetToCheck = InAsset;
if(InExposedPin != nullptr && AssetToCheck == nullptr)
{
AssetToCheck = Cast<UAnimationAsset>(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<UAnimationAsset>(InExposedPin->DefaultObject);
}
if(AssetToLoad)
{
// HandleAnimReferenceCollection adds 'this' as well as any recursive dependencies
TSet<UAnimationAsset*> PreloadedAssets;
TSet<UAnimationAsset*> DiscoveredAssets;
TArray<UAnimationAsset*> 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<UAnimationAsset*> Dependencies;
Asset->HandleAnimReferenceCollection(Dependencies, true);
for (UAnimationAsset* AssetDependency : Dependencies)
{
if (!DiscoveredAssets.Contains(AssetDependency))
{
DiscoveredAssets.Add(AssetDependency);
AssetAndDependencies.Add(AssetDependency);
}
}
}
}
}
}
#undef LOCTEXT_NAMESPACE