You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Users can now select the blend profile mode: Blend Weight Factor based or Time Factor based. The Blend Weight Factor method is the legacy method, which multiplies the transition blend weight with the per bone factor. The new Time Factor method allows you to specify a time factor. A value of 0.5 would mean the bone would take half the time of the transition to reach its target state, while a value of 0.1 would mean it takes a tenth of the time, etc. A value of 0 would make the bone instantly transition into the target state. #jira UE-100994 #review-14530265 @Aaron.Cox, @Thomas.Sarkanen [CL 14539393 by john vanderburg in ue5-main branch]
768 lines
24 KiB
C++
768 lines
24 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
AnimStateTransitionNode.cpp
|
|
=============================================================================*/
|
|
|
|
#include "AnimStateTransitionNode.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "Animation/AnimInstance.h"
|
|
#include "AnimationTransitionGraph.h"
|
|
#include "AnimationTransitionSchema.h"
|
|
#include "AnimationCustomTransitionGraph.h"
|
|
#include "AnimationCustomTransitionSchema.h"
|
|
#include "AnimGraphNode_TransitionResult.h"
|
|
#include "Kismet2/CompilerResultsLog.h"
|
|
#include "EdGraphUtilities.h"
|
|
#include "Kismet2/Kismet2NameValidators.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "Animation/BlendProfile.h"
|
|
#include "UObject/UE5MainStreamObjectVersion.h"
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// IAnimStateTransitionNodeSharedDataHelper
|
|
|
|
#define LOCTEXT_NAMESPACE "A3Nodes"
|
|
|
|
class ANIMGRAPH_API IAnimStateTransitionNodeSharedDataHelper
|
|
{
|
|
public:
|
|
void UpdateSharedData(UAnimStateTransitionNode* Node, TSharedPtr<INameValidatorInterface> NameValidator);
|
|
void MakeSureGuidExists(UAnimStateTransitionNode* Node);
|
|
|
|
protected:
|
|
virtual bool CheckIfNodesShouldShareData(const UAnimStateTransitionNode* NodeA, const UAnimStateTransitionNode* NodeB) = 0;
|
|
virtual bool CheckIfHasDataToShare(const UAnimStateTransitionNode* Node) = 0;
|
|
virtual void ShareData(UAnimStateTransitionNode* NodeWhoWantsToShare, const UAnimStateTransitionNode* ShareFrom) = 0;
|
|
virtual FString& AccessShareDataName(UAnimStateTransitionNode* Node) = 0;
|
|
virtual FGuid& AccessShareDataGuid(UAnimStateTransitionNode* Node) = 0;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FAnimStateTransitionNodeSharedRulesHelper
|
|
|
|
class ANIMGRAPH_API FAnimStateTransitionNodeSharedRulesHelper : public IAnimStateTransitionNodeSharedDataHelper
|
|
{
|
|
protected:
|
|
virtual bool CheckIfNodesShouldShareData(const UAnimStateTransitionNode* NodeA, const UAnimStateTransitionNode* NodeB) override;
|
|
virtual bool CheckIfHasDataToShare(const UAnimStateTransitionNode* Node) override;
|
|
virtual void ShareData(UAnimStateTransitionNode* NodeWhoWantsToShare, const UAnimStateTransitionNode* ShareFrom) override;
|
|
virtual FString& AccessShareDataName(UAnimStateTransitionNode* Node) override;
|
|
virtual FGuid& AccessShareDataGuid(UAnimStateTransitionNode* Node) override;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FAnimStateTransitionNodeSharedCrossfadeHelper
|
|
|
|
class ANIMGRAPH_API FAnimStateTransitionNodeSharedCrossfadeHelper : public IAnimStateTransitionNodeSharedDataHelper
|
|
{
|
|
protected:
|
|
virtual bool CheckIfNodesShouldShareData(const UAnimStateTransitionNode* NodeA, const UAnimStateTransitionNode* NodeB) override;
|
|
virtual bool CheckIfHasDataToShare(const UAnimStateTransitionNode* Node) override;
|
|
virtual void ShareData(UAnimStateTransitionNode* NodeWhoWantsToShare, const UAnimStateTransitionNode* ShareFrom) override;
|
|
virtual FString& AccessShareDataName(UAnimStateTransitionNode* Node) override;
|
|
virtual FGuid& AccessShareDataGuid(UAnimStateTransitionNode* Node) override;
|
|
};
|
|
|
|
/////////////////////////////////////////////////////
|
|
// UAnimStateTransitionNode
|
|
|
|
UAnimStateTransitionNode::UAnimStateTransitionNode(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
|
|
CrossfadeDuration = 0.2f;
|
|
BlendMode = EAlphaBlendOption::HermiteCubic;
|
|
bSharedRules = false;
|
|
SharedRulesGuid.Invalidate();
|
|
bSharedCrossfade = false;
|
|
SharedCrossfadeIdx = INDEX_NONE;
|
|
SharedCrossfadeGuid.Invalidate();
|
|
Bidirectional = false;
|
|
PriorityOrder = 1;
|
|
LogicType = ETransitionLogicType::TLT_StandardBlend;
|
|
BlendProfileMode = EBlendProfileMode::TimeFactor;
|
|
}
|
|
|
|
void UAnimStateTransitionNode::AllocateDefaultPins()
|
|
{
|
|
UEdGraphPin* Inputs = CreatePin(EGPD_Input, TEXT("Transition"), TEXT("In"));
|
|
Inputs->bHidden = true;
|
|
UEdGraphPin* Outputs = CreatePin(EGPD_Output, TEXT("Transition"), TEXT("Out"));
|
|
Outputs->bHidden = true;
|
|
}
|
|
|
|
void UAnimStateTransitionNode::PostPlacedNewNode()
|
|
{
|
|
CreateBoundGraph();
|
|
}
|
|
|
|
void UAnimStateTransitionNode::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
// make sure we have guid for shared rules
|
|
if (bSharedRules && !SharedRulesGuid.IsValid())
|
|
{
|
|
FAnimStateTransitionNodeSharedRulesHelper().MakeSureGuidExists(this);
|
|
}
|
|
|
|
// make sure we have guid for shared crossfade
|
|
if (bSharedCrossfade && !SharedCrossfadeGuid.IsValid())
|
|
{
|
|
FAnimStateTransitionNodeSharedCrossfadeHelper().MakeSureGuidExists(this);
|
|
}
|
|
|
|
if(GetLinkerUE4Version() < VER_UE4_ADDED_NON_LINEAR_TRANSITION_BLENDS)
|
|
{
|
|
switch(CrossfadeMode_DEPRECATED)
|
|
{
|
|
case ETransitionBlendMode::TBM_Linear:
|
|
BlendMode = EAlphaBlendOption::Linear;
|
|
break;
|
|
case ETransitionBlendMode::TBM_Cubic:
|
|
// Old cubic was actually an in/out hermite polynomial (FMath::SmoothStep)
|
|
BlendMode = EAlphaBlendOption::HermiteCubic;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(GetLinkerCustomVersion(FAnimPhysObjectVersion::GUID) < FAnimPhysObjectVersion::FixupBadBlendProfileReferences)
|
|
{
|
|
ValidateBlendProfile();
|
|
}
|
|
}
|
|
|
|
bool UAnimStateTransitionNode::ValidateBlendProfile()
|
|
{
|
|
if(BlendProfile)
|
|
{
|
|
// validate the skeleton of our blend profile
|
|
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(this);
|
|
UAnimBlueprint* AnimBP = CastChecked<UAnimBlueprint>(Blueprint);
|
|
|
|
if(AnimBP->TargetSkeleton && !AnimBP->TargetSkeleton->BlendProfiles.Contains(BlendProfile))
|
|
{
|
|
BlendProfile = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void UAnimStateTransitionNode::PostPasteNode()
|
|
{
|
|
if (bSharedRules)
|
|
{
|
|
FAnimStateTransitionNodeSharedRulesHelper().UpdateSharedData(this, MakeShareable(new FAnimStateTransitionNodeSharedRulesNameValidator(this)));
|
|
}
|
|
|
|
if (bSharedCrossfade)
|
|
{
|
|
FAnimStateTransitionNodeSharedCrossfadeHelper().UpdateSharedData(this, MakeShareable(new FAnimStateTransitionNodeSharedCrossfadeNameValidator(this)));
|
|
}
|
|
|
|
if (BoundGraph == NULL)
|
|
{
|
|
// fail-safe, create empty transition graph
|
|
CreateBoundGraph();
|
|
}
|
|
|
|
for (UEdGraphNode* GraphNode : BoundGraph->Nodes)
|
|
{
|
|
GraphNode->CreateNewGuid();
|
|
GraphNode->PostPasteNode();
|
|
GraphNode->ReconstructNode();
|
|
}
|
|
|
|
if(CustomTransitionGraph)
|
|
{
|
|
// Needs to be added to the parent graph
|
|
UEdGraph* ParentGraph = GetGraph();
|
|
|
|
if(ParentGraph->SubGraphs.Find(CustomTransitionGraph) == INDEX_NONE)
|
|
{
|
|
ParentGraph->SubGraphs.Add(CustomTransitionGraph);
|
|
}
|
|
|
|
// Transactional flag is lost in copy/paste, restore it.
|
|
CustomTransitionGraph->SetFlags(RF_Transactional);
|
|
}
|
|
|
|
ValidateBlendProfile();
|
|
|
|
Super::PostPasteNode();
|
|
|
|
// We don't want to paste nodes in that aren't fully linked (transition nodes have fixed pins as they
|
|
// really describe the connection between two other nodes). If we find one missing link, get rid of the node.
|
|
for(UEdGraphPin* Pin : Pins)
|
|
{
|
|
if(Pin->LinkedTo.Num() == 0)
|
|
{
|
|
DestroyNode();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
FText UAnimStateTransitionNode::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
|
{
|
|
UAnimStateNodeBase* PrevState = GetPreviousState();
|
|
UAnimStateNodeBase* NextState = GetNextState();
|
|
|
|
if (!SharedRulesName.IsEmpty())
|
|
{
|
|
return FText::FromString(SharedRulesName);
|
|
}
|
|
else if ((PrevState != NULL) && (NextState != NULL))
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("PrevState"), FText::FromString(PrevState->GetStateName()));
|
|
Args.Add(TEXT("NextState"), FText::FromString(NextState->GetStateName()));
|
|
|
|
return FText::Format(LOCTEXT("PrevStateToNewState", "{PrevState} to {NextState}"), Args);
|
|
}
|
|
else
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("BoundGraph"), (BoundGraph != NULL) ? FText::FromString(BoundGraph->GetName()) : LOCTEXT("Null", "(null)") );
|
|
// @TODO: FText::Format() is slow, and we could benefit from caching
|
|
// this off like we do for a lot of other nodes (but we have to
|
|
// make sure to invalidate the cached string at the appropriate
|
|
// times).
|
|
return FText::Format(LOCTEXT("TransitioNState", "Trans {BoundGraph}}"), Args);
|
|
}
|
|
}
|
|
|
|
FText UAnimStateTransitionNode::GetTooltipText() const
|
|
{
|
|
return LOCTEXT("StateTransitionTooltip", "This is a state transition");
|
|
}
|
|
|
|
UAnimStateNodeBase* UAnimStateTransitionNode::GetPreviousState() const
|
|
{
|
|
if (Pins[0]->LinkedTo.Num() > 0)
|
|
{
|
|
return Cast<UAnimStateNodeBase>(Pins[0]->LinkedTo[0]->GetOwningNode());
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
UAnimStateNodeBase* UAnimStateTransitionNode::GetNextState() const
|
|
{
|
|
if (Pins[1]->LinkedTo.Num() > 0)
|
|
{
|
|
return Cast<UAnimStateNodeBase>(Pins[1]->LinkedTo[0]->GetOwningNode());
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
FLinearColor UAnimStateTransitionNode::GetNodeTitleColor() const
|
|
{
|
|
return FColorList::Red;
|
|
}
|
|
|
|
void UAnimStateTransitionNode::PinConnectionListChanged(UEdGraphPin* Pin)
|
|
{
|
|
if (Pin->LinkedTo.Num() == 0)
|
|
{
|
|
// Commit suicide; 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 UAnimStateTransitionNode::CreateConnections(UAnimStateNodeBase* PreviousState, UAnimStateNodeBase* NextState)
|
|
{
|
|
// Previous to this
|
|
Pins[0]->Modify();
|
|
Pins[0]->LinkedTo.Empty();
|
|
|
|
PreviousState->GetOutputPin()->Modify();
|
|
Pins[0]->MakeLinkTo(PreviousState->GetOutputPin());
|
|
|
|
// This to next
|
|
Pins[1]->Modify();
|
|
Pins[1]->LinkedTo.Empty();
|
|
|
|
NextState->GetInputPin()->Modify();
|
|
Pins[1]->MakeLinkTo(NextState->GetInputPin());
|
|
}
|
|
|
|
void UAnimStateTransitionNode::PrepareForCopying()
|
|
{
|
|
Super::PrepareForCopying();
|
|
// move bound graph node here, so during copying it will be referenced
|
|
// for shared nodes at least one of them has to be referencing it, so we will be fine
|
|
BoundGraph->Rename(NULL, this, REN_DoNotDirty | REN_DontCreateRedirectors);
|
|
}
|
|
|
|
void UAnimStateTransitionNode::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
FName PropertyName = (PropertyChangedEvent.Property != NULL) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
|
|
|
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, CrossfadeDuration) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, BlendMode) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, CustomBlendCurve) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, BlendProfile) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(UAnimStateTransitionNode, BlendProfileMode))
|
|
{
|
|
PropagateCrossfadeSettings();
|
|
}
|
|
|
|
if (PropertyName == FName(TEXT("LogicType")) )
|
|
{
|
|
if ((LogicType == ETransitionLogicType::TLT_Custom) && (CustomTransitionGraph == NULL))
|
|
{
|
|
CreateCustomTransitionGraph();
|
|
}
|
|
else if (CustomTransitionGraph != NULL)
|
|
{
|
|
// UAnimationCustomTransitionSchema::HandleGraphBeingDeleted resets logic type, so we'll need to restore it after RemoveGraph
|
|
const TEnumAsByte<ETransitionLogicType::Type> DesiredLogicType = LogicType;
|
|
|
|
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(this);
|
|
FBlueprintEditorUtils::RemoveGraph(Blueprint, CustomTransitionGraph);
|
|
CustomTransitionGraph = NULL;
|
|
|
|
LogicType = DesiredLogicType;
|
|
}
|
|
}
|
|
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
|
|
FString UAnimStateTransitionNode::GetStateName() const
|
|
{
|
|
return (BoundGraph != NULL) ? *(BoundGraph->GetName()) : TEXT("(null)");
|
|
}
|
|
|
|
void UAnimStateTransitionNode::MakeRulesShareable(FString ShareName)
|
|
{
|
|
bSharedRules = true;
|
|
SharedRulesName = ShareName;
|
|
SharedRulesGuid = FGuid::NewGuid();
|
|
}
|
|
|
|
void UAnimStateTransitionNode::MakeCrossfadeShareable(FString ShareName)
|
|
{
|
|
// Give us a unique idx. This remaps every SharedCrossfadeIdx in the graph (in case some were deleted)
|
|
UEdGraph* CurrentGraph = GetGraph();
|
|
|
|
SharedCrossfadeIdx = INDEX_NONE;
|
|
TArray<int32> Remap;
|
|
for (int32 idx=0; idx < CurrentGraph->Nodes.Num(); idx++)
|
|
{
|
|
if (UAnimStateTransitionNode* Node = Cast<UAnimStateTransitionNode>(CurrentGraph->Nodes[idx]))
|
|
{
|
|
if (Node->SharedCrossfadeIdx != INDEX_NONE || Node == this)
|
|
{
|
|
Node->SharedCrossfadeIdx = Remap.AddUnique(Node->SharedCrossfadeIdx)+1; // Remaps existing index to lowest index available
|
|
}
|
|
}
|
|
}
|
|
|
|
bSharedCrossfade = true;
|
|
SharedCrossfadeName = ShareName;
|
|
SharedCrossfadeGuid = FGuid::NewGuid();
|
|
}
|
|
|
|
void UAnimStateTransitionNode::UnshareRules()
|
|
{
|
|
bSharedRules = false;
|
|
SharedRulesName.Empty();
|
|
SharedRulesGuid.Invalidate();
|
|
|
|
if ((BoundGraph == NULL) || IsBoundGraphShared())
|
|
{
|
|
BoundGraph = NULL;
|
|
CreateBoundGraph();
|
|
}
|
|
}
|
|
|
|
void UAnimStateTransitionNode::UnshareCrossade()
|
|
{
|
|
bSharedCrossfade = false;
|
|
SharedCrossfadeIdx = INDEX_NONE;
|
|
SharedCrossfadeName.Empty();
|
|
SharedCrossfadeGuid.Invalidate();
|
|
}
|
|
|
|
void UAnimStateTransitionNode::UseSharedRules(const UAnimStateTransitionNode* Node)
|
|
{
|
|
if(Node == this || Node == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FScopedTransaction Transaction(LOCTEXT("UseSharedRules", "Use Shared Rules"));
|
|
|
|
Modify();
|
|
|
|
UEdGraph* CurrentGraph = GetGraph();
|
|
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(CurrentGraph);
|
|
|
|
UEdGraph* GraphToDelete = NULL;
|
|
if ((BoundGraph != NULL) && !IsBoundGraphShared())
|
|
{
|
|
GraphToDelete = BoundGraph;
|
|
}
|
|
|
|
BoundGraph = Node->BoundGraph;
|
|
bSharedRules = Node->bSharedRules;
|
|
SharedRulesName = Node->SharedRulesName;
|
|
SharedColor = Node->SharedColor;
|
|
SharedRulesGuid = Node->SharedRulesGuid;
|
|
|
|
if (GraphToDelete != NULL)
|
|
{
|
|
FBlueprintEditorUtils::RemoveGraph(Blueprint, GraphToDelete);
|
|
}
|
|
|
|
// If this node has shared crossfade settings, and we currently dont... share with it automatically.
|
|
// We'll see if this is actually helpful or just confusing. I think it might be a common operation
|
|
// and this avoid having to manually select to share the rules and then share the crossfade settings.
|
|
if ((SharedCrossfadeIdx == INDEX_NONE) && (Node->SharedCrossfadeIdx != INDEX_NONE))
|
|
{
|
|
UseSharedCrossfade(Node);
|
|
}
|
|
}
|
|
|
|
void UAnimStateTransitionNode::UseSharedCrossfade(const UAnimStateTransitionNode* Node)
|
|
{
|
|
if(Node == this || Node == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FScopedTransaction Transaction(LOCTEXT("UseSharedCrossfade", "Use Shared Crossfade"));
|
|
|
|
Modify();
|
|
|
|
bSharedCrossfade = Node->bSharedCrossfade;
|
|
SharedCrossfadeName = Node->SharedCrossfadeName;
|
|
SharedCrossfadeGuid = Node->SharedCrossfadeGuid;
|
|
CopyCrossfadeSettings(Node);
|
|
}
|
|
|
|
void UAnimStateTransitionNode::CopyCrossfadeSettings(const UAnimStateTransitionNode* SrcNode)
|
|
{
|
|
CrossfadeDuration = SrcNode->CrossfadeDuration;
|
|
CrossfadeMode_DEPRECATED = SrcNode->CrossfadeMode_DEPRECATED;
|
|
BlendMode = SrcNode->BlendMode;
|
|
CustomBlendCurve = SrcNode->CustomBlendCurve;
|
|
BlendProfile = SrcNode->BlendProfile;
|
|
BlendProfileMode = SrcNode->BlendProfileMode;
|
|
SharedCrossfadeIdx = SrcNode->SharedCrossfadeIdx;
|
|
SharedCrossfadeName = SrcNode->SharedCrossfadeName;
|
|
SharedCrossfadeGuid = SrcNode->SharedCrossfadeGuid;
|
|
}
|
|
|
|
void UAnimStateTransitionNode::PropagateCrossfadeSettings()
|
|
{
|
|
UEdGraph* CurrentGraph = GetGraph();
|
|
for (int32 idx = 0; idx < CurrentGraph->Nodes.Num(); idx++)
|
|
{
|
|
if (UAnimStateTransitionNode* Node = Cast<UAnimStateTransitionNode>(CurrentGraph->Nodes[idx]))
|
|
{
|
|
if (Node->SharedCrossfadeIdx != INDEX_NONE && Node->SharedCrossfadeGuid == SharedCrossfadeGuid)
|
|
{
|
|
Node->Modify();
|
|
Node->CopyCrossfadeSettings(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UAnimStateTransitionNode::IsReverseTrans(const UAnimStateNodeBase* Node)
|
|
{
|
|
return (Bidirectional && GetNextState() == Node);
|
|
}
|
|
|
|
void UAnimStateTransitionNode::CreateBoundGraph()
|
|
{
|
|
// Create a new animation graph
|
|
check(BoundGraph == NULL);
|
|
BoundGraph = FBlueprintEditorUtils::CreateNewGraph(this, NAME_None, UAnimationTransitionGraph::StaticClass(), UAnimationTransitionSchema::StaticClass());
|
|
check(BoundGraph);
|
|
|
|
// Find an interesting name
|
|
FEdGraphUtilities::RenameGraphToNameOrCloseToName(BoundGraph, TEXT("Transition"));
|
|
|
|
// Initialize the anim graph
|
|
const UEdGraphSchema* Schema = BoundGraph->GetSchema();
|
|
Schema->CreateDefaultNodesForGraph(*BoundGraph);
|
|
|
|
// Add the new graph as a child of our parent graph
|
|
UEdGraph* ParentGraph = GetGraph();
|
|
|
|
if(ParentGraph->SubGraphs.Find(BoundGraph) == INDEX_NONE)
|
|
{
|
|
ParentGraph->SubGraphs.Add(BoundGraph);
|
|
}
|
|
}
|
|
|
|
void UAnimStateTransitionNode::CreateCustomTransitionGraph()
|
|
{
|
|
// Create a new animation graph
|
|
check(CustomTransitionGraph == NULL);
|
|
CustomTransitionGraph = FBlueprintEditorUtils::CreateNewGraph(
|
|
this,
|
|
NAME_None,
|
|
UAnimationCustomTransitionGraph::StaticClass(),
|
|
UAnimationCustomTransitionSchema::StaticClass());
|
|
check(CustomTransitionGraph);
|
|
|
|
// Find an interesting name
|
|
FEdGraphUtilities::RenameGraphToNameOrCloseToName(CustomTransitionGraph, TEXT("CustomTransition"));
|
|
|
|
// Initialize the anim graph
|
|
const UEdGraphSchema* Schema = CustomTransitionGraph->GetSchema();
|
|
Schema->CreateDefaultNodesForGraph(*CustomTransitionGraph);
|
|
|
|
// Add the new graph as a child of our parent graph
|
|
UEdGraph* ParentGraph = GetGraph();
|
|
|
|
if(ParentGraph->SubGraphs.Find(CustomTransitionGraph) == INDEX_NONE)
|
|
{
|
|
ParentGraph->Modify();
|
|
ParentGraph->SubGraphs.Add(CustomTransitionGraph);
|
|
}
|
|
}
|
|
|
|
void UAnimStateTransitionNode::Serialize(FArchive& Ar)
|
|
{
|
|
Super::Serialize(Ar);
|
|
Ar.UsingCustomVersion(FAnimPhysObjectVersion::GUID);
|
|
Ar.UsingCustomVersion(FUE5MainStreamObjectVersion::GUID);
|
|
|
|
// Make sure we use the old behavior on Blend Profiles for older files.
|
|
if (Ar.IsLoading() && Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) < FUE5MainStreamObjectVersion::AnimationAddedBlendProfileModes)
|
|
{
|
|
BlendProfileMode = EBlendProfileMode::WeightFactor;
|
|
}
|
|
}
|
|
|
|
void UAnimStateTransitionNode::DestroyNode()
|
|
{
|
|
// BoundGraph may be shared with another graph, if so, don't remove it here
|
|
UEdGraph* GraphToRemove = IsBoundGraphShared() ? NULL : GetBoundGraph();
|
|
|
|
BoundGraph = NULL;
|
|
Super::DestroyNode();
|
|
|
|
if (GraphToRemove)
|
|
{
|
|
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(this);
|
|
FBlueprintEditorUtils::RemoveGraph(Blueprint, GraphToRemove, EGraphRemoveFlags::Recompile);
|
|
}
|
|
|
|
if (CustomTransitionGraph)
|
|
{
|
|
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(this);
|
|
FBlueprintEditorUtils::RemoveGraph(Blueprint, CustomTransitionGraph, EGraphRemoveFlags::Recompile);
|
|
}
|
|
}
|
|
|
|
/** Returns true if this nodes BoundGraph is shared with another node in the parent graph */
|
|
bool UAnimStateTransitionNode::IsBoundGraphShared()
|
|
{
|
|
if (BoundGraph)
|
|
{
|
|
//@TODO: O(N) search
|
|
UEdGraph* ParentGraph = GetGraph();
|
|
for (int32 NodeIdx = 0; NodeIdx < ParentGraph->Nodes.Num(); NodeIdx++)
|
|
{
|
|
UAnimStateNodeBase* AnimNode = Cast<UAnimStateNodeBase>(ParentGraph->Nodes[NodeIdx]);
|
|
if ((AnimNode != NULL) && (AnimNode != this) && (AnimNode->GetBoundGraph() == BoundGraph))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void UAnimStateTransitionNode::ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const
|
|
{
|
|
Super::ValidateNodeDuringCompilation(MessageLog);
|
|
|
|
if (UAnimationTransitionGraph* TransGraph = Cast<UAnimationTransitionGraph>(BoundGraph))
|
|
{
|
|
UAnimGraphNode_TransitionResult* ResultNode = TransGraph->GetResultNode();
|
|
check(ResultNode);
|
|
|
|
if(ResultNode->Pins.Num() > 0)
|
|
{
|
|
UEdGraphPin* BoolResultPin = ResultNode->Pins[0];
|
|
if (BoolResultPin && (BoolResultPin->LinkedTo.Num() == 0) && (BoolResultPin->DefaultValue.ToBool() == false))
|
|
{
|
|
// check for native transition rule before warning
|
|
bool bHasNative = false;
|
|
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(this);
|
|
if(Blueprint && Blueprint->ParentClass)
|
|
{
|
|
UAnimInstance* AnimInstance = CastChecked<UAnimInstance>(Blueprint->ParentClass->GetDefaultObject());
|
|
if(AnimInstance)
|
|
{
|
|
UEdGraph* ParentGraph = GetGraph();
|
|
UAnimStateNodeBase* PrevState = GetPreviousState();
|
|
UAnimStateNodeBase* NextState = GetNextState();
|
|
if(PrevState != nullptr && NextState != nullptr && ParentGraph != nullptr)
|
|
{
|
|
FName FunctionName;
|
|
bHasNative = AnimInstance->HasNativeTransitionBinding(ParentGraph->GetFName(), FName(*PrevState->GetStateName()), FName(*NextState->GetStateName()), FunctionName);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bHasNative && !bAutomaticRuleBasedOnSequencePlayerInState)
|
|
{
|
|
MessageLog.Warning(TEXT("@@ will never be taken, please connect something to @@"), this, BoolResultPin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MessageLog.Error(TEXT("@@ contains an invalid or NULL BoundGraph. Please delete and recreate the transition."), this);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// IAnimStateTransitionNodeSharedDataHelper
|
|
|
|
void IAnimStateTransitionNodeSharedDataHelper::UpdateSharedData(UAnimStateTransitionNode* Node, TSharedPtr<INameValidatorInterface> NameValidator)
|
|
{
|
|
// get all other transition nodes
|
|
TArray<UAnimStateTransitionNode*> TransitionNodes;
|
|
|
|
UEdGraph* ParentGraph = Node->GetGraph();
|
|
ParentGraph->GetNodesOfClass(TransitionNodes);
|
|
|
|
// check if there is other node that can provide us with data
|
|
for (TArray<UAnimStateTransitionNode*>::TIterator It(TransitionNodes); It; ++ It)
|
|
{
|
|
UAnimStateTransitionNode* OtherNode = *It;
|
|
if (OtherNode != Node &&
|
|
CheckIfHasDataToShare(OtherNode) &&
|
|
CheckIfNodesShouldShareData(Node, OtherNode))
|
|
{
|
|
// use shared data of that node (to make sure everything is linked up properly)
|
|
ShareData(Node, OtherNode);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check if our shared rule name is original
|
|
if (NameValidator->FindValidString(AccessShareDataName(Node)) != EValidatorResult::Ok)
|
|
{
|
|
// rename all shared rules name in nodes that should share same name
|
|
for (TArray<UAnimStateTransitionNode*>::TIterator It(TransitionNodes); It; ++ It)
|
|
{
|
|
UAnimStateTransitionNode* OtherNode = *It;
|
|
if (OtherNode != Node &&
|
|
CheckIfNodesShouldShareData(Node, OtherNode))
|
|
{
|
|
AccessShareDataName(OtherNode) = AccessShareDataName(Node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void IAnimStateTransitionNodeSharedDataHelper::MakeSureGuidExists(UAnimStateTransitionNode* Node)
|
|
{
|
|
UEdGraph* CurrentGraph = Node->GetGraph();
|
|
for (int32 idx=0; idx < CurrentGraph->Nodes.Num(); idx++)
|
|
{
|
|
if (UAnimStateTransitionNode* OtherNode = Cast<UAnimStateTransitionNode>(CurrentGraph->Nodes[idx]))
|
|
{
|
|
if (OtherNode != Node &&
|
|
CheckIfNodesShouldShareData(Node, OtherNode))
|
|
{
|
|
AccessShareDataName(Node) = AccessShareDataName(OtherNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! AccessShareDataGuid(Node).IsValid())
|
|
{
|
|
AccessShareDataGuid(Node) = FGuid::NewGuid();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FAnimStateTransitionNodeSharedRulesHelper
|
|
|
|
bool FAnimStateTransitionNodeSharedRulesHelper::CheckIfNodesShouldShareData(const UAnimStateTransitionNode* NodeA, const UAnimStateTransitionNode* NodeB)
|
|
{
|
|
return NodeA->bSharedRules && NodeB->bSharedRules && NodeA->SharedRulesGuid == NodeB->SharedRulesGuid;
|
|
}
|
|
|
|
bool FAnimStateTransitionNodeSharedRulesHelper::CheckIfHasDataToShare(const UAnimStateTransitionNode* Node)
|
|
{
|
|
return Node->BoundGraph != NULL;
|
|
}
|
|
|
|
void FAnimStateTransitionNodeSharedRulesHelper::ShareData(UAnimStateTransitionNode* NodeWhoWantsToShare, const UAnimStateTransitionNode* ShareFrom)
|
|
{
|
|
NodeWhoWantsToShare->UseSharedRules(ShareFrom);
|
|
}
|
|
|
|
FString& FAnimStateTransitionNodeSharedRulesHelper::AccessShareDataName(UAnimStateTransitionNode* Node)
|
|
{
|
|
return Node->SharedRulesName;
|
|
}
|
|
|
|
FGuid& FAnimStateTransitionNodeSharedRulesHelper::AccessShareDataGuid(UAnimStateTransitionNode* Node)
|
|
{
|
|
return Node->SharedRulesGuid;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FAnimStateTransitionNodeSharedCrossfadeHelper
|
|
|
|
bool FAnimStateTransitionNodeSharedCrossfadeHelper::CheckIfNodesShouldShareData(const UAnimStateTransitionNode* NodeA, const UAnimStateTransitionNode* NodeB)
|
|
{
|
|
return NodeA->bSharedCrossfade && NodeB->bSharedCrossfade && NodeA->SharedCrossfadeGuid == NodeB->SharedCrossfadeGuid;
|
|
}
|
|
|
|
bool FAnimStateTransitionNodeSharedCrossfadeHelper::CheckIfHasDataToShare(const UAnimStateTransitionNode* Node)
|
|
{
|
|
return Node->SharedCrossfadeIdx != INDEX_NONE;
|
|
}
|
|
|
|
void FAnimStateTransitionNodeSharedCrossfadeHelper::ShareData(UAnimStateTransitionNode* NodeWhoWantsToShare, const UAnimStateTransitionNode* ShareFrom)
|
|
{
|
|
NodeWhoWantsToShare->UseSharedCrossfade(ShareFrom);
|
|
}
|
|
|
|
FString& FAnimStateTransitionNodeSharedCrossfadeHelper::AccessShareDataName(UAnimStateTransitionNode* Node)
|
|
{
|
|
return Node->SharedCrossfadeName;
|
|
}
|
|
|
|
FGuid& FAnimStateTransitionNodeSharedCrossfadeHelper::AccessShareDataGuid(UAnimStateTransitionNode* Node)
|
|
{
|
|
return Node->SharedCrossfadeGuid;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|