// Copyright Epic Games, Inc. All Rights Reserved. #include "K2Node_TransitionRuleGetter.h" #include "AnimGraphNode_Base.h" #include "AnimStateNode.h" #include "AnimStateTransitionNode.h" #include "Animation/AnimBlueprint.h" #include "Animation/AnimBlueprintGeneratedClass.h" #include "Animation/AnimationAsset.h" #include "AnimationGraphSchema.h" #include "AnimationTransitionSchema.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintActionFilter.h" #include "BlueprintNodeBinder.h" #include "BlueprintNodeSpawner.h" #include "Containers/Array.h" #include "Containers/Map.h" #include "Containers/UnrealString.h" #include "Delegates/Delegate.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphSchema.h" #include "EdGraphSchema_K2.h" #include "HAL/PlatformCrt.h" #include "HAL/PlatformMath.h" #include "Internationalization/Internationalization.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Misc/AssertionMacros.h" #include "Templates/Casts.h" #include "Templates/UnrealTemplate.h" #include "UObject/Class.h" #include "UObject/Object.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" ///////////////////////////////////////////////////// // UK2Node_TransitionRuleGetter #define LOCTEXT_NAMESPACE "TransitionRuleGetter" UK2Node_TransitionRuleGetter::UK2Node_TransitionRuleGetter(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void UK2Node_TransitionRuleGetter::AllocateDefaultPins() { UEdGraphPin* OutputPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Real, UEdGraphSchema_K2::PC_Float, TEXT("Output")); OutputPin->PinFriendlyName = GetFriendlyName(GetterType); PreloadObject(AssociatedAnimAssetPlayerNode); PreloadObject(AssociatedStateNode); Super::AllocateDefaultPins(); } void UK2Node_TransitionRuleGetter::PreloadRequiredAssets() { PreloadObject(AssociatedAnimAssetPlayerNode); PreloadObject(AssociatedStateNode); Super::PreloadRequiredAssets(); } FText UK2Node_TransitionRuleGetter::GetFriendlyName(ETransitionGetter::Type TypeID) { FText FriendlyName; switch (TypeID) { case ETransitionGetter::AnimationAsset_GetCurrentTime: FriendlyName = LOCTEXT("AnimationAssetTimeElapsed", "CurrentTime"); break; case ETransitionGetter::AnimationAsset_GetLength: FriendlyName = LOCTEXT("AnimationAssetSequenceLength", "Length"); break; case ETransitionGetter::AnimationAsset_GetCurrentTimeFraction: FriendlyName = LOCTEXT("AnimationAssetFractionalTimeElapsed", "CurrentTime (ratio)"); break; case ETransitionGetter::AnimationAsset_GetTimeFromEnd: FriendlyName = LOCTEXT("AnimationAssetTimeRemaining", "TimeRemaining"); break; case ETransitionGetter::AnimationAsset_GetTimeFromEndFraction: FriendlyName = LOCTEXT("AnimationAssetFractionalTimeRemaining", "TimeRemaining (ratio)"); break; case ETransitionGetter::CurrentState_ElapsedTime: FriendlyName = LOCTEXT("CurrentStateElapsedTime", "Elapsed State Time"); break; case ETransitionGetter::CurrentState_GetBlendWeight: FriendlyName = LOCTEXT("CurrentStateBlendWeight", "State's Blend Weight"); break; case ETransitionGetter::ArbitraryState_GetBlendWeight: FriendlyName = LOCTEXT("ArbitraryStateBlendWeight", "BlendWeight"); break; case ETransitionGetter::CurrentTransitionDuration: FriendlyName = LOCTEXT("CrossfadeDuration", "Crossfade Duration"); break; default: ensure(false); break; } return FriendlyName; } FText UK2Node_TransitionRuleGetter::GetNodeTitle(ENodeTitleType::Type TitleType) const { // @TODO: FText::Format() is slow... consider caching this tooltip like // we do for a lot of the BP nodes now (unfamiliar with this // node's purpose, so hesitant to muck with this at this time). if (AssociatedAnimAssetPlayerNode != NULL) { UAnimationAsset* BoundAsset = AssociatedAnimAssetPlayerNode->GetAnimationAsset(); if (BoundAsset) { FFormatNamedArguments Args; Args.Add(TEXT("BoundAsset"), FText::FromString(BoundAsset->GetName())); return FText::Format(LOCTEXT("AnimationAssetInfoGetterTitle", "{BoundAsset} Asset"), Args); } else { return LOCTEXT("AnimationAssetInfoGetterTitle_None", "Animation Asset"); } } else if (AssociatedStateNode != NULL) { if (UAnimStateNode* State = AssociatedStateNode) { const FString OwnerName = State->GetOuter()->GetName(); FFormatNamedArguments Args; Args.Add(TEXT("OwnerName"), FText::FromString(OwnerName)); Args.Add(TEXT("StateName"), FText::FromString(State->GetStateName())); return FText::Format(LOCTEXT("StateInfoGetterTitle", "{OwnerName}.{StateName} State"), Args); } else { return LOCTEXT("StateInfoGetterTitle_None", "State"); } } else if (GetterType == ETransitionGetter::CurrentTransitionDuration) { return LOCTEXT("TransitionDuration", "Transition"); } else if (GetterType == ETransitionGetter::CurrentState_ElapsedTime || GetterType == ETransitionGetter::CurrentState_GetBlendWeight) { return LOCTEXT("CurrentState", "Current State"); } return Super::GetNodeTitle(TitleType); } void UK2Node_TransitionRuleGetter::GetStateSpecificAnimGraphSchemaMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar, const UAnimBlueprint* AnimBlueprint, UAnimStateNode* StateNode) const { auto UiSpecOverride = [](const FBlueprintActionContext& /*Context*/, const IBlueprintNodeBinder::FBindingSet& Bindings, FBlueprintActionUiSpec* UiSpecOut, UAnimStateNode* AnimStateNode) { const FString OwnerName = AnimStateNode->GetOuter()->GetName(); UiSpecOut->MenuName = FText::Format(LOCTEXT("TransitionRuleGetterTitle", "Current {0} for state '{1}.{2}'"), UK2Node_TransitionRuleGetter::GetFriendlyName(ETransitionGetter::ArbitraryState_GetBlendWeight), FText::FromString(OwnerName), FText::FromString(AnimStateNode->GetStateName())); }; auto PostSpawnSetupLambda = [](UEdGraphNode* NewNode, bool /*bIsTemplateNode*/, UAnimStateNode* AnimStateNode) { UK2Node_TransitionRuleGetter* NewNodeTyped = CastChecked(NewNode); NewNodeTyped->AssociatedStateNode = AnimStateNode; NewNodeTyped->GetterType = ETransitionGetter::ArbitraryState_GetBlendWeight; }; UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create( UK2Node_TransitionRuleGetter::StaticClass(), nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(PostSpawnSetupLambda, StateNode) ); Spawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(UiSpecOverride, StateNode); ActionRegistrar.AddBlueprintAction( AnimBlueprint, Spawner ); } void UK2Node_TransitionRuleGetter::GetStateSpecificAnimTransitionSchemaMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar, const UAnimBlueprint* AnimBlueprint, UAnimStateNode* StateNode) const { // Offer options from the source state // Sequence player positions ETransitionGetter::Type SequenceSpecificGetters[] = { ETransitionGetter::AnimationAsset_GetCurrentTime, ETransitionGetter::AnimationAsset_GetLength, ETransitionGetter::AnimationAsset_GetCurrentTimeFraction, ETransitionGetter::AnimationAsset_GetTimeFromEnd, ETransitionGetter::AnimationAsset_GetTimeFromEndFraction }; // Using the State Machine's graph, find all asset players nodes TArray AssetPlayers; StateNode->BoundGraph->GetNodesOfClassEx(/*out*/ AssetPlayers); for (int32 TypeIndex = 0; TypeIndex < UE_ARRAY_COUNT(SequenceSpecificGetters); ++TypeIndex) { for (auto NodeIt = AssetPlayers.CreateConstIterator(); NodeIt; ++NodeIt) { UAnimGraphNode_Base* AnimNode = CastChecked(*NodeIt); if (AnimNode->DoesSupportTimeForTransitionGetter()) { UAnimationAsset * AnimAsset = AnimNode->GetAnimationAsset(); if (AnimAsset) { auto UiSpecOverride = [](const FBlueprintActionContext& /*Context*/, const IBlueprintNodeBinder::FBindingSet& Bindings, FBlueprintActionUiSpec* UiSpecOut, FString AssetName, TEnumAsByte InGetterType) { UiSpecOut->Category = LOCTEXT("AssetPlayer", "Asset Player"); FFormatNamedArguments Args; Args.Add(TEXT("NodeName"), UK2Node_TransitionRuleGetter::GetFriendlyName(InGetterType)); Args.Add(TEXT("AssetName"), FText::FromString(AssetName)); FText Title = FText::Format(LOCTEXT("TransitionFor", "{NodeName} for '{AssetName}'"), Args); UiSpecOut->MenuName = Title; }; auto PostSpawnSetupLambda = [](UEdGraphNode* NewNode, bool /*bIsTemplateNode*/, UAnimGraphNode_Base* InAssociatedAnimAssetPlayerNode, TEnumAsByte InGetterType) { UK2Node_TransitionRuleGetter* NewNodeTyped = CastChecked(NewNode); NewNodeTyped->AssociatedAnimAssetPlayerNode = InAssociatedAnimAssetPlayerNode; NewNodeTyped->GetterType = InGetterType; }; // Prepare the node spawner UAnimGraphNode_Base* AssociatedAnimNode = AnimNode; const FString AssetName = AnimAsset->GetName(); TEnumAsByte TransitionGetterType = SequenceSpecificGetters[TypeIndex]; UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create( UK2Node_TransitionRuleGetter::StaticClass(), nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(PostSpawnSetupLambda, AssociatedAnimNode, TransitionGetterType) ); Spawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(UiSpecOverride, AssetName, TransitionGetterType); ActionRegistrar.AddBlueprintAction( AnimBlueprint, Spawner ); } } } } } void UK2Node_TransitionRuleGetter::GetStateSpecificMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar, const UAnimBlueprint* AnimBlueprint) const { TArray States; FBlueprintEditorUtils::GetAllNodesOfClass(AnimBlueprint, /*out*/ States); // Go through all states to generate possible menu actions for (auto StateIt = States.CreateIterator(); StateIt; ++StateIt) { UAnimStateNode* StateNode = *StateIt; GetStateSpecificAnimGraphSchemaMenuActions(ActionRegistrar, AnimBlueprint, StateNode); GetStateSpecificAnimTransitionSchemaMenuActions(ActionRegistrar, AnimBlueprint, StateNode); } } void UK2Node_TransitionRuleGetter::GetNonStateSpecificMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { // actions get registered under specific object-keys; the idea is that // actions might have to be updated (or deleted) if their object-key is // mutated (or removed)... here we use the node's class (so if the node // type disappears, then the action should go with it) UClass* ActionKey = GetClass(); // to keep from needlessly instantiating a UBlueprintNodeSpawner, first // check to make sure that the registrar is looking for actions of this type // (could be regenerating actions for a specific asset, and therefore the // registrar would only accept actions corresponding to that asset) if (ActionRegistrar.IsOpenForRegistration(ActionKey)) { // Non-sequence specific ones ETransitionGetter::Type NonSpecificGetters[] = { ETransitionGetter::CurrentTransitionDuration, ETransitionGetter::CurrentState_ElapsedTime, ETransitionGetter::CurrentState_GetBlendWeight }; for (int32 TypeIndex = 0; TypeIndex < UE_ARRAY_COUNT(NonSpecificGetters); ++TypeIndex) { auto UiSpecOverride = [](const FBlueprintActionContext& /*Context*/, const IBlueprintNodeBinder::FBindingSet& Bindings, FBlueprintActionUiSpec* UiSpecOut, TEnumAsByte InGetterType) { UiSpecOut->Category = LOCTEXT("Transition", "Transition"); UiSpecOut->MenuName = UK2Node_TransitionRuleGetter::GetFriendlyName(InGetterType); }; auto PostSpawnSetupLambda = [](UEdGraphNode* NewNode, bool /*bIsTemplateNode*/, TEnumAsByte InGetterType) { UK2Node_TransitionRuleGetter* NewNodeTyped = CastChecked(NewNode); NewNodeTyped->GetterType = InGetterType; }; TEnumAsByte TransitionGetterType = NonSpecificGetters[TypeIndex]; UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create( UK2Node_TransitionRuleGetter::StaticClass(), nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(PostSpawnSetupLambda, TransitionGetterType) ); Spawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(UiSpecOverride, TransitionGetterType); ActionRegistrar.AddBlueprintAction( ActionKey, Spawner ); } } } void UK2Node_TransitionRuleGetter::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { } FText UK2Node_TransitionRuleGetter::GetTooltipText() const { return GetNodeTitle(ENodeTitleType::FullTitle); } bool UK2Node_TransitionRuleGetter::CanCreateUnderSpecifiedSchema(const UEdGraphSchema* Schema) const { return Cast(Schema) != NULL || Cast(Schema) != NULL; } UAnimStateTransitionNode* GetTransitionNodeFromGraph(const FAnimBlueprintDebugData& DebugData, const UEdGraph* Graph) { if (const TWeakObjectPtr* TransNodePtr = DebugData.TransitionGraphToNodeMap.Find(Graph)) { return TransNodePtr->Get(); } if (const TWeakObjectPtr* TransNodePtr = DebugData.TransitionBlendGraphToNodeMap.Find(Graph)) { return TransNodePtr->Get(); } return NULL; } bool UK2Node_TransitionRuleGetter::IsActionFilteredOut(class FBlueprintActionFilter const& Filter) { if(Filter.Context.Graphs.Num()) { const UEdGraphSchema* Schema = Filter.Context.Graphs[0]->GetSchema(); if(Cast(Schema)) { if(GetterType == ETransitionGetter::ArbitraryState_GetBlendWeight) { // only show the transition nodes if the associated state node is in every graph: if( AssociatedStateNode ) { for( auto Blueprint : Filter.Context.Blueprints ) { TArray States; FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, /*out*/ States); if( !States.Contains( AssociatedStateNode ) ) { return true; } } return false; } } return true; } else if(Cast(Schema)) { // Non-sequence specific TransitionRuleGetter nodes have no associated nodes assigned, they are always allowed in AnimationTransitionSchema graphs if( AssociatedStateNode == nullptr && AssociatedAnimAssetPlayerNode == nullptr) { return false; } else if(AssociatedAnimAssetPlayerNode) { if(Filter.Context.Blueprints.Num()) { UAnimBlueprint* AnimBlueprint = Cast(Filter.Context.Blueprints[0]); check(AnimBlueprint); if (UAnimBlueprintGeneratedClass* AnimBlueprintClass = AnimBlueprint->GetAnimBlueprintSkeletonClass()) { // Local function to find the transition node using the context graph and AnimBlueprintDebugData auto GetTransitionNodeFromGraphLambda = [](const FAnimBlueprintDebugData& DebugData, const UEdGraph* Graph) -> UAnimStateTransitionNode* { if (const TWeakObjectPtr* TransNodePtr = DebugData.TransitionGraphToNodeMap.Find(Graph)) { return TransNodePtr->Get(); } if (const TWeakObjectPtr* TransNodePtr = DebugData.TransitionBlendGraphToNodeMap.Find(Graph)) { return TransNodePtr->Get(); } return NULL; }; // Check if the TransitionNode can be found in the AninBlueprint's debug data if (UAnimStateTransitionNode* TransNode = GetTransitionNodeFromGraph(AnimBlueprintClass->GetAnimBlueprintDebugData(), Filter.Context.Graphs[0])) { if (UAnimStateNode* SourceStateNode = Cast(TransNode->GetPreviousState())) { // Check if the AnimAssetPlayerNode's graph is the state machine's bound graph if(AssociatedAnimAssetPlayerNode->GetGraph() == SourceStateNode->BoundGraph) { return false; } } } } } } } } return true; } UEdGraphPin* UK2Node_TransitionRuleGetter::GetOutputPin() const { return FindPinChecked(TEXT("Output")); } #undef LOCTEXT_NAMESPACE