Files
UnrealEngine/Engine/Plugins/Experimental/SceneState/Source/SceneStateMachineGraph/Private/Nodes/SceneStateMachineTransitionNode.cpp
2025-05-18 13:04:45 +08:00

329 lines
9.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Nodes/SceneStateMachineTransitionNode.h"
#include "EdGraphUtilities.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "SceneStateBindingUtils.h"
#include "SceneStateMachineGraphSchema.h"
#include "SceneStateTransitionGraph.h"
#include "SceneStateTransitionGraphSchema.h"
#define LOCTEXT_NAMESPACE "SceneStateMachineTransitionNode"
USceneStateMachineTransitionNode::FOnParametersChanged USceneStateMachineTransitionNode::OnParametersChangedDelegate;
USceneStateMachineTransitionNode::USceneStateMachineTransitionNode()
{
NodeName = TEXT("Transition");
NodeType = UE::SceneState::Graph::EStateMachineNodeType::Transition;
}
USceneStateMachineTransitionNode::FOnParametersChanged::RegistrationType& USceneStateMachineTransitionNode::OnParametersChanged()
{
return OnParametersChangedDelegate;
}
void USceneStateMachineTransitionNode::NotifyParametersChanged()
{
OnParametersChangedDelegate.Broadcast(*this);
}
TArray<USceneStateMachineTransitionNode*> USceneStateMachineTransitionNode::GetTransitionsToRelink(UEdGraphPin* InSourcePin, UEdGraphPin* InOldTargetPin, TConstArrayView<UEdGraphNode*> InSelectedGraphNodes)
{
check(InSourcePin);
USceneStateMachineNode* SourceNode = Cast<USceneStateMachineNode>(InSourcePin->GetOwningNode());
if (!SourceNode || !SourceNode->HasValidPins())
{
return {};
}
check(InOldTargetPin);
USceneStateMachineTransitionNode* OldTransitionNode = Cast<USceneStateMachineTransitionNode>(InOldTargetPin->GetOwningNode());
if (!OldTransitionNode)
{
return {};
}
// Collect all transition nodes starting at the source state
TArray<USceneStateMachineTransitionNode*> TransitionNodes = SourceNode->GatherTransitions();
// Compare the target states rather than comparing against the transition nodes
UEdGraphNode* OldTargetNode = OldTransitionNode->GetTargetNode();
// Remove the transition nodes from the candidates that are linked to a different target state.
for (TArray<USceneStateMachineTransitionNode*>::TIterator Iter(TransitionNodes); Iter; ++Iter)
{
USceneStateMachineTransitionNode* CurrentTransition = *Iter;
if (!CurrentTransition)
{
continue;
}
// Get the actual target states from the transition nodes
UEdGraphNode* TargetNode = CurrentTransition->GetTargetNode();
if (TargetNode != OldTargetNode)
{
Iter.RemoveCurrent();
}
}
// Collect the subset of selected transitions from the list of possible transitions to be relinked
TSet<USceneStateMachineTransitionNode*> SelectedTransitionNodes;
SelectedTransitionNodes.Reserve(InSelectedGraphNodes.Num());
for (UEdGraphNode* GraphNode : InSelectedGraphNodes)
{
USceneStateMachineTransitionNode* TransitionNode = Cast<USceneStateMachineTransitionNode>(GraphNode);
if (TransitionNode && TransitionNodes.Contains(TransitionNode))
{
SelectedTransitionNodes.Add(TransitionNode);
}
}
if (!SelectedTransitionNodes.IsEmpty())
{
for (TArray<USceneStateMachineTransitionNode*>::TIterator Iter(TransitionNodes); Iter; ++Iter)
{
// Only relink the selected transitions. If none are selected, relink them all.
if (!SelectedTransitionNodes.Contains(*Iter))
{
Iter.RemoveCurrent();
}
}
}
return TransitionNodes;
}
USceneStateMachineNode* USceneStateMachineTransitionNode::GetSourceNode() const
{
UEdGraphPin* InputPin = GetInputPin();
if (!InputPin || InputPin->LinkedTo.IsEmpty() || !InputPin->LinkedTo[0])
{
return nullptr;
}
return Cast<USceneStateMachineNode>(InputPin->LinkedTo[0]->GetOwningNode());
}
USceneStateMachineNode* USceneStateMachineTransitionNode::GetTargetNode() const
{
UEdGraphPin* OutputPin = GetOutputPin();
if (!OutputPin || OutputPin->LinkedTo.IsEmpty() || !OutputPin->LinkedTo[0])
{
return nullptr;
}
return Cast<USceneStateMachineNode>(OutputPin->LinkedTo[0]->GetOwningNode());
}
void USceneStateMachineTransitionNode::CreateConnections(USceneStateMachineNode* InSourceState, USceneStateMachineNode* InTargetState)
{
check(InSourceState && InTargetState);
// Source State's Output -> This Input
{
UEdGraphPin* InputPin = GetInputPin();
check(InputPin);
InputPin->Modify();
InputPin->LinkedTo.Empty();
UEdGraphPin* SourceOutputPin = InSourceState->GetOutputPin();
check(SourceOutputPin);
InputPin->MakeLinkTo(SourceOutputPin);
}
// This Output -> Target State's Input
{
UEdGraphPin* OutputPin = GetOutputPin();
check(OutputPin);
// This to next
OutputPin->Modify();
OutputPin->LinkedTo.Empty();
UEdGraphPin* TargetInputPin = InTargetState->GetInputPin();
check(TargetInputPin);
OutputPin->MakeLinkTo(TargetInputPin);
}
}
void USceneStateMachineTransitionNode::RelinkHead(USceneStateMachineNode* InNewTargetState)
{
// Relink the target state of the transition node
UEdGraphPin* OutputPin = GetOutputPin();
check(OutputPin);
OutputPin->Modify();
if (USceneStateMachineNode* OldTargetState = GetTargetNode())
{
OutputPin->BreakLinkTo(OldTargetState->GetInputPin());
}
if (InNewTargetState)
{
OutputPin->MakeLinkTo(InNewTargetState->GetInputPin());
}
}
UEdGraph* USceneStateMachineTransitionNode::CreateBoundGraphInternal()
{
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(this
, NAME_None
, USceneStateTransitionGraph::StaticClass()
, USceneStateTransitionGraphSchema::StaticClass());
check(NewGraph);
FEdGraphUtilities::RenameGraphToNameOrCloseToName(NewGraph, TEXT("TransitionGraph"));
return NewGraph;
}
void USceneStateMachineTransitionNode::AllocateDefaultPins()
{
UEdGraphPin* InputPin = CreatePin(EGPD_Input, USceneStateMachineGraphSchema::PC_Transition, USceneStateMachineGraphSchema::PN_In);
UEdGraphPin* OutputPin = CreatePin(EGPD_Output, USceneStateMachineGraphSchema::PC_Transition, USceneStateMachineGraphSchema::PN_Out);
check(InputPin && OutputPin);
InputPin->bHidden = true;
OutputPin->bHidden = true;
}
FText USceneStateMachineTransitionNode::GetNodeTitle(ENodeTitleType::Type InTitleType) const
{
return GetTitle();
}
FText USceneStateMachineTransitionNode::GetTooltipText() const
{
return LOCTEXT("Tooltip", "State transition node in a State Machine");
}
void USceneStateMachineTransitionNode::PinConnectionListChanged(UEdGraphPin* InPin)
{
if (InPin && InPin->LinkedTo.IsEmpty())
{
// Destroy this node. Transitions must always have an input and output connection
Modify();
// Our parent graph will have our graph in SubGraphs so needs to be modified to record that.
if (UEdGraph* ParentGraph = GetGraph())
{
ParentGraph->Modify();
}
DestroyNode();
}
}
void USceneStateMachineTransitionNode::PostPasteNode()
{
GenerateNewNodeName();
// fail-safe, create empty transition graph
ConditionallyCreateBoundGraph();
check(GetBoundGraph());
Super::PostPasteNode();
// Get rid of nodes that aren't fully linked (transition nodes have fixed pins as they describe connection between two nodes)
for (UEdGraphPin* Pin : Pins)
{
if (!Pin || Pin->LinkedTo.IsEmpty())
{
DestroyNode();
break;
}
}
}
void USceneStateMachineTransitionNode::PostPlacedNewNode()
{
Super::PostPlacedNewNode();
GenerateNewNodeName();
ConditionallyCreateBoundGraph();
}
FText USceneStateMachineTransitionNode::GetTitle() const
{
USceneStateMachineNode* SourceState = GetSourceNode();
USceneStateMachineNode* TargetState = GetTargetNode();
if (SourceState && TargetState)
{
FFormatNamedArguments Args;
Args.Add(TEXT("SourceState"), FText::FromName(SourceState->GetNodeName()));
Args.Add(TEXT("TargetState"), FText::FromName(TargetState->GetNodeName()));
return FText::Format(LOCTEXT("SourceTargetTransitionTitle", "{SourceState} to {TargetState}"), Args);
}
return FText::Format(LOCTEXT("DefaultTransitionStateTitle", "Transition: {0}"), FText::FromName(GetNodeName()));
}
bool USceneStateMachineTransitionNode::IsBoundToGraphLifetime(UEdGraph& InGraph) const
{
return &InGraph == GetBoundGraph();
}
UEdGraphNode* USceneStateMachineTransitionNode::AsNode()
{
return this;
}
void USceneStateMachineTransitionNode::PostInitProperties()
{
Super::PostInitProperties();
if (!IsTemplate())
{
ParametersId = FGuid::NewGuid();
}
}
void USceneStateMachineTransitionNode::PostLoad()
{
Super::PostLoad();
// Move transition graph to bound graph
if (TransitionGraph && TransitionGraph->GetOuter() == this)
{
BoundGraphs.Empty(0);
BoundGraphs.Add(TransitionGraph);
}
TransitionGraph = nullptr;
}
void USceneStateMachineTransitionNode::PostDuplicate(bool bInDuplicateForPIE)
{
Super::PostDuplicate(bInDuplicateForPIE);
GenerateNewParametersId();
}
void USceneStateMachineTransitionNode::PostEditImport()
{
Super::PostEditImport();
GenerateNewParametersId();
}
void USceneStateMachineTransitionNode::PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent)
{
Super::PostEditChangeProperty(InPropertyChangedEvent);
if (InPropertyChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED(USceneStateMachineTransitionNode, Parameters))
{
NotifyParametersChanged();
}
}
void USceneStateMachineTransitionNode::GenerateNewParametersId()
{
const FGuid OldParametersId = ParametersId;
ParametersId = FGuid::NewGuid();
UE::SceneState::HandleStructIdChanged(*this, OldParametersId, ParametersId);
}
#undef LOCTEXT_NAMESPACE