Files
UnrealEngineUWP/Engine/Source/Editor/AnimGraph/Private/AnimationStateMachineSchema.cpp
Michael Schoell db9c8e2240 UProperty and UFunction metadata for Category can now be localized and have improved FText usage throughout the editor (Blueprints, Behavior Trees, Materials, etc).
Big conversion of FStrings and FNames to FText.

Version bump to move some MaterialFunction UProperty from a TArray<FString> to TArray<FText> (some Engine assets are older than being allowed to auto-convert the UProperty)

Auto conversion of FName to FText (and back) now supported (as well as TArrays of those types).

Searching categories by both the localized string and the source string is now supported in Blueprints.

#jira UE-14481 - We are missing ability to translate node categories

#codereview Justin.Sargent

[CL 2542875 by Michael Schoell in Main branch]
2015-05-08 10:46:42 -04:00

444 lines
15 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
AnimationStateMachineSchema.cpp
=============================================================================*/
#include "AnimGraphPrivatePCH.h"
#include "AnimationStateMachineGraph.h"
#include "AnimationStateMachineSchema.h"
#include "AnimationGraphSchema.h"
#include "AnimStateEntryNode.h"
#include "AnimStateNode.h"
#include "AnimStateTransitionNode.h"
#include "AnimStateConduitNode.h"
#include "AnimStateNodeBase.h"
#include "ScopedTransaction.h"
#include "GraphEditorActions.h"
#include "EdGraphUtilities.h"
#include "AssetData.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "GenericCommands.h"
#define LOCTEXT_NAMESPACE "AnimationStateMachineSchema"
/////////////////////////////////////////////////////
TSharedPtr<FEdGraphSchemaAction_NewStateNode> AddNewStateNodeAction(FGraphContextMenuBuilder& ContextMenuBuilder, const FText& Category, const FText& MenuDesc, const FString& Tooltip, const int32 Grouping = 0)
{
TSharedPtr<FEdGraphSchemaAction_NewStateNode> NewStateNode( new FEdGraphSchemaAction_NewStateNode(Category, MenuDesc, Tooltip, Grouping) );
ContextMenuBuilder.AddAction( NewStateNode );
return NewStateNode;
}
/////////////////////////////////////////////////////
// FEdGraphSchemaAction_NewStateNode
UEdGraphNode* FEdGraphSchemaAction_NewStateNode::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode/* = true*/)
{
UEdGraphNode* ResultNode = NULL;
// If there is a template, we actually use it
if (NodeTemplate != NULL)
{
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "K2_AddNode", "Add Node") );
ParentGraph->Modify();
if (FromPin)
{
FromPin->Modify();
}
NodeTemplate->SetFlags(RF_Transactional);
// set outer to be the graph so it doesn't go away
NodeTemplate->Rename(NULL, ParentGraph);
ParentGraph->AddNode(NodeTemplate, false, bSelectNewNode);
NodeTemplate->CreateNewGuid();
NodeTemplate->PostPlacedNewNode();
NodeTemplate->AllocateDefaultPins();
NodeTemplate->AutowireNewNode(FromPin);
NodeTemplate->NodePosX = Location.X;
NodeTemplate->NodePosY = Location.Y;
//@TODO: ANIM: SNAP_GRID isn't centralized or exposed - NodeTemplate->SnapToGrid(SNAP_GRID);
ResultNode = NodeTemplate;
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(ParentGraph);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
return ResultNode;
}
void FEdGraphSchemaAction_NewStateNode::AddReferencedObjects( FReferenceCollector& Collector )
{
FEdGraphSchemaAction::AddReferencedObjects( Collector );
// These don't get saved to disk, but we want to make sure the objects don't get GC'd while the action array is around
Collector.AddReferencedObject( NodeTemplate );
}
/////////////////////////////////////////////////////
// FEdGraphSchemaAction_NewStateComment
UEdGraphNode* FEdGraphSchemaAction_NewStateComment::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode/* = true*/)
{
// Add menu item for creating comment boxes
UEdGraphNode_Comment* CommentTemplate = NewObject<UEdGraphNode_Comment>();
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(ParentGraph);
FVector2D SpawnLocation = Location;
FSlateRect Bounds;
if ((Blueprint != NULL) && FKismetEditorUtilities::GetBoundsForSelectedNodes(Blueprint, Bounds, 50.0f))
{
CommentTemplate->SetBounds(Bounds);
SpawnLocation.X = CommentTemplate->NodePosX;
SpawnLocation.Y = CommentTemplate->NodePosY;
}
return FEdGraphSchemaAction_NewNode::SpawnNodeFromTemplate<UEdGraphNode_Comment>(ParentGraph, CommentTemplate, SpawnLocation);
}
/////////////////////////////////////////////////////
// UAnimationStateMachineSchema
UAnimationStateMachineSchema::UAnimationStateMachineSchema(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PC_Exec = TEXT("exec");
}
void UAnimationStateMachineSchema::CreateDefaultNodesForGraph(UEdGraph& Graph) const
{
// Create the entry/exit tunnels
FGraphNodeCreator<UAnimStateEntryNode> NodeCreator(Graph);
UAnimStateEntryNode* EntryNode = NodeCreator.CreateNode();
NodeCreator.Finalize();
SetNodeMetaData(EntryNode, FNodeMetadata::DefaultGraphNode);
if (UAnimationStateMachineGraph* StateMachineGraph = CastChecked<UAnimationStateMachineGraph>(&Graph))
{
StateMachineGraph->EntryNode = EntryNode;
}
}
const FPinConnectionResponse UAnimationStateMachineSchema::CanCreateConnection(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const
{
// Make sure the pins are not on the same node
if (PinA->GetOwningNode() == PinB->GetOwningNode())
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Both are on the same node"));
}
// Connect entry node to a state is OK
const bool bPinAIsEntry = PinA->GetOwningNode()->IsA(UAnimStateEntryNode::StaticClass());
const bool bPinBIsEntry = PinB->GetOwningNode()->IsA(UAnimStateEntryNode::StaticClass());
const bool bPinAIsStateNode = PinA->GetOwningNode()->IsA(UAnimStateNodeBase::StaticClass());
const bool bPinBIsStateNode = PinB->GetOwningNode()->IsA(UAnimStateNodeBase::StaticClass());
if (bPinAIsEntry || bPinBIsEntry)
{
if (bPinAIsEntry && bPinBIsStateNode)
{
return FPinConnectionResponse(CONNECT_RESPONSE_BREAK_OTHERS_A, TEXT(""));
}
if (bPinBIsEntry && bPinAIsStateNode)
{
return FPinConnectionResponse(CONNECT_RESPONSE_BREAK_OTHERS_B, TEXT(""));
}
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Entry must connect to a state node"));
}
const bool bPinAIsTransition = PinA->GetOwningNode()->IsA(UAnimStateTransitionNode::StaticClass());
const bool bPinBIsTransition = PinB->GetOwningNode()->IsA(UAnimStateTransitionNode::StaticClass());
if (bPinAIsTransition && bPinBIsTransition)
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Cannot wire a transition to a transition"));
}
// Compare the directions
bool bDirectionsOK = false;
if ((PinA->Direction == EGPD_Input) && (PinB->Direction == EGPD_Output))
{
bDirectionsOK = true;
}
else if ((PinB->Direction == EGPD_Input) && (PinA->Direction == EGPD_Output))
{
bDirectionsOK = true;
}
/*
if (!bDirectionsOK)
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Directions are not compatible"));
}
*/
// Transitions are exclusive (both input and output), but states are not
if (bPinAIsTransition)
{
return FPinConnectionResponse(CONNECT_RESPONSE_BREAK_OTHERS_A, TEXT(""));
}
else if (bPinBIsTransition)
{
return FPinConnectionResponse(CONNECT_RESPONSE_BREAK_OTHERS_B, TEXT(""));
}
else if (!bPinAIsTransition && !bPinBIsTransition)
{
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, TEXT("Create a transition"));
}
else
{
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE, TEXT(""));
}
}
bool UAnimationStateMachineSchema::TryCreateConnection(UEdGraphPin* PinA, UEdGraphPin* PinB) const
{
if (PinB->Direction == PinA->Direction)
{
if (UAnimStateNodeBase* Node = Cast<UAnimStateNodeBase>(PinB->GetOwningNode()))
{
if (PinA->Direction == EGPD_Input)
{
PinB = Node->GetOutputPin();
}
else
{
PinB = Node->GetInputPin();
}
}
}
const bool bModified = UEdGraphSchema::TryCreateConnection(PinA, PinB);
if (bModified)
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(PinA->GetOwningNode());
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
return bModified;
}
bool UAnimationStateMachineSchema::CreateAutomaticConversionNodeAndConnections(UEdGraphPin* PinA, UEdGraphPin* PinB) const
{
UAnimStateNodeBase* NodeA = Cast<UAnimStateNodeBase>(PinA->GetOwningNode());
UAnimStateNodeBase* NodeB = Cast<UAnimStateNodeBase>(PinB->GetOwningNode());
if ((NodeA != NULL) && (NodeB != NULL)
&& (NodeA->GetInputPin() != NULL) && (NodeA->GetOutputPin() != NULL)
&& (NodeB->GetInputPin() != NULL) && (NodeB->GetOutputPin() != NULL))
{
UAnimStateTransitionNode* TransitionNode = FEdGraphSchemaAction_NewStateNode::SpawnNodeFromTemplate<UAnimStateTransitionNode>(NodeA->GetGraph(), NewObject<UAnimStateTransitionNode>(), FVector2D(0.0f, 0.0f), false);
if (PinA->Direction == EGPD_Output)
{
TransitionNode->CreateConnections(NodeA, NodeB);
}
else
{
TransitionNode->CreateConnections(NodeB, NodeA);
}
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(TransitionNode->GetBoundGraph());
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
return true;
}
return false;
}
void UAnimationStateMachineSchema::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const
{
// Add state node
{
TSharedPtr<FEdGraphSchemaAction_NewStateNode> Action = AddNewStateNodeAction(ContextMenuBuilder, FText::GetEmpty(), LOCTEXT("AddState", "Add State..."), TEXT("A new state"));
Action->NodeTemplate = NewObject<UAnimStateNode>(ContextMenuBuilder.OwnerOfTemporaries);
}
// Add conduit node
{
TSharedPtr<FEdGraphSchemaAction_NewStateNode> Action = AddNewStateNodeAction(ContextMenuBuilder, FText::GetEmpty(), LOCTEXT("AddConduit", "Add Conduit..."), TEXT("A new conduit state"));
Action->NodeTemplate = NewObject<UAnimStateConduitNode>(ContextMenuBuilder.OwnerOfTemporaries);
}
// Entry point (only if doesn't already exist)
{
bool bHasEntry = false;
for (auto NodeIt = ContextMenuBuilder.CurrentGraph->Nodes.CreateConstIterator(); NodeIt; ++NodeIt)
{
UEdGraphNode* Node = *NodeIt;
if (const UAnimStateEntryNode* StateNode = Cast<UAnimStateEntryNode>(Node))
{
bHasEntry = true;
break;
}
}
if (!bHasEntry)
{
TSharedPtr<FEdGraphSchemaAction_NewStateNode> Action = AddNewStateNodeAction(ContextMenuBuilder, FText::GetEmpty(), LOCTEXT("AddEntryPoint", "Add Entry Point..."), TEXT("Define State Machine's Entry Point"));
Action->NodeTemplate = NewObject<UAnimStateEntryNode>(ContextMenuBuilder.OwnerOfTemporaries);
}
}
// Add Comment
if (!ContextMenuBuilder.FromPin)
{
UBlueprint* OwnerBlueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(ContextMenuBuilder.CurrentGraph);
const bool bIsManyNodesSelected = (FKismetEditorUtilities::GetNumberOfSelectedNodes(OwnerBlueprint) > 0);
const FText MenuDescription = bIsManyNodesSelected ? LOCTEXT("CreateCommentSelection", "Create Comment from Selection") : LOCTEXT("AddComment", "Add Comment...");
const FString ToolTip = TEXT("Create a resizeable comment box around selected nodes.");
TSharedPtr<FEdGraphSchemaAction_NewStateComment> NewComment( new FEdGraphSchemaAction_NewStateComment(FText::GetEmpty(), MenuDescription, ToolTip, 0) );
ContextMenuBuilder.AddAction( NewComment );
}
}
EGraphType UAnimationStateMachineSchema::GetGraphType(const UEdGraph* TestEdGraph) const
{
return GT_StateMachine;
}
void UAnimationStateMachineSchema::GetContextMenuActions(const UEdGraph* CurrentGraph, const UEdGraphNode* InGraphNode, const UEdGraphPin* InGraphPin, FMenuBuilder* MenuBuilder, bool bIsDebugging) const
{
check(CurrentGraph);
UBlueprint* OwnerBlueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(CurrentGraph);
if (InGraphNode != NULL)
{
MenuBuilder->BeginSection("AnimationStateMachineNodeActions", LOCTEXT("NodeActionsMenuHeader", "Node Actions"));
{
if (!bIsDebugging)
{
// Node contextual actions
MenuBuilder->AddMenuEntry( FGenericCommands::Get().Delete );
MenuBuilder->AddMenuEntry( FGenericCommands::Get().Cut );
MenuBuilder->AddMenuEntry( FGenericCommands::Get().Copy );
MenuBuilder->AddMenuEntry( FGenericCommands::Get().Duplicate );
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().ReconstructNodes );
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().BreakNodeLinks );
if(InGraphNode->bCanRenameNode)
{
MenuBuilder->AddMenuEntry( FGenericCommands::Get().Rename );
}
}
}
MenuBuilder->EndSection();
}
Super::GetContextMenuActions(CurrentGraph, InGraphNode, InGraphPin, MenuBuilder, bIsDebugging);
}
FLinearColor UAnimationStateMachineSchema::GetPinTypeColor(const FEdGraphPinType& PinType) const
{
return FLinearColor::White;
}
void UAnimationStateMachineSchema::GetGraphDisplayInformation(const UEdGraph& Graph, /*out*/ FGraphDisplayInfo& DisplayInfo) const
{
DisplayInfo.PlainName = FText::FromString( Graph.GetName() );
DisplayInfo.DisplayName = DisplayInfo.PlainName;
}
void UAnimationStateMachineSchema::DroppedAssetsOnGraph(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraph* Graph) const
{
UAnimationAsset* Asset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets);
if(Asset != NULL)
{
// Spawn new state
UAnimStateNode* NewStateNode = FEdGraphSchemaAction_NewStateNode::SpawnNodeFromTemplate<UAnimStateNode>(Graph, NewObject<UAnimStateNode>(), GraphPosition);
// Try to name the state close to the asset
FEdGraphUtilities::RenameGraphToNameOrCloseToName(NewStateNode->BoundGraph, Asset->GetName());
// Change the current graph context to the inner graph, so that the rest of the drag drop happens inside it
FVector2D NewGraphPosition = FVector2D(-300.0f, 0.0f);
UAnimationGraphSchema::SpawnNodeFromAsset(Asset, NewGraphPosition, NewStateNode->BoundGraph, NewStateNode->GetPoseSinkPinInsideState());
}
}
void UAnimationStateMachineSchema::DroppedAssetsOnNode(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraphNode* Node) const
{
UAnimationAsset* Asset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets);
UAnimStateNode* StateNodeUnderCursor = Cast<UAnimStateNode>(Node);
if(Asset != NULL && StateNodeUnderCursor != NULL)
{
// Dropped onto a state machine state; try some user friendly resposnes
if (UEdGraphPin* PosePin = StateNodeUnderCursor->GetPoseSinkPinInsideState())
{
if (PosePin->LinkedTo.Num() > 0)
{
//@TODO: A2REMOVAL: This doesn't do anything
/*
// Try dropping in onto the node attached to the sink inside the state
check(PosePin->LinkedTo[0] != NULL);
UA2Node* A2Node = Cast<UA2Node>(PosePin->LinkedTo[0]->GetOwningNode());
if(A2Node != NULL)
{
UAnimationGraphSchema::UpdateNodeWithAsset(A2Node, Asset);
}
*/
}
else
{
// Try dropping onto the pose pin, since nothing is currently connected
const FVector2D NewGraphPosition(-300.0f, 0.0f);
UAnimationGraphSchema::SpawnNodeFromAsset(Asset, NewGraphPosition, StateNodeUnderCursor->BoundGraph, PosePin);
}
}
}
}
void UAnimationStateMachineSchema::DroppedAssetsOnPin(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraphPin* Pin) const
{
// unused for state machines?
}
void UAnimationStateMachineSchema::GetAssetsNodeHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphNode* HoverNode, FString& OutTooltipText, bool& OutOkIcon) const
{
UAnimationAsset* Asset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets);
if ((Asset == NULL) || (HoverNode == NULL))
{
OutTooltipText = TEXT("");
OutOkIcon = false;
return;
}
const UAnimStateNode* StateNodeUnderCursor = Cast<const UAnimStateNode>(HoverNode);
if (StateNodeUnderCursor != NULL)
{
OutOkIcon = true;
OutTooltipText = FString::Printf(TEXT("Change node to play %s"), *(Asset->GetName()));
}
else
{
OutTooltipText = TEXT("");
OutOkIcon = false;
}
}
void UAnimationStateMachineSchema::GetAssetsPinHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphPin* HoverPin, FString& OutTooltipText, bool& OutOkIcon) const
{
// unused for state machines?
OutTooltipText = TEXT("");
OutOkIcon = false;
}
#undef LOCTEXT_NAMESPACE