Compare commits

...

12 Commits

Author SHA1 Message Date
Jamie Greunbaum
26b506eb0e Merge pull request 'Created the necessary changes for Unreal Engine 5.4' (#1) from update-to-5.4 into main
Reviewed-on: BattyBovine/ComboInput#1
2024-06-28 17:28:10 -04:00
Jamie Greunbaum
67c0126322 Created the necessary changes for Unreal Engine 5.4 2024-06-28 17:26:45 -04:00
Jamie Greunbaum
a9a841911a Fixed a bug that can cause buttons not mapped to combo inputs to block later combo inputs when they aren't part of a multi-press. 2023-10-11 01:20:29 -04:00
Jamie Greunbaum
b77a9bd2ed - Release triggers only fire if one is available in the current node's children.
- Crashes related to PreviousNode occasionally becoming null have been fixed.
2023-10-07 19:41:23 -04:00
Jamie Greunbaum
08b89b9d84 Fixed some grammar issues with the variable names that were annoying me. 2023-10-07 18:58:26 -04:00
Jamie Greunbaum
b7d299f8b1 Display a validation window when validating a graph, and also a message stating that auto-arrange does not currently work. 2023-10-05 13:13:37 -04:00
Jamie Greunbaum
6261a7eddc Added a popup class for graph validation. 2023-10-05 13:13:03 -04:00
Jamie Greunbaum
9dec4a1c91 Disabled verification on save. 2023-10-04 23:32:07 -04:00
Jamie Greunbaum
0feb21fefc Components were reworked to no longer need a separate list of valid inputs to listen for. 2023-10-04 18:52:53 -04:00
Jamie Greunbaum
3d0fedabf6 Combo graph code now compiles and functions properly in shipping builds. 2023-10-04 13:05:53 -04:00
Jamie Greunbaum
c50c9c6bf7 Components now use InitializeComponent instead of BeginPlay. 2023-10-04 13:05:15 -04:00
Jamie Greunbaum
13172b3b60 - ComboManagerComponent accepts a list of actions to unlock.
- Removed more unused functions.
2023-10-03 22:52:48 -04:00
27 changed files with 457 additions and 192 deletions

View File

@ -6,7 +6,7 @@
"Description": "A set of components and classes for capturing Enhanced Input actions for buffering actions and stringing them into complex combos.", "Description": "A set of components and classes for capturing Enhanced Input actions for buffering actions and stringing them into complex combos.",
"Category": "Input", "Category": "Input",
"CreatedBy": "Jamie Greunbaum", "CreatedBy": "Jamie Greunbaum",
"EngineVersion": "5.3.0", "EngineVersion": "5.4.0",
"CanContainContent": true, "CanContainContent": true,
"IsBetaVersion": true, "IsBetaVersion": true,
"IsExperimentalVersion": true, "IsExperimentalVersion": true,

View File

@ -26,24 +26,6 @@ UComboActionGraph::UComboActionGraph()
#endif #endif
} }
bool UComboActionGraph::CanStartDialogueGraph() const
{
if (this->AllNodes.Num() == 0)
{
return false;
}
for (const UComboActionGraphNode *Itr : this->AllNodes)
{
if (!Itr || !Itr->ValidateNodeRuntime())
{
return false;
}
}
return true;
}
void UComboActionGraph::CreateGraph() void UComboActionGraph::CreateGraph()
{ {
#if WITH_EDITOR #if WITH_EDITOR
@ -69,7 +51,7 @@ void UComboActionGraph::ClearGraph()
for (UComboActionGraphNode *Node : this->AllNodes) for (UComboActionGraphNode *Node : this->AllNodes)
{ {
Node->ParentNodes.Empty(); Node->ParentNodes.Empty();
Node->ChildrenNodes.Empty(); Node->ChildNodes.Empty();
Node->Edges.Empty(); Node->Edges.Empty();
} }
@ -124,11 +106,12 @@ bool UComboActionGraph::ValidateGraph(TArray<FText> &ValidationErrors, bool Rich
return bReturnValue; return bReturnValue;
} }
EDataValidationResult UComboActionGraph::IsDataValid(TArray<FText>& ValidationErrors) EDataValidationResult UComboActionGraph::IsDataValid(TArray<FText> &ValidationErrors)
{ {
return this->ValidateGraph(ValidationErrors, false) //return this->ValidateGraph(ValidationErrors, false)
? EDataValidationResult::Valid // ? EDataValidationResult::Valid
: EDataValidationResult::Invalid; // : EDataValidationResult::Invalid;
return EDataValidationResult::NotValidated;
} }
#endif #endif

View File

@ -19,14 +19,28 @@ UComboManagerComponent::UComboManagerComponent()
this->PrimaryComponentTick.bStartWithTickEnabled = false; this->PrimaryComponentTick.bStartWithTickEnabled = false;
this->PrimaryComponentTick.bTickEvenWhenPaused = false; this->PrimaryComponentTick.bTickEvenWhenPaused = false;
this->PrimaryComponentTick.bCanEverTick = false; this->PrimaryComponentTick.bCanEverTick = false;
this->bWantsInitializeComponent = true;
} }
void UComboManagerComponent::BeginPlay() void UComboManagerComponent::InitializeComponent()
{ {
Super::BeginPlay(); Super::InitializeComponent();
checkf(this->ComboGraph, TEXT("No combo graph is set for %s in actor %s"), *UComboManagerComponent::StaticClass()->GetName(), *this->GetOwner()->GetName()); checkf(this->ComboGraph, TEXT("No combo graph is set for %s in actor %s"), *UComboManagerComponent::StaticClass()->GetName(), *this->GetOwner()->GetName());
this->ActiveNode = this->ComboGraph->StartNode; this->PreviousNode = this->ActiveNode = this->ComboGraph->StartNode;
}
void UComboManagerComponent::SetComboGraph(const UComboActionGraph *Graph)
{
checkf(Graph, TEXT("Attempting to set a null combo graph."));
this->ComboGraph = Graph;
this->FoundInputsCache.Empty();
this->FindAllUsedInputs();
this->ResetCombo();
} }
@ -110,11 +124,40 @@ void UComboManagerComponent::DEBUG__UnlockAction(TObjectPtr<const UComboInputAss
void UComboManagerComponent::ReleaseComboAction(const class UComboInputAsset *Input) void UComboManagerComponent::ReleaseComboAction(const class UComboInputAsset *Input)
{ {
if (this->LastComboAction) if (this->LastComboAction && this->ActiveNode)
{ {
this->BroadcastDelegates(this->LastComboAction, EComboActionTriggerEvent::Released); // See if we have a fallback we can release.
this->LastComboAction = nullptr; const TObjectPtr<const UComboAction> *FallbackAction = this->FallbackActions.Find(Input);
if (FallbackAction && *FallbackAction == this->LastComboAction)
{
this->ResetCombo();
this->BroadcastDelegates(*FallbackAction, EComboActionTriggerEvent::Released);
UE_LOG(LogComboManagerComponent, Verbose, TEXT("Fallback action %s released"), *(*FallbackAction)->ActionName.ToString());
}
else
{
// Find a node that matches the release action.
const UComboAction *ComboAction = nullptr;
for (const UComboActionGraphNode *Node : this->ActiveNode->ChildNodes)
{
if (const UComboActionGraphNode_ActionNode *ActionNode = StaticCast<const UComboActionGraphNode_ActionNode *>(Node))
{
if (ActionNode->GetTriggerEvent() == EComboActionTriggerEvent::Released)
{
ComboAction = ActionNode->GetComboAction();
break;
}
}
}
if (ComboAction)
{
this->BroadcastDelegates(ComboAction, EComboActionTriggerEvent::Released);
UE_LOG(LogComboManagerComponent, Verbose, TEXT("%s released"), *ComboAction->ActionName.ToString());
}
}
} }
this->LastComboAction = nullptr;
} }
@ -135,25 +178,73 @@ void UComboManagerComponent::FinishTransition()
void UComboManagerComponent::ResetCombo() void UComboManagerComponent::ResetCombo()
{ {
this->GetWorld()->GetTimerManager().ClearTimer(this->DEBUG__ResetComboTimer); this->GetWorld()->GetTimerManager().ClearTimer(this->DEBUG__ResetComboTimer);
this->PreviousNode = this->ActiveNode; this->PreviousNode = this->ActiveNode = this->ComboGraph->StartNode;
this->ActiveNode = this->ComboGraph->StartNode;
APlayerController *Controller = UGameplayStatics::GetPlayerController(this, 0);
if (UInputBufferComponent *InputComponent = Cast<UInputBufferComponent>(Controller->InputComponent))
{
for (const UComboInputAsset *Unlock : this->FoundInputsCache)
{
InputComponent->UnlockComboInput(Unlock);
}
}
} }
TSet<const UComboInputAsset *> &UComboManagerComponent::FindAllUsedInputs()
{
if (this->FoundInputsCache.IsEmpty())
{
// First check the graph for inputs to respond to.
this->FindAllUsedInputs_RecurseGraph(this->ComboGraph->StartNode, this->FoundInputsCache);
// Next check the fallback actions for supplementary inputs to respond to.
TArray<TObjectPtr<const UComboInputAsset>> FallbackInputs;
this->FallbackActions.GetKeys(FallbackInputs);
for (const TObjectPtr<const UComboInputAsset> &FallbackInput : FallbackInputs)
{
this->FoundInputsCache.Add(FallbackInput);
}
// Finally check the offset action for the final input to respond to.
if (this->OffsetMap.ComboInput)
{
this->FoundInputsCache.Add(this->OffsetMap.ComboInput);
}
}
return this->FoundInputsCache;
}
void UComboManagerComponent::FindAllUsedInputs_RecurseGraph(const UComboActionGraphNode *CurrentNode, TSet<const UComboInputAsset *> &FoundInputs)
{
for (const UComboActionGraphNode *NextNode : CurrentNode->ChildNodes)
{
// It should be safe to assume all of our nodes are action nodes. If at some point
// this becomes less reliable, this should be changed. But just for the sake of
// saving a bit of performance on start-up, we're doing this right now.
checkf(NextNode->GetClass() == UComboActionGraphNode_ActionNode::StaticClass(), TEXT("This graph contains non-action nodes. The cast in this function will fail."));
FoundInputs.Add(StaticCast<const UComboActionGraphNode_ActionNode *>(NextNode)->GetComboInput());
this->FindAllUsedInputs_RecurseGraph(NextNode, FoundInputs);
}
}
const UComboActionGraphNode *UComboManagerComponent::FindActiveNodeData(const UComboActionGraphNode *CurrentNode, const UComboInputAsset *Input, const EComboActionTriggerEvent TriggerEvent, const UComboAction *&ComboAction) const UComboActionGraphNode *UComboManagerComponent::FindActiveNodeData(const UComboActionGraphNode *CurrentNode, const UComboInputAsset *Input, const EComboActionTriggerEvent TriggerEvent, const UComboAction *&ComboAction)
{ {
checkf(CurrentNode, TEXT("Attempting to find an active node from a null node."));
// Find a node that matches both the combo input and the trigger action. // Find a node that matches both the combo input and the trigger action.
const UComboActionGraphNode *NextNode = nullptr; const UComboActionGraphNode *NextNode = nullptr;
for (const UComboActionGraphNode *GraphNode : CurrentNode->ChildrenNodes) for (const UComboActionGraphNode *GraphNode : CurrentNode->ChildNodes)
{ {
if (const UComboActionGraphNode_ActionNode *ActionNode = Cast<UComboActionGraphNode_ActionNode>(GraphNode)) if (const UComboActionGraphNode_ActionNode *ActionNode = Cast<UComboActionGraphNode_ActionNode>(GraphNode))
{ {
if (ActionNode->GetComboInput() == Input && ActionNode->GetTriggerEvent() == EComboActionTriggerEvent::Activated) if (ActionNode->GetComboInput() == Input && ActionNode->GetTriggerEvent() == EComboActionTriggerEvent::Activated)
{ {
// If we found the right node, only acknowledge it if it's enabled. Otherwise just skip it. // If we found the right node, only acknowledge it if it's enabled.
if (ActionNode->bEnabled) const UComboAction *CheckAction = ActionNode->GetComboAction();
if (ActionNode->bEnabled || this->ComboGraph->UnlockedActions.Contains(CheckAction))
{ {
ComboAction = ActionNode->GetComboAction(); ComboAction = CheckAction;
NextNode = ActionNode; NextNode = ActionNode;
} }
break; break;

View File

@ -12,9 +12,14 @@
DEFINE_LOG_CATEGORY(LogInputBufferComponent); DEFINE_LOG_CATEGORY(LogInputBufferComponent);
void UInputBufferComponent::BeginPlay() UInputBufferComponent::UInputBufferComponent()
{ {
Super::BeginPlay(); this->bWantsInitializeComponent = true;
}
void UInputBufferComponent::InitializeComponent()
{
Super::InitializeComponent();
if (APlayerController *PlayerController = UGameplayStatics::GetPlayerController(this, 0)) if (APlayerController *PlayerController = UGameplayStatics::GetPlayerController(this, 0))
{ {
@ -23,14 +28,15 @@ void UInputBufferComponent::BeginPlay()
// Get the player character and try to connect to its combo manager. // Get the player character and try to connect to its combo manager.
this->OnNewComboInput.BindUObject(ComboManager, &UComboManagerComponent::HandleComboInput); this->OnNewComboInput.BindUObject(ComboManager, &UComboManagerComponent::HandleComboInput);
const TSet<const UComboInputAsset *> &ComboInputs = ComboManager->FindAllUsedInputs();
// Get all unique EnhancedInput actions bound to combo input actions. // Get all unique EnhancedInput actions bound to combo input actions.
const UInputBufferGlobalSettings *Settings = GetDefault<UInputBufferGlobalSettings>();
TSet<const UInputAction *> InputActionsToBind; TSet<const UInputAction *> InputActionsToBind;
for (TSoftObjectPtr<const UComboInputAsset> ComboInput : Settings->ComboInputs) for (const UComboInputAsset *ComboInput : ComboInputs)
{ {
if (ComboInput.IsValid()) if (ComboInput)
{ {
this->ComboInputList.Emplace(ComboInput.Get()); this->ComboInputList.Emplace(ComboInput);
for (const UInputAction *InputAction : ComboInput->ActionGroup) for (const UInputAction *InputAction : ComboInput->ActionGroup)
{ {
InputActionsToBind.Add(InputAction); InputActionsToBind.Add(InputAction);
@ -38,7 +44,7 @@ void UInputBufferComponent::BeginPlay()
} }
else else
{ {
UE_LOG(LogInputBufferComponent, Verbose, TEXT("Invalid combo action found in Combo Actions list in %s"), *Settings->GetClass()->GetName()); UE_LOG(LogInputBufferComponent, Verbose, TEXT("Invalid combo input found"));
} }
} }
for (const UInputAction *InputAction : InputActionsToBind) for (const UInputAction *InputAction : InputActionsToBind)
@ -53,20 +59,47 @@ void UInputBufferComponent::BeginPlay()
void UInputBufferComponent::AddActionToBuffer(const FInputActionValue &Value, const class UInputAction *Action) void UInputBufferComponent::AddActionToBuffer(const FInputActionValue &Value, const class UInputAction *Action)
{ {
this->MostRecentActions.Add(Action); this->MultiPressActions.Add(Action);
this->ExpiringActions.Remove(Action); this->ExpiringActions.Remove(Action);
// Find any combo input that matches this action, plus buffered actions. // Find any combo input that matches this action, plus buffered actions.
bool bComboInputFound = false;
for (TObjectPtr<const UComboInputAsset> ComboInput : this->ComboInputList) for (TObjectPtr<const UComboInputAsset> ComboInput : this->ComboInputList)
{ {
if (ComboInput->MatchesInputActions(this->MostRecentActions)) if (ComboInput->MatchesInputActions(this->MultiPressActions))
{ {
this->ActivateComboInput(ComboInput.Get()); this->ActivateComboInput(ComboInput.Get());
bComboInputFound = true;
break; break;
} }
} }
// If we haven't found any matching inputs, check if anything was somehow unhandled.
// This can happen if, for example, a jump input can combine with an attack input to
// create a combo action, but jump by itself has no action on its own.
if (!bComboInputFound)
{
if (!this->UnhandledActions.IsEmpty())
{
TSet<const UInputAction*> HandledActions = this->MultiPressActions;
for (const UInputAction *UnhandledAction : this->UnhandledActions)
{
HandledActions.Remove(UnhandledAction);
}
for (TObjectPtr<const UComboInputAsset> ComboInput : this->ComboInputList)
{
if (ComboInput->MatchesInputActions(HandledActions))
{
this->ActivateComboInput(ComboInput);
bComboInputFound = true;
break;
}
}
}
this->UnhandledActions.Add(Action);
}
const UInputBufferGlobalSettings *Settings = GetDefault<UInputBufferGlobalSettings>(); const UInputBufferGlobalSettings *Settings = GetDefault<UInputBufferGlobalSettings>();
this->GetWorld()->GetTimerManager().SetTimer(this->MultiPressTimerHandle, this, &UInputBufferComponent::ClearMultiPresses, Settings->MultiPressTimerLength); this->GetWorld()->GetTimerManager().SetTimer(this->MultiPressTimerHandle, this, &UInputBufferComponent::ClearMultiPresses, Settings->MultiPressTimerLength);
} }
@ -74,13 +107,14 @@ void UInputBufferComponent::ClearMultiPresses()
{ {
#if WITH_EDITOR #if WITH_EDITOR
TArray<FString> ActionNames; TArray<FString> ActionNames;
for (const UInputAction *Action : this->MostRecentActions) for (const UInputAction *Action : this->MultiPressActions)
{ {
ActionNames.Add(Action->GetName()); ActionNames.Add(Action->GetName());
} }
UE_LOG(LogInputBufferComponent, Verbose, TEXT("Multi-press buffer cleared (%s)"), *FString::Join(ActionNames, TEXT(" | "))); UE_LOG(LogInputBufferComponent, Verbose, TEXT("Multi-press buffer cleared (%s)"), *FString::Join(ActionNames, TEXT(" | ")));
#endif #endif
this->MostRecentActions.Empty(); this->MultiPressActions.Empty();
this->UnhandledActions.Empty();
} }
void UInputBufferComponent::ActivateComboInput(const UComboInputAsset *ComboInput) void UInputBufferComponent::ActivateComboInput(const UComboInputAsset *ComboInput)

View File

@ -15,11 +15,12 @@ UComboActionGraphNode::UComboActionGraphNode()
{ {
this->NodeGUID = FGuid::NewGuid(); this->NodeGUID = FGuid::NewGuid();
this->bEnabled = true;
#if WITH_EDITORONLY_DATA #if WITH_EDITORONLY_DATA
this->CompatibleGraphType = UComboActionGraph::StaticClass(); this->CompatibleGraphType = UComboActionGraph::StaticClass();
this->BackgroundColor = FLinearColor::Black; this->BackgroundColor = FLinearColor::Black;
this->bEnabled = true;
this->bAllowInputNodes = true; this->bAllowInputNodes = true;
this->bAllowOutputNodes = true; this->bAllowOutputNodes = true;
@ -37,10 +38,10 @@ UComboActionGraphNode::UComboActionGraphNode()
void UComboActionGraphNode::SetNewWorld(UWorld *NewWorld) void UComboActionGraphNode::SetNewWorld(UWorld *NewWorld)
{ {
if (!NewWorld) return; if (NewWorld)
if (NewWorld == this->OwningWorld) return; {
this->OwningWorld = NewWorld;
this->OwningWorld = NewWorld; }
} }
void UComboActionGraphNode::InitializeNode_Implementation(UWorld *InWorld) void UComboActionGraphNode::InitializeNode_Implementation(UWorld *InWorld)
@ -103,7 +104,7 @@ bool UComboActionGraphNode::CanCreateConnection(UComboActionGraphNode *Other, en
ErrorMessage = FText::FromString("Invalid Other Node!"); ErrorMessage = FText::FromString("Invalid Other Node!");
} }
if (Other->GetMaxChildNodes() > -1 && Other->ChildrenNodes.Num() >= Other->GetMaxChildNodes()) if (Other->GetMaxChildNodes() > -1 && Other->ChildNodes.Num() >= Other->GetMaxChildNodes())
{ {
const FString TextReturn = const FString TextReturn =
FString(Other->GetNodeTitle().ToString()). FString(Other->GetNodeTitle().ToString()).
@ -142,7 +143,7 @@ bool UComboActionGraphNode::ValidateNode(TArray<FText> &ValidationsMessages, con
{ {
bool bResult = true; bool bResult = true;
if (this->ParentNodes.Num() == 0 && this->ChildrenNodes.Num() == 0) if (this->ParentNodes.Num() == 0 && this->ChildNodes.Num() == 0)
{ {
bResult = false; bResult = false;
@ -186,7 +187,7 @@ void UComboActionGraphNode::OnPasted()
this->NodeGUID = FGuid::NewGuid(); this->NodeGUID = FGuid::NewGuid();
this->ParentNodes.Empty(); this->ParentNodes.Empty();
this->ChildrenNodes.Empty(); this->ChildNodes.Empty();
this->Edges.Empty(); this->Edges.Empty();
} }

View File

@ -19,7 +19,7 @@ UComboActionGraphNode_ActionNode::UComboActionGraphNode_ActionNode()
this->AllowedInputClasses.Add(UComboActionGraphNode_StartNode::StaticClass()); this->AllowedInputClasses.Add(UComboActionGraphNode_StartNode::StaticClass());
this->AllowedInputClasses.Add(UComboActionGraphNode_ActionNode::StaticClass()); this->AllowedInputClasses.Add(UComboActionGraphNode_ActionNode::StaticClass());
this->MaxChildrenNodes = -1; this->MaxChildNodes = -1;
} }
void UComboActionGraphNode_ActionNode::PreProcessNode(const TScriptInterface<IComboActionGraphManagerInterface> &Manager) void UComboActionGraphNode_ActionNode::PreProcessNode(const TScriptInterface<IComboActionGraphManagerInterface> &Manager)

View File

@ -45,7 +45,7 @@ bool UComboActionGraphNode_ActionNodeBase::ValidateNodeRuntime_Implementation()
return false; return false;
} }
if (this->MaxChildrenNodes > -1 && this->ChildrenNodes.Num() > this->MaxChildrenNodes) if (this->MaxChildNodes > -1 && this->ChildNodes.Num() > this->MaxChildNodes)
{ {
return false; return false;
} }
@ -113,7 +113,7 @@ bool UComboActionGraphNode_ActionNodeBase::ValidateNode(TArray<FText> &Validatio
ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn)); ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
} }
if (this->MaxChildrenNodes > -1 && this->ChildrenNodes.Num() > this->MaxChildrenNodes) if (this->MaxChildNodes > -1 && this->ChildNodes.Num() > this->MaxChildNodes)
{ {
const FString RichTextReturn = FString("* ") const FString RichTextReturn = FString("* ")
.Append("<RichTextBlock.Bold>") .Append("<RichTextBlock.Bold>")
@ -121,12 +121,12 @@ bool UComboActionGraphNode_ActionNodeBase::ValidateNode(TArray<FText> &Validatio
.Append("</>") .Append("</>")
.Append(": Has more than ") .Append(": Has more than ")
.Append("<RichTextBlock.Bold>") .Append("<RichTextBlock.Bold>")
.Append(FString::FromInt(this->MaxChildrenNodes)) .Append(FString::FromInt(this->MaxChildNodes))
.Append("</>") .Append("</>")
.Append(" child nodes!"); .Append(" child nodes!");
const FString TextReturn = FString(this->NodeTitle.ToString()) const FString TextReturn = FString(this->NodeTitle.ToString())
.Append(": Has more than ").Append(FString::FromInt(this->MaxChildrenNodes)).Append(" child nodes!"); .Append(": Has more than ").Append(FString::FromInt(this->MaxChildNodes)).Append(" child nodes!");
ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn)); ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
} }
@ -151,7 +151,8 @@ void UComboActionGraphNode_ActionNodeBase::PostEditChangeProperty(FPropertyChang
} }
} }
FLinearColor UComboActionGraphNode_ActionNodeBase::GetBackgroundColor() const #if WITH_EDITOR
FLinearColor UComboActionGraphNode_ActionNodeBase::GetBackgroundColour() const
{ {
if (this->ComboAction && this->ComboAction->NodeColor != FLinearColor()) if (this->ComboAction && this->ComboAction->NodeColor != FLinearColor())
{ {
@ -161,8 +162,9 @@ FLinearColor UComboActionGraphNode_ActionNodeBase::GetBackgroundColor() const
{ {
return this->ComboInput->NodeColor; return this->ComboInput->NodeColor;
} }
return Super::GetBackgroundColor(); return Super::GetBackgroundColour();
} }
#endif
FText UComboActionGraphNode_ActionNodeBase::GetDescription_Implementation() const FText UComboActionGraphNode_ActionNodeBase::GetDescription_Implementation() const
{ {

View File

@ -23,7 +23,7 @@ UComboActionGraphNode_StartNode::UComboActionGraphNode_StartNode()
this->NodeTooltipText = LOCTEXT("ComboActionGraphNode_CompleteTooltip", "* This Node will be added to the graph automatically.\n* This Node cannot be created manually.\n* This Node cannot be deleted.\n* Does not implement any logic."); this->NodeTooltipText = LOCTEXT("ComboActionGraphNode_CompleteTooltip", "* This Node will be added to the graph automatically.\n* This Node cannot be created manually.\n* This Node cannot be deleted.\n* Does not implement any logic.");
#endif #endif
this->MaxChildrenNodes = -1; this->MaxChildNodes = -1;
} }
#if WITH_EDITOR #if WITH_EDITOR
@ -37,7 +37,7 @@ bool UComboActionGraphNode_StartNode::ValidateNode(TArray<FText>& ValidationsMes
{ {
bool bResult = Super::ValidateNode(ValidationsMessages, RichFormat); bool bResult = Super::ValidateNode(ValidationsMessages, RichFormat);
if (ChildrenNodes.Num() == 0) if (this->ChildNodes.Num() == 0)
{ {
bResult = false; bResult = false;

View File

@ -43,17 +43,17 @@ public:
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
class UComboActionGraphNode *StartNode = nullptr; class UComboActionGraphNode *StartNode = nullptr;
/** /**
* The class of the dialogue node represented by this instance. * The class of the action node represented by this instance.
*/ */
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
TSubclassOf<UComboActionGraphNode> NodeType; TSubclassOf<UComboActionGraphNode> NodeType;
/** /**
* The class of the dialogue edge represented by this instance. * The class of the action edge represented by this instance.
*/ */
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
TSubclassOf<UComboActionGraphEdge> EdgeType; TSubclassOf<UComboActionGraphEdge> EdgeType;
/** /**
* An array of root nodes in the dialogue graph. These are the nodes that do not have any incoming connections. * An array of root nodes in the action graph. These are the nodes that do not have any incoming connections.
*/ */
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
TArray<UComboActionGraphNode*> RootNodes; TArray<UComboActionGraphNode*> RootNodes;
@ -62,6 +62,13 @@ public:
*/ */
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
TArray<UComboActionGraphNode*> AllNodes; TArray<UComboActionGraphNode*> AllNodes;
/**
* Set containing actions to be unlocked if their associated nodes are currently locked.
*/
UPROPERTY(BlueprintReadWrite, Category="Combo Input|Action")
TSet<const class UComboAction*> UnlockedActions;
// Flag indicating whether an edge is enabled // Flag indicating whether an edge is enabled
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action") UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
bool bEdgeEnabled; bool bEdgeEnabled;
@ -88,18 +95,12 @@ public:
UFUNCTION(BlueprintCallable, Category="Combo Input|Action") UFUNCTION(BlueprintCallable, Category="Combo Input|Action")
TArray<UComboActionGraphNode*> GetRootNodes() const { return this->RootNodes; } TArray<UComboActionGraphNode*> GetRootNodes() const { return this->RootNodes; }
/** /**
* Returns the root nodes of the dialogue graph. * Returns the first node in the graph.
* *
* @return An array of all root nodes. * @return The start node of this graph.
*/ */
UFUNCTION(BlueprintCallable, Category="Combo Input|Action") UFUNCTION(BlueprintCallable, Category="Combo Input|Action")
UComboActionGraphNode *GetStartNode() const { return this->StartNode; } UComboActionGraphNode *GetStartNode() const { return this->StartNode; }
/**
* Determines whether the dialogue graph can be started.
*
* @return true if the graph can be started, false otherwise.
*/
bool CanStartDialogueGraph() const;
public: public:
void CreateGraph(); void CreateGraph();
@ -115,7 +116,7 @@ public:
public: public:
UPROPERTY() UPROPERTY()
class UEdGraph *EdGraph; TObjectPtr<class UEdGraph> EdGraph;
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action|Editor") UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action|Editor")
bool bCanRenameNode; bool bCanRenameNode;

View File

@ -42,8 +42,10 @@ class COMBOINPUT_API UComboManagerComponent : public UActorComponent
public: public:
UComboManagerComponent(); UComboManagerComponent();
virtual void InitializeComponent() override;
virtual void BeginPlay() override; UFUNCTION(BlueprintCallable)
void SetComboGraph(const class UComboActionGraph *Graph);
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
void HandleComboInput(const class UComboInputAsset *Input, const EComboActionTriggerEvent &TriggerEvent); void HandleComboInput(const class UComboInputAsset *Input, const EComboActionTriggerEvent &TriggerEvent);
@ -56,6 +58,9 @@ public:
this->ComboActionEventBindings.Emplace(Key.Key, MoveTemp(Delegate)); this->ComboActionEventBindings.Emplace(Key.Key, MoveTemp(Delegate));
} }
// Recursively search the graph for all inputs used by the graph.
TSet<const UComboInputAsset *> &FindAllUsedInputs();
protected: protected:
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly) UPROPERTY(BlueprintReadOnly, EditDefaultsOnly)
TObjectPtr<const class UComboActionGraph> ComboGraph; TObjectPtr<const class UComboActionGraph> ComboGraph;
@ -87,6 +92,7 @@ private:
void ResetCombo(); void ResetCombo();
const UComboActionGraphNode *FindActiveNodeData(const UComboActionGraphNode *CurrentNode, const UComboInputAsset *Input, const EComboActionTriggerEvent TriggerEvent, const UComboAction *&ComboAction); const UComboActionGraphNode *FindActiveNodeData(const UComboActionGraphNode *CurrentNode, const UComboInputAsset *Input, const EComboActionTriggerEvent TriggerEvent, const UComboAction *&ComboAction);
void FindAllUsedInputs_RecurseGraph(const UComboActionGraphNode *CurrentNode, TSet<const UComboInputAsset *> &FoundInputs);
void BroadcastDelegates(const class UComboAction *ComboAction, const EComboActionTriggerEvent &TriggerEvent); void BroadcastDelegates(const class UComboAction *ComboAction, const EComboActionTriggerEvent &TriggerEvent);
@ -100,6 +106,9 @@ private:
TMap<FName, FComboActionHandlerDynamicSignature> ComboActionEventBindings; TMap<FName, FComboActionHandlerDynamicSignature> ComboActionEventBindings;
TObjectPtr<class UInputBufferComponent> AttachedInputBuffer; TObjectPtr<class UInputBufferComponent> AttachedInputBuffer;
// Cache of combo inputs found in the current graph.
TSet<const UComboInputAsset *> FoundInputsCache;
FTimerHandle FinishTransitionTimer; FTimerHandle FinishTransitionTimer;
FTimerHandle DEBUG__ResetComboTimer; FTimerHandle DEBUG__ResetComboTimer;

View File

@ -21,7 +21,8 @@ class COMBOINPUT_API UInputBufferComponent : public UEnhancedInputComponent
GENERATED_BODY() GENERATED_BODY()
public: public:
virtual void BeginPlay() override; UInputBufferComponent();
virtual void InitializeComponent() override;
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
void LockComboInput(const class UComboInputAsset *Input); void LockComboInput(const class UComboInputAsset *Input);
@ -52,7 +53,8 @@ private:
// A local backup of the global setting containing the list of combo inputs to respond to. // A local backup of the global setting containing the list of combo inputs to respond to.
TSet<TObjectPtr<const UComboInputAsset>> ComboInputList; TSet<TObjectPtr<const UComboInputAsset>> ComboInputList;
TSet<const class UInputAction*> MostRecentActions; TSet<const class UInputAction*> MultiPressActions;
TSet<const class UInputAction*> UnhandledActions;
TSet<const class UInputAction*> ExpiringActions; TSet<const class UInputAction*> ExpiringActions;
FNewComboInput OnNewComboInput; FNewComboInput OnNewComboInput;

View File

@ -16,12 +16,6 @@ class COMBOINPUT_API UInputBufferGlobalSettings : public UDeveloperSettingsBacke
GENERATED_BODY() GENERATED_BODY()
public: public:
// List of possible combo inputs that can be taken. A combo input is selected from this list
// either if an action is made while the current combo is inactive, or when the previous
// action expires.
UPROPERTY(Config, BlueprintReadOnly, EditDefaultsOnly)
TSet<TSoftObjectPtr<const class UComboInputAsset>> ComboInputs;
// Length of time after releasing an input to keep the associated combo action buffered before clearing it. // Length of time after releasing an input to keep the associated combo action buffered before clearing it.
UPROPERTY(Config, BlueprintReadOnly, EditDefaultsOnly, meta=(UIMin="0.0", UIMax="0.5", ClampMin="0.0", ClampMax="0.5")) UPROPERTY(Config, BlueprintReadOnly, EditDefaultsOnly, meta=(UIMin="0.0", UIMax="0.5", ClampMin="0.0", ClampMax="0.5"))
float InputReleaseExpirationTimerLength = 0.15f; float InputReleaseExpirationTimerLength = 0.15f;

View File

@ -32,13 +32,13 @@ public:
* Array of parent nodes for the current active node in the combo string. * Array of parent nodes for the current active node in the combo string.
* Parent nodes are nodes that have a directed edge pointing to the current active node. * Parent nodes are nodes that have a directed edge pointing to the current active node.
*/ */
UPROPERTY(BlueprintReadOnly, Category="Private") UPROPERTY(BlueprintReadOnly, Category = "Private")
TArray<class UComboActionGraphNode*> ParentNodes; TArray<class UComboActionGraphNode *> ParentNodes;
/** /**
* The array of child nodes of the current dialogue node. * The array of child nodes of the current dialogue node.
*/ */
UPROPERTY(BlueprintReadOnly, Category="Private") UPROPERTY(BlueprintReadOnly, Category = "Private")
TArray<class UComboActionGraphNode*> ChildrenNodes; TArray<class UComboActionGraphNode *> ChildNodes;
/** /**
* Map of edges connecting this Node in the Combo Action graph. * Map of edges connecting this Node in the Combo Action graph.
* The key of the map is the source node, and the value is the edge connecting it to its target node. * The key of the map is the source node, and the value is the edge connecting it to its target node.
@ -94,7 +94,7 @@ public:
* Can be used to enforce a maximum number of connections for certain types of nodes. * Can be used to enforce a maximum number of connections for certain types of nodes.
*/ */
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category="Combo Input|Action") UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category="Combo Input|Action")
int32 MaxChildrenNodes = -1; int32 MaxChildNodes = -1;
/** /**
* The array of allowed input classes for this Dialogue Node. * The array of allowed input classes for this Dialogue Node.
@ -126,10 +126,10 @@ public:
* Returns how many Children Nodes this Node allows to have. * Returns how many Children Nodes this Node allows to have.
* -1 means no limits. * -1 means no limits.
* *
* @return MaxChildrenNodes * @return Max child nodes
*/ */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action") UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
int32 GetMaxChildNodes() const { return MaxChildrenNodes; } int32 GetMaxChildNodes() const { return this->MaxChildNodes; }
/** /**
@ -163,23 +163,6 @@ public:
UFUNCTION(BlueprintCallable, Category = "Combo Input|Action") UFUNCTION(BlueprintCallable, Category = "Combo Input|Action")
class UComboActionGraph *GetGraph() const { return this->Graph; } class UComboActionGraph *GetGraph() const { return this->Graph; }
/**
* Gets children Nodes this one has,
* Might be empty
*
* @return Amount of children Nodes
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
FORCEINLINE TArray<UComboActionGraphNode*> GetChildrenNodes() const { return ChildrenNodes; }
/**
* Gets how many parent Nodes point to this one
* Might be empty
*
* @return Amount of how parent Nodes
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
FORCEINLINE TArray<UComboActionGraphNode*> GetParentNodes() const { return ParentNodes; }
/** /**
* Serves purpose of validating Node before Dialogue gets Started. * Serves purpose of validating Node before Dialogue gets Started.
@ -325,11 +308,11 @@ public:
virtual FString GetNodeDocumentationLink_Implementation() const; virtual FString GetNodeDocumentationLink_Implementation() const;
/** /**
* Returns the Background Colour for this graph node. * Returns the background colour for this graph node.
* *
* @return The Background Colour for this node. * @return The background colour for this node.
*/ */
virtual FLinearColor GetBackgroundColor() const { return this->BackgroundColor; } virtual FLinearColor GetBackgroundColour() const { return this->BackgroundColor; }
FText GetInternalName() const { return this->NodeTypeName; } FText GetInternalName() const { return this->NodeTypeName; }
// Allows setting up the Node Title // Allows setting up the Node Title

View File

@ -39,9 +39,9 @@ public:
virtual bool ValidateNodeRuntime_Implementation() const override; virtual bool ValidateNodeRuntime_Implementation() const override;
virtual FLinearColor GetBackgroundColor() const override; #if WITH_EDITOR
virtual FLinearColor GetBackgroundColour() const override;
public: #endif
#if WITH_EDITORONLY_DATA #if WITH_EDITORONLY_DATA

View File

@ -95,7 +95,7 @@ public:
virtual UEdGraphNode *PerformAction(class UEdGraph *ParentGraph, UEdGraphPin *FromPin, const FVector2D Location, bool bSelectNewNode = true) override; virtual UEdGraphNode *PerformAction(class UEdGraph *ParentGraph, UEdGraphPin *FromPin, const FVector2D Location, bool bSelectNewNode = true) override;
virtual void AddReferencedObjects(FReferenceCollector &Collector) override; virtual void AddReferencedObjects(FReferenceCollector &Collector) override;
class UEdComboActionGraphNode *NodeTemplate; TObjectPtr<class UEdComboActionGraphNode> NodeTemplate;
}; };
USTRUCT() USTRUCT()
@ -114,7 +114,7 @@ public:
virtual UEdGraphNode *PerformAction(class UEdGraph *ParentGraph, UEdGraphPin *FromPin, const FVector2D Location, bool bSelectNewNode = true) override; virtual UEdGraphNode *PerformAction(class UEdGraph *ParentGraph, UEdGraphPin *FromPin, const FVector2D Location, bool bSelectNewNode = true) override;
virtual void AddReferencedObjects(FReferenceCollector &Collector) override; virtual void AddReferencedObjects(FReferenceCollector &Collector) override;
class UEdComboActionGraphEdge *NodeTemplate; TObjectPtr<class UEdComboActionGraphEdge> NodeTemplate;
}; };

View File

@ -234,7 +234,6 @@ FComboInputSlateStyle::FComboInputSlateStyle() : FSlateStyleSet("ComboInputEdito
this->Set("MDSStyleSet.Node.Icon.small", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon12)); this->Set("MDSStyleSet.Node.Icon.small", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon12));
this->Set("MDSStyleSet.Icon.Close", new IMAGE_BRUSH(TEXT("CloseIcon"), Icon12)); this->Set("MDSStyleSet.Icon.Close", new IMAGE_BRUSH(TEXT("CloseIcon"), Icon12));
this->Set("MDSStyleSet.Icon.SupportDiscord", new IMAGE_BRUSH(TEXT("DiscordIcon"), Icon12));
this->Set("MDSStyleSet.Icon.HeartIcon", new IMAGE_BRUSH(TEXT("HeartIcon"), Icon12)); this->Set("MDSStyleSet.Icon.HeartIcon", new IMAGE_BRUSH(TEXT("HeartIcon"), Icon12));
this->Set("MDSStyleSet.Icon.UBIcon", new IMAGE_BRUSH(TEXT("UnrealBucketIcon"), Icon12)); this->Set("MDSStyleSet.Icon.UBIcon", new IMAGE_BRUSH(TEXT("UnrealBucketIcon"), Icon12));
this->Set("MDSStyleSet.Icon.MoneyIcon", new IMAGE_BRUSH(TEXT("MoneyIcon"), Icon12)); this->Set("MDSStyleSet.Icon.MoneyIcon", new IMAGE_BRUSH(TEXT("MoneyIcon"), Icon12));

View File

@ -2,28 +2,26 @@
#include "AssetEditor_ComboActionGraph.h" #include "AssetEditor_ComboActionGraph.h"
#include "GraphEditorActions.h"
#include "Framework/Commands/GenericCommands.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "HAL/PlatformApplicationMisc.h"
#include "EdGraphUtilities.h"
#include "ComboInputEditor.h"
#include "ComboActionGraph.h" #include "ComboActionGraph.h"
#include "Nodes/ComboActionGraphNode.h" #include "ComboActionGraphSchema.h"
#include "ComboInputEditor.h"
#include "EdGraphUtilities.h"
#include "GraphEditorActions.h"
#include "Ed/EdComboActionGraph.h" #include "Ed/EdComboActionGraph.h"
#include "Ed/EdComboActionGraphEdge.h" #include "Ed/EdComboActionGraphEdge.h"
#include "Ed/EdComboActionGraphNode.h" #include "Ed/EdComboActionGraphNode.h"
#include "Ed/FAssetEditorToolbarComboActionGraph.h" #include "Ed/FAssetEditorToolbarComboActionGraph.h"
#include "Ed/FComboActionGraphEditorCommands.h" #include "Ed/FComboActionGraphEditorCommands.h"
#include "ComboActionGraphSchema.h" #include "Framework/Commands/GenericCommands.h"
//#include "Helpers/ComboActionGraphHelpers.h" #include "HAL/PlatformApplicationMisc.h"
//#include "Helpers/ComboActionSystemEditorBFC.h" #include "Kismet2/BlueprintEditorUtils.h"
#include "Layout/AssetEditorTabs.h" #include "Layout/AssetEditorTabs.h"
#include "Layout/ComboActionGraphLayoutStrategy.h" #include "Layout/ComboActionGraphLayoutStrategy.h"
#include "Layout/ComboActionForceDirectedSolveLayoutStrategy.h" #include "Layout/ComboActionForceDirectedSolveLayoutStrategy.h"
#include "Layout/ComboActionTreeSolveLayoutStrategy.h" #include "Layout/ComboActionTreeSolveLayoutStrategy.h"
//#include "Popups/MDSPopup_GraphValidation.h" #include "Nodes/ComboActionGraphNode.h"
#include "Popups/ComboInputPopup_GraphValidation.h"
#include "Search/ComboActionSearchUtils.h" #include "Search/ComboActionSearchUtils.h"
#include "Search/SComboActionSearch.h" #include "Search/SComboActionSearch.h"
#include "Settings/ComboActionGraphEditorSettings.h" #include "Settings/ComboActionGraphEditorSettings.h"
@ -742,35 +740,41 @@ bool FAssetEditor_ComboActionGraph::CanPasteNodes() const
void FAssetEditor_ComboActionGraph::AutoArrange() void FAssetEditor_ComboActionGraph::AutoArrange()
{ {
UEdComboActionGraph *EdGraph = Cast<UEdComboActionGraph>(this->EditingGraph->EdGraph); // For now, auto-arrange is broken. Just in case it can be activated by accident,
check(EdGraph != nullptr); // show a popup saying as much if an auto-arrange is attempted.
TArray<FText> ValidationMessages;
ValidationMessages.Add(LOCTEXT("AssetEditor_ComboActionGraph_AutoArrangeDisabled", "Auto-arrange is currently broken."));
ComboInputPopup_GraphValidation::Open(ValidationMessages);
const FScopedTransaction Transaction(LOCTEXT("ComboActionGraphEditorAutoArrange", "Combo Action Graph Editor: Auto Arrange all Nodes")); //UEdComboActionGraph *EdGraph = Cast<UEdComboActionGraph>(this->EditingGraph->EdGraph);
//check(EdGraph != nullptr);
EdGraph->Modify(true); //const FScopedTransaction Transaction(LOCTEXT("ComboActionGraphEditorAutoArrange", "Combo Action Graph Editor: Auto Arrange all Nodes"));
UComboActionGraphLayoutStrategy *LayoutStrategy = nullptr; //EdGraph->Modify(true);
switch (this->ComboActionGraphEditorSettings->GetAutoLayoutStrategy())
{
case EComboActionAutoLayoutStrategyType::Tree:
LayoutStrategy = NewObject<UComboActionGraphLayoutStrategy>(EdGraph, UComboActionTreeSolveLayoutStrategy::StaticClass());
break;
case EComboActionAutoLayoutStrategyType::ForceDirected:
LayoutStrategy = NewObject<UComboActionGraphLayoutStrategy>(EdGraph, UComboActionForceDirectedSolveLayoutStrategy::StaticClass());
break;
default:
break;
}
if (LayoutStrategy != nullptr) //UComboActionGraphLayoutStrategy *LayoutStrategy = nullptr;
{ //switch (this->ComboActionGraphEditorSettings->GetAutoLayoutStrategy())
LayoutStrategy->Layout(EdGraph); //{
LayoutStrategy->ConditionalBeginDestroy(); // case EComboActionAutoLayoutStrategyType::Tree:
} // LayoutStrategy = NewObject<UComboActionGraphLayoutStrategy>(EdGraph, UComboActionTreeSolveLayoutStrategy::StaticClass());
else // break;
{ // case EComboActionAutoLayoutStrategyType::ForceDirected:
UE_LOG(LogAssetEditorComboActionGraph, Error, TEXT("[AutoArrange] LayoutStrategy is null.")); // LayoutStrategy = NewObject<UComboActionGraphLayoutStrategy>(EdGraph, UComboActionForceDirectedSolveLayoutStrategy::StaticClass());
} // break;
// default:
// break;
//}
//if (LayoutStrategy != nullptr)
//{
// LayoutStrategy->Layout(EdGraph);
// LayoutStrategy->ConditionalBeginDestroy();
//}
//else
//{
// UE_LOG(LogAssetEditorComboActionGraph, Error, TEXT("[AutoArrange] LayoutStrategy is null."));
//}
} }
bool FAssetEditor_ComboActionGraph::CanAutoArrange() const bool FAssetEditor_ComboActionGraph::CanAutoArrange() const
@ -798,12 +802,12 @@ void FAssetEditor_ComboActionGraph::ValidateGraph()
TArray<FText> ValidationMessages; TArray<FText> ValidationMessages;
if (ComboActionGraph->ValidateGraph(ValidationMessages, true) == false) if (ComboActionGraph->ValidateGraph(ValidationMessages, true) == false)
{ {
//ValidationWindow = MDSPopup_GraphValidation::Open(ValidationMessages); this->ValidationWindow = ComboInputPopup_GraphValidation::Open(ValidationMessages);
} }
else else
{ {
ValidationMessages.Empty(); ValidationMessages.Empty();
//ValidationWindow = MDSPopup_GraphValidation::Open(ValidationMessages); this->ValidationWindow = ComboInputPopup_GraphValidation::Open(ValidationMessages);
} }
} }

View File

@ -146,7 +146,7 @@ private:
class UComboActionGraphEditorSettings *ComboActionGraphEditorSettings; class UComboActionGraphEditorSettings *ComboActionGraphEditorSettings;
class UComboActionGraph *EditingGraph; TObjectPtr<class UComboActionGraph> EditingGraph;
//Toolbar //Toolbar
TSharedPtr<FAssetEditorToolbarComboActionGraph> ToolbarBuilder; TSharedPtr<FAssetEditorToolbarComboActionGraph> ToolbarBuilder;

View File

@ -63,7 +63,7 @@ void UEdComboActionGraph::RebuildComboActionGraph()
if (ChildNode != nullptr) if (ChildNode != nullptr)
{ {
ComboActionGraphNode->ChildrenNodes.Add(ChildNode); ComboActionGraphNode->ChildNodes.Add(ChildNode);
ChildNode->ParentNodes.Add(ComboActionGraphNode); ChildNode->ParentNodes.Add(ComboActionGraphNode);
} }
@ -168,7 +168,7 @@ void UEdComboActionGraph::Clear()
{ {
UComboActionGraphNode *MounteaDialogueGraphNode = EdNode->ComboActionGraphNode; UComboActionGraphNode *MounteaDialogueGraphNode = EdNode->ComboActionGraphNode;
MounteaDialogueGraphNode->ParentNodes.Reset(); MounteaDialogueGraphNode->ParentNodes.Reset();
MounteaDialogueGraphNode->ChildrenNodes.Reset(); MounteaDialogueGraphNode->ChildNodes.Reset();
MounteaDialogueGraphNode->Edges.Reset(); MounteaDialogueGraphNode->Edges.Reset();
} }
} }
@ -194,12 +194,12 @@ void UEdComboActionGraph::SortNodes(UComboActionGraphNode *RootNode)
return EdNode_LNode->NodePosX < EdNode_RNode->NodePosX; return EdNode_LNode->NodePosX < EdNode_RNode->NodePosX;
}; };
Node->ChildrenNodes.Sort(Comp); Node->ChildNodes.Sort(Comp);
Node->ParentNodes.Sort(Comp); Node->ParentNodes.Sort(Comp);
for (int j = 0; j < Node->ChildrenNodes.Num(); ++j) for (int j = 0; j < Node->ChildNodes.Num(); ++j)
{ {
NextLevelNodes.Add(Node->ChildrenNodes[j]); NextLevelNodes.Add(Node->ChildNodes[j]);
} }
} }

View File

@ -82,11 +82,6 @@ void UEdComboActionGraphNode::AutowireNewNode(UEdGraphPin* FromPin)
} }
} }
FLinearColor UEdComboActionGraphNode::GetBackgroundColor() const
{
return this->ComboActionGraphNode ? this->ComboActionGraphNode->GetBackgroundColor() : FLinearColor::Black;
}
UEdGraphPin* UEdComboActionGraphNode::GetInputPin() const UEdGraphPin* UEdComboActionGraphNode::GetInputPin() const
{ {
return Pins[0]; return Pins[0];
@ -151,12 +146,19 @@ FText UEdComboActionGraphNode::GetTooltipText() const
return NSLOCTEXT("UEdComboActionGraphNode", "DefaultToolTip", "Combo Action Node"); return NSLOCTEXT("UEdComboActionGraphNode", "DefaultToolTip", "Combo Action Node");
} }
#if WITH_EDITOR
FLinearColor UEdComboActionGraphNode::GetBackgroundColor() const
{
return this->ComboActionGraphNode ? this->ComboActionGraphNode->GetBackgroundColour() : FLinearColor::Black;
}
FSlateIcon UEdComboActionGraphNode::GetIconAndTint(FLinearColor& OutColor) const FSlateIcon UEdComboActionGraphNode::GetIconAndTint(FLinearColor& OutColor) const
{ {
static const FSlateIcon Icon = FSlateIcon(FComboInputSlateStyle::GetAppStyleSetName(), "MDSStyleSet.Node.Icon.small"); static const FSlateIcon Icon = FSlateIcon(FComboInputSlateStyle::GetAppStyleSetName(), "MDSStyleSet.Node.Icon.small");
OutColor = this->ComboActionGraphNode->GetBackgroundColor(); OutColor = this->ComboActionGraphNode->GetBackgroundColour();
return Icon; return Icon;
} }
#endif
void UEdComboActionGraphNode::PostEditUndo() void UEdComboActionGraphNode::PostEditUndo()
{ {

View File

@ -31,7 +31,6 @@ public:
virtual void PrepareForCopying() override; virtual void PrepareForCopying() override;
virtual void AutowireNewNode(UEdGraphPin *FromPin) override; virtual void AutowireNewNode(UEdGraphPin *FromPin) override;
virtual FLinearColor GetBackgroundColor() const;
virtual UEdGraphPin *GetInputPin() const; virtual UEdGraphPin *GetInputPin() const;
virtual UEdGraphPin *GetOutputPin() const; virtual UEdGraphPin *GetOutputPin() const;
@ -41,11 +40,13 @@ public:
virtual bool CanUserPasteNodes() const; virtual bool CanUserPasteNodes() const;
virtual FText GetTooltipText() const override; virtual FText GetTooltipText() const override;
virtual FSlateIcon GetIconAndTint(FLinearColor &OutColor) const override;
#if WITH_EDITOR #if WITH_EDITOR
virtual void PostEditUndo() override; virtual void PostEditUndo() override;
virtual void PostEditChangeProperty(FPropertyChangedEvent &PropertyChangedEvent) override; virtual void PostEditChangeProperty(FPropertyChangedEvent &PropertyChangedEvent) override;
virtual FLinearColor GetBackgroundColor() const;
virtual FSlateIcon GetIconAndTint(FLinearColor &OutColor) const override;
#endif #endif
UPROPERTY(VisibleAnywhere, Instanced, Category="Combo Action Graph") UPROPERTY(VisibleAnywhere, Instanced, Category="Combo Action Graph")

View File

@ -113,11 +113,11 @@ FBox2D UComboActionForceDirectedSolveLayoutStrategy::LayoutOneTree(UComboActionG
UEdComboActionGraphNode *EdNode_ParentNode = this->EdGraph->NodeMap[Node]; UEdComboActionGraphNode *EdNode_ParentNode = this->EdGraph->NodeMap[Node];
for (int32 j = 0; j < Node->ChildrenNodes.Num(); j++) for (int32 j = 0; j < Node->ChildNodes.Num(); j++)
{ {
NextLevelNodes.Add(Node->ChildrenNodes[j]); NextLevelNodes.Add(Node->ChildNodes[j]);
UEdComboActionGraphNode *EdNode_ChildNode = this->EdGraph->NodeMap[Node->ChildrenNodes[j]]; UEdComboActionGraphNode *EdNode_ChildNode = this->EdGraph->NodeMap[Node->ChildNodes[j]];
Diff.X = EdNode_ChildNode->NodePosX - EdNode_ParentNode->NodePosY; Diff.X = EdNode_ChildNode->NodePosX - EdNode_ParentNode->NodePosY;
Diff.Y = EdNode_ChildNode->NodePosY - EdNode_ParentNode->NodePosY; Diff.Y = EdNode_ChildNode->NodePosY - EdNode_ParentNode->NodePosY;

View File

@ -52,7 +52,7 @@ FBox2D UComboActionGraphLayoutStrategy::GetActualBounds(UComboActionGraphNode *R
Rtn += GetNodeBound(this->EdGraph->NodeMap[Node]); Rtn += GetNodeBound(this->EdGraph->NodeMap[Node]);
for (UComboActionGraphNode *ChildNode : Node->ChildrenNodes) for (UComboActionGraphNode *ChildNode : Node->ChildNodes)
{ {
NextLevelNodes.Add(ChildNode); NextLevelNodes.Add(ChildNode);
} }
@ -82,7 +82,7 @@ void UComboActionGraphLayoutStrategy::RandomLayoutOneTree(UComboActionGraphNode
EdNode_Node->NodePosX = UKismetMathLibrary::RandomFloatInRange(Bound.Min.X, Bound.Max.X); EdNode_Node->NodePosX = UKismetMathLibrary::RandomFloatInRange(Bound.Min.X, Bound.Max.X);
EdNode_Node->NodePosY = UKismetMathLibrary::RandomFloatInRange(Bound.Min.Y, Bound.Max.Y); EdNode_Node->NodePosY = UKismetMathLibrary::RandomFloatInRange(Bound.Min.Y, Bound.Max.Y);
for (UComboActionGraphNode *ChildNode : Node->ChildrenNodes) for (UComboActionGraphNode *ChildNode : Node->ChildNodes)
{ {
NextLevelNodes.Add(ChildNode); NextLevelNodes.Add(ChildNode);
} }

View File

@ -66,13 +66,13 @@ void UComboActionTreeSolveLayoutStrategy::InitPass(UComboActionGraphNode *RootNo
UEdComboActionGraphNode *EdNode_RootNode = this->EdGraph->NodeMap[RootNode]; UEdComboActionGraphNode *EdNode_RootNode = this->EdGraph->NodeMap[RootNode];
FVector2D ChildAnchor(FVector2D(0.0f, this->GetNodeHeight(EdNode_RootNode) + this->OptimalDistance + Anchor.Y)); FVector2D ChildAnchor(FVector2D(0.0f, this->GetNodeHeight(EdNode_RootNode) + this->OptimalDistance + Anchor.Y));
for (int32 i = 0; i < RootNode->ChildrenNodes.Num(); i++) for (int32 i = 0; i < RootNode->ChildNodes.Num(); i++)
{ {
UComboActionGraphNode *Child = RootNode->ChildrenNodes[i]; UComboActionGraphNode *Child = RootNode->ChildNodes[i];
UEdComboActionGraphNode *EdNode_ChildNode = this->EdGraph->NodeMap[Child]; UEdComboActionGraphNode *EdNode_ChildNode = this->EdGraph->NodeMap[Child];
if (i > 0) if (i > 0)
{ {
UComboActionGraphNode *PreChild = RootNode->ChildrenNodes[i - 1]; UComboActionGraphNode *PreChild = RootNode->ChildNodes[i - 1];
UEdComboActionGraphNode *EdNode_PreChildNode = this->EdGraph->NodeMap[PreChild]; UEdComboActionGraphNode *EdNode_PreChildNode = this->EdGraph->NodeMap[PreChild];
ChildAnchor.X += this->OptimalDistance + this->GetNodeWidth(EdNode_PreChildNode) / 2; ChildAnchor.X += this->OptimalDistance + this->GetNodeWidth(EdNode_PreChildNode) / 2;
} }
@ -83,7 +83,7 @@ void UComboActionTreeSolveLayoutStrategy::InitPass(UComboActionGraphNode *RootNo
float NodeWidth = this->GetNodeWidth(EdNode_RootNode); float NodeWidth = this->GetNodeWidth(EdNode_RootNode);
EdNode_RootNode->NodePosY = Anchor.Y; EdNode_RootNode->NodePosY = Anchor.Y;
if (RootNode->ChildrenNodes.Num() == 0) if (RootNode->ChildNodes.Num() == 0)
{ {
EdNode_RootNode->NodePosX = Anchor.X - NodeWidth / 2; EdNode_RootNode->NodePosX = Anchor.X - NodeWidth / 2;
} }
@ -96,9 +96,9 @@ void UComboActionTreeSolveLayoutStrategy::InitPass(UComboActionGraphNode *RootNo
bool UComboActionTreeSolveLayoutStrategy::ResolveConflictPass(UComboActionGraphNode *Node) bool UComboActionTreeSolveLayoutStrategy::ResolveConflictPass(UComboActionGraphNode *Node)
{ {
bool HasConflict = false; bool HasConflict = false;
for (int32 i = 0; i < Node->ChildrenNodes.Num(); ++i) for (int32 i = 0; i < Node->ChildNodes.Num(); ++i)
{ {
UComboActionGraphNode *Child = Node->ChildrenNodes[i]; UComboActionGraphNode *Child = Node->ChildNodes[i];
if (this->ResolveConflictPass(Child)) if (this->ResolveConflictPass(Child))
{ {
HasConflict = true; HasConflict = true;
@ -107,7 +107,7 @@ bool UComboActionTreeSolveLayoutStrategy::ResolveConflictPass(UComboActionGraphN
for (const UComboActionGraphNode *ParentNode : Node->ParentNodes) for (const UComboActionGraphNode *ParentNode : Node->ParentNodes)
{ {
for (UComboActionGraphNode *LeftSibling : ParentNode->ChildrenNodes) for (UComboActionGraphNode *LeftSibling : ParentNode->ChildNodes)
{ {
if (LeftSibling == Node) if (LeftSibling == Node)
{ {
@ -183,7 +183,7 @@ void UComboActionTreeSolveLayoutStrategy::GetLeftContour(UComboActionGraphNode *
Contour[Level] = EdNode_Node; Contour[Level] = EdNode_Node;
} }
for (UComboActionGraphNode *Child : RootNode->ChildrenNodes) for (UComboActionGraphNode *Child : RootNode->ChildNodes)
{ {
this->GetLeftContour(Child, Level + 1, Contour); this->GetLeftContour(Child, Level + 1, Contour);
} }
@ -201,7 +201,7 @@ void UComboActionTreeSolveLayoutStrategy::GetRightContour(UComboActionGraphNode
Contour[Level] = EdNode_Node; Contour[Level] = EdNode_Node;
} }
for (UComboActionGraphNode *Child : RootNode->ChildrenNodes) for (UComboActionGraphNode *Child : RootNode->ChildNodes)
{ {
this->GetRightContour(Child, Level + 1, Contour); this->GetRightContour(Child, Level + 1, Contour);
} }
@ -213,7 +213,7 @@ void UComboActionTreeSolveLayoutStrategy::ShiftSubTree(UComboActionGraphNode *Ro
EdNode_Node->NodePosX += Offset.X; EdNode_Node->NodePosX += Offset.X;
EdNode_Node->NodePosY += Offset.Y; EdNode_Node->NodePosY += Offset.Y;
for (UComboActionGraphNode *Child : RootNode->ChildrenNodes) for (UComboActionGraphNode *Child : RootNode->ChildNodes)
{ {
if (Child->ParentNodes[0] == RootNode) if (Child->ParentNodes[0] == RootNode)
{ {
@ -225,17 +225,17 @@ void UComboActionTreeSolveLayoutStrategy::ShiftSubTree(UComboActionGraphNode *Ro
void UComboActionTreeSolveLayoutStrategy::UpdateParentNodePosition(UComboActionGraphNode *RootNode) void UComboActionTreeSolveLayoutStrategy::UpdateParentNodePosition(UComboActionGraphNode *RootNode)
{ {
UEdComboActionGraphNode *EdNode_ParentNode = this->EdGraph->NodeMap[RootNode]; UEdComboActionGraphNode *EdNode_ParentNode = this->EdGraph->NodeMap[RootNode];
if (RootNode->ChildrenNodes.Num() % 2 == 0) if (RootNode->ChildNodes.Num() % 2 == 0)
{ {
UEdComboActionGraphNode *FirstChild = this->EdGraph->NodeMap[RootNode->ChildrenNodes[0]]; UEdComboActionGraphNode *FirstChild = this->EdGraph->NodeMap[RootNode->ChildNodes[0]];
UEdComboActionGraphNode *LastChild = this->EdGraph->NodeMap[RootNode->ChildrenNodes.Last()]; UEdComboActionGraphNode *LastChild = this->EdGraph->NodeMap[RootNode->ChildNodes.Last()];
float LeftBound = FirstChild->NodePosX; float LeftBound = FirstChild->NodePosX;
float RightBound = LastChild->NodePosX + this->GetNodeWidth(LastChild); float RightBound = LastChild->NodePosX + this->GetNodeWidth(LastChild);
EdNode_ParentNode->NodePosX = (LeftBound + RightBound) / 2 - this->GetNodeWidth(EdNode_ParentNode) / 2; EdNode_ParentNode->NodePosX = (LeftBound + RightBound) / 2 - this->GetNodeWidth(EdNode_ParentNode) / 2;
} }
else else
{ {
UEdComboActionGraphNode *MidChild = this->EdGraph->NodeMap[RootNode->ChildrenNodes[RootNode->ChildrenNodes.Num() / 2]]; UEdComboActionGraphNode *MidChild = this->EdGraph->NodeMap[RootNode->ChildNodes[RootNode->ChildNodes.Num() / 2]];
EdNode_ParentNode->NodePosX = MidChild->NodePosX + this->GetNodeWidth(MidChild) / 2 - this->GetNodeWidth(EdNode_ParentNode) / 2; EdNode_ParentNode->NodePosX = MidChild->NodePosX + this->GetNodeWidth(MidChild) / 2 - this->GetNodeWidth(EdNode_ParentNode) / 2;
} }
} }

View File

@ -0,0 +1,147 @@
#include "Popups/ComboInputPopup_GraphValidation.h"
#include "ComboInputEditor.h"
#include "Helpers/ComboActionGraphColors.h"
#include "Interfaces/IPluginManager.h"
#include "Settings/ComboActionGraphEditorSettings.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Text/SRichTextBlock.h"
#define LOCTEXT_NAMESPACE "ComboInputPopup_GraphValidation"
void ComboInputPopup_GraphValidation::OnBrowserLinkClicked(const FSlateHyperlinkRun::FMetadata &Metadata)
{
const FString *URL = Metadata.Find(TEXT("href"));
if (URL)
{
FPlatformProcess::LaunchURL(**URL, nullptr, nullptr);
}
}
TSharedPtr<SWindow> ComboInputPopup_GraphValidation::Open(const TArray<FText> ValidationMessages)
{
if (!FSlateApplication::Get().CanDisplayWindows())
{
return nullptr;
}
const FComboInputEditorModule &Module = FModuleManager::GetModuleChecked<FComboInputEditorModule>("ComboInputEditor");
const TSharedPtr<FSlateStyleSet> &EditorStyle = Module.Get().GetComboInputEditorStyleSet();
const TSharedRef<SBorder> WindowContent = SNew(SBorder)
.BorderImage(EditorStyle->GetBrush("MDSStyleSet.Node.TextSoftEdges"))
.BorderBackgroundColor
(
ComboActionGraphColors::ValidationGraph::DarkTheme
)
.Padding(FMargin(8.0f, 8.0f));
TSharedPtr<SWindow> Window = SNew(SWindow)
.AutoCenter(EAutoCenter::PreferredWorkArea)
.SupportsMaximize(false)
.SupportsMinimize(false)
.SizingRule(ESizingRule::FixedSize)
.ClientSize(FVector2D(650, 750))
.Title(FText::FromString("Combo Action Graph Validation"))
.IsTopmostWindow(true)
[
WindowContent
];
const FSlateFontInfo Heading1Font = FCoreStyle::GetDefaultFontStyle("Bold", 24);
const FSlateFontInfo Heading2Font = FCoreStyle::GetDefaultFontStyle("Bold", 18);
const FSlateFontInfo NormalFont = FCoreStyle::GetDefaultFontStyle("Regular", 12);
const TSharedRef<SScrollBox> ListOfMessages = SNew(SScrollBox);
for (auto Itr : ValidationMessages)
{
ListOfMessages->AddSlot()
[
SNew(SBox)
.Padding(FMargin(0.f, 3.5f, 0.f, 3.5f))
[
SNew(SRichTextBlock)
.Text(Itr)
.TextStyle(FAppStyle::Get(), "NormalText")
.DecoratorStyleSet(&FAppStyle::Get())
.AutoWrapText(true)
]
];
}
if (ValidationMessages.Num() == 0)
{
ListOfMessages->AddSlot()
[
SNew(STextBlock)
.Text(FText::FromString("There are no issues with your graph. You can close this window."))
.TextStyle(FAppStyle::Get(), "NormalText")
.AutoWrapText(true)
];
}
const TSharedRef<SVerticalBox> InnerContent =
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.FillHeight(1.0)
.Padding(10)
[
SNew(SBorder)
.Padding(10)
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
[
ListOfMessages
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(10)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot().FillWidth(1.0f)
[
SNew(SButton)
.HAlign(HAlign_Center)
.OnClicked_Lambda([Window]()
{
Window->RequestDestroyWindow();
return FReply::Handled();
})
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.Text(FText::FromString("Close window"))
]
+ SHorizontalBox::Slot()
[
SNew(SSpacer)
.Size(FVector2D(5, 0))
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SImage)
.ColorAndOpacity(FLinearColor::Red)
.Image(EditorStyle->GetBrush("MDSStyleSet.Icon.Close"))
]
]
]
];
const int32 A = InnerContent.Get().GetAllChildren()->Num();
WindowContent->SetContent(InnerContent);
Window = FSlateApplication::Get().AddWindow(Window.ToSharedRef());
return Window;
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,13 @@
#pragma once
#include "Framework/Text/SlateHyperlinkRun.h"
class SScrollBox;
class ComboInputPopup_GraphValidation
{
public:
static TSharedPtr<SWindow> Open(const TArray<FText> ValidationMessages);
static void OnBrowserLinkClicked(const FSlateHyperlinkRun::FMetadata &Metadata);
};

View File

@ -95,7 +95,6 @@ private:
TArray<TSharedPtr<IAssetTypeActions>> CreatedAssetTypeActions; TArray<TSharedPtr<IAssetTypeActions>> CreatedAssetTypeActions;
TSharedPtr<class FSlateStyleSet> ComboInputEditorStyleSet; TSharedPtr<class FSlateStyleSet> ComboInputEditorStyleSet;
TSharedPtr<class FSlateStyleSet> ComboActionGraphEditorStyleSet;
TSharedPtr<struct FGraphPanelNodeFactory> ComboActionGraphPanelNodeFactory; TSharedPtr<struct FGraphPanelNodeFactory> ComboActionGraphPanelNodeFactory;
TSharedPtr<class FAssetTypeActions_ComboActionGraph> ComboActionGraphAssetActions; TSharedPtr<class FAssetTypeActions_ComboActionGraph> ComboActionGraphAssetActions;
}; };