What you can also do is create a K2Node that does this. It should be fairly simple.
You can take a look at UK2Node_SwitchEnum. It takes an enum then creates output pins matching its possible values.
You want to make enum an output, and all those pins input. You can have an expand node that makes a literal enum depending on the execute pin and outputs its trough output pin.
I may get around to making a minimum functional version later.
Edit: Here’s the code I wrote for this node. Some parts are derived from other existing K2Node’s. I tested it very briefly. It’s best to consider it as a learning exercise.
K2Node_CaseToEnum.h
// Written By Dobacetr 15.01.2024
#pragma once
#include "Containers/Array.h"
#include "CoreMinimal.h"
#include "EdGraph/EdGraphNodeUtils.h"
#include "K2Node.h"
#include "NodeDependingOnEnumInterface.h"
#include "UObject/Class.h"
#include "K2Node_CaseToEnum.generated.h"
UCLASS()
class BLUEPRINTGRAPH_API UK2Node_CaseToEnum : public UK2Node, public INodeDependingOnEnumInterface
{
GENERATED_UCLASS_BODY()
/** Name of the enum being switched on */
UPROPERTY()
TObjectPtr<UEnum> Enum;
/** List of the current entries in the enum */
UPROPERTY()
TArray<FName> EnumEntries;
/** List of the current entries in the enum */
UPROPERTY(Transient)
TArray<FText> EnumFriendlyNames;
// INodeDependingOnEnumInterface
virtual class UEnum* GetEnum() const override { return Enum; }
virtual void ReloadEnum(class UEnum* InEnum) override { SetEnum(InEnum); };
virtual bool ShouldBeReconstructedAfterEnumChanged() const override { return true; }
// End of INodeDependingOnEnumInterface
//~ Begin UEdGraphNode Interface.
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
virtual FText GetTooltipText() const override;
virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
virtual void AddPinSearchMetaDataInfo(const UEdGraphPin* Pin, TArray<struct FSearchTagDataPair>& OutTaggedMetaData) const override;
virtual FSlateIcon GetIconAndTint(FLinearColor& OutColor) const override;
//~ End UEdGraphNode Interface.
//~ Begin UK2Node Interface
virtual bool CanEverRemoveExecutionPin() const override { return true; }
virtual bool CanEverInsertExecutionPin() const override { return true; }
virtual bool CanUserEditPinAdvancedViewFlag() const override { return true; }
virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
virtual FText GetMenuCategory() const override;
virtual bool IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const override;
virtual void PreloadRequiredAssets() override;
virtual ERedirectType DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const override;
//~ End UK2Node Interface
/** Bind the switch to a named enum */
void SetEnum(UEnum* InEnum);
// Create Input Case Exec Pins
void CreateExecCasePins();
private:
/** Constructing FText strings can be costly, so we cache the node's title */
FNodeTextCache CachedNodeTitle;
};
K2Node_CaseToEnum.cpp
// Written By Dobacetr 15.01.2024
#include "K2Node_CaseToEnum.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintFieldNodeSpawner.h"
#include "EdGraphSchema_K2.h"
#include "EditorCategoryUtils.h"
#include "FindInBlueprintManager.h"
#include "KismetCompiler.h"
#include "K2Node_AssignmentStatement.h"
#include "K2Node_TemporaryVariable.h"
#define LOCTEXT_NAMESPACE "K2Node_GetDataTableRow"
UK2Node_CaseToEnum::UK2Node_CaseToEnum(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
OrphanedPinSaveMode = ESaveOrphanPinMode::SaveAll;
}
void UK2Node_CaseToEnum::AllocateDefaultPins()
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
// Add execution pin
UEdGraphPin* ThenPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
// Add output case pin
UEdGraphPin* EnumPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Byte, Enum, TEXT("Condition"));
GetDefault<UEdGraphSchema_K2>()->SetPinAutogeneratedDefaultValueBasedOnType(EnumPin);
// Create all exec case pins
CreateExecCasePins();
}
FText UK2Node_CaseToEnum::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
if (Enum == nullptr)
{
return LOCTEXT("CaseToEnum_BadEnumTitle", "(bad enum) to Switch on");
}
else if (CachedNodeTitle.IsOutOfDate(this))
{
FFormatNamedArguments Args;
Args.Add(TEXT("EnumName"), FText::FromString(Enum->GetName()));
// FText::Format() is slow, so we cache this to save on performance
CachedNodeTitle.SetCachedText(FText::Format(NSLOCTEXT("K2Node", "CaseToEnum", "Cases to {EnumName}"), Args), this);
}
return CachedNodeTitle;
}
FText UK2Node_CaseToEnum::GetTooltipText() const
{
return NSLOCTEXT("K2Node", "CaseToEnum_ToolTip", "Performs the inverse of SwitchEnum node. Generates the enum from cases.");
}
void UK2Node_CaseToEnum::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
Super::ExpandNode(CompilerContext, SourceGraph);
if (!Enum)
{
CompilerContext.MessageLog.Error(*LOCTEXT("CaseToEnum_EnumNoneError", "Enum must be connected.").ToString(), this);
// we break exec links so this is the only error we get
BreakAllNodeLinks();
return;
}
const auto K2Schema = GetDefault< UEdGraphSchema_K2 >();
UEdGraphPin* ThenPin = GetThenPin();
if (ThenPin->LinkedTo.Num() > 0)
{
// TemporaryVariable node to store result enum
UK2Node_TemporaryVariable* TempEnumVarNode = CompilerContext.SpawnIntermediateNode<UK2Node_TemporaryVariable>(this, SourceGraph);
TempEnumVarNode->VariableType.PinCategory = UEdGraphSchema_K2::PC_Byte;
TempEnumVarNode->VariableType.PinSubCategoryObject = Enum;
TempEnumVarNode->AllocateDefaultPins();
// Get the output pin
UEdGraphPin* TempEnumVarOutput = TempEnumVarNode->GetVariablePin();
for (const FName& EnumEntry : EnumEntries)
{
UEdGraphPin* CasePin = FindPinChecked(EnumEntry);
if (CasePin->LinkedTo.Num() > 0)
{
// AssignmentStatement node to set the result enum
UK2Node_AssignmentStatement* SetEnumValue = CompilerContext.SpawnIntermediateNode<UK2Node_AssignmentStatement>(this, SourceGraph);
SetEnumValue->AllocateDefaultPins();
// Connect TemporaryVariable and AssignmentStatement
K2Schema->TryCreateConnection(SetEnumValue->GetVariablePin(), TempEnumVarOutput);
// Set the value for assignment
SetEnumValue->GetValuePin()->DefaultValue = EnumEntry.ToString();
// Connect Case to Assignment
CompilerContext.MovePinLinksToIntermediate(*CasePin, *(SetEnumValue->GetExecPin()));
// After Assignment, continue with ThenPin
CompilerContext.CopyPinLinksToIntermediate(*ThenPin, *(SetEnumValue->GetThenPin()));
}
}
CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(TEXT("Condition")), *TempEnumVarOutput);
// Break leftover links
ThenPin->BreakAllPinLinks();
return;
}
// No connection to Then, we dont need to do anything
BreakAllNodeLinks();
}
void UK2Node_CaseToEnum::AddPinSearchMetaDataInfo(const UEdGraphPin* Pin, TArray<struct FSearchTagDataPair>& OutTaggedMetaData) const
{
Super::AddPinSearchMetaDataInfo(Pin, OutTaggedMetaData);
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
if (Enum != nullptr && K2Schema->IsExecPin(*Pin) && Pin->Direction == EGPD_Input && Enum->IsNative() && EnumEntries.Contains(Pin->GetFName()))
{
// Allow native enum switch pins to be searchable by C++ enum name
OutTaggedMetaData.Add(FSearchTagDataPair(FFindInBlueprintSearchTags::FiB_NativeName, FText::FromString(Pin->GetName())));
}
}
FSlateIcon UK2Node_CaseToEnum::GetIconAndTint(FLinearColor& OutColor) const
{
static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.Switch_16x");
return Icon;
}
void UK2Node_CaseToEnum::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
struct GetMenuActions_Utils
{
static void SetNodeEnum(UEdGraphNode* NewNode, FFieldVariant /*EnumField*/, TWeakObjectPtr<UEnum> NonConstEnumPtr)
{
UK2Node_CaseToEnum* EnumNode = CastChecked<UK2Node_CaseToEnum>(NewNode);
EnumNode->Enum = NonConstEnumPtr.Get();
}
};
UClass* NodeClass = GetClass();
ActionRegistrar.RegisterEnumActions(FBlueprintActionDatabaseRegistrar::FMakeEnumSpawnerDelegate::CreateLambda([NodeClass](const UEnum* InEnum)->UBlueprintNodeSpawner*
{
UBlueprintFieldNodeSpawner* NodeSpawner = UBlueprintFieldNodeSpawner::Create(NodeClass, const_cast<UEnum*>(InEnum));
check(NodeSpawner != nullptr);
TWeakObjectPtr<UEnum> NonConstEnumPtr = MakeWeakObjectPtr(const_cast<UEnum*>(InEnum));
NodeSpawner->SetNodeFieldDelegate = UBlueprintFieldNodeSpawner::FSetNodeFieldDelegate::CreateStatic(GetMenuActions_Utils::SetNodeEnum, NonConstEnumPtr);
return NodeSpawner;
}));
}
FText UK2Node_CaseToEnum::GetMenuCategory() const
{
static FNodeTextCache CachedCategory;
if (CachedCategory.IsOutOfDate(this))
{
// FText::Format() is slow, so we cache this to save on performance
CachedCategory.SetCachedText(FEditorCategoryUtils::BuildCategoryString(FCommonEditorCategory::FlowControl, LOCTEXT("ActionMenuCategory", "Switch")), this);
}
return CachedCategory;
}
bool UK2Node_CaseToEnum::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const
{
const UEnum* SubCategoryObject = Cast<UEnum>(OtherPin->PinType.PinSubCategoryObject.Get());
if (SubCategoryObject)
{
if (SubCategoryObject != Enum)
{
return true;
}
}
return false;
}
void UK2Node_CaseToEnum::PreloadRequiredAssets()
{
PreloadObject(Enum);
Super::PreloadRequiredAssets();
}
UK2Node::ERedirectType UK2Node_CaseToEnum::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const
{
if (NewPin->PinName == OldPin->PinName)
{
// Compare the names, case-sensitively
return ERedirectType_Name;
}
if (Enum && OldPinIndex > 2 && NewPinIndex > 2)
{
const int64 OldValue = Enum->GetValueByName(OldPin->PinName);
const int64 NewValue = Enum->GetValueByName(NewPin->PinName);
// This handles redirects properly
if (OldValue == NewValue && OldValue != INDEX_NONE)
{
return UK2Node::ERedirectType_Name;
}
}
return ERedirectType_None;
}
void UK2Node_CaseToEnum::SetEnum(UEnum* InEnum)
{
// Same code as UK2Node_SwitchEnum::SetEnum
Enum = InEnum;
// regenerate enum name list
EnumEntries.Empty();
EnumFriendlyNames.Empty();
if (Enum)
{
PreloadObject(Enum);
// When on async loading thread, postload happens later on GT unless it's possible
// to do it right now safely.
if (IsInGameThread() || Enum->IsPostLoadThreadSafe())
{
Enum->ConditionalPostLoad();
}
for (int32 EnumIndex = 0; EnumIndex < Enum->NumEnums() - 1; ++EnumIndex)
{
bool const bShouldBeHidden = Enum->HasMetaData(TEXT("Hidden"), EnumIndex) || Enum->HasMetaData(TEXT("Spacer"), EnumIndex);
if (!bShouldBeHidden)
{
FString const EnumValueName = Enum->GetNameStringByIndex(EnumIndex);
EnumEntries.Add(FName(*EnumValueName));
FText EnumFriendlyName = Enum->GetDisplayNameTextByIndex(EnumIndex);
EnumFriendlyNames.Add(EnumFriendlyName);
}
}
}
}
void UK2Node_CaseToEnum::CreateExecCasePins()
{
// Same code as UK2Node_SwitchEnum::CreateCasePins, but pin directions are change from Output to Input
if (Enum)
{
SetEnum(Enum);
}
const bool bShouldUseAdvancedView = (EnumEntries.Num() > 5);
if (bShouldUseAdvancedView && (ENodeAdvancedPins::NoPins == AdvancedPinDisplay))
{
AdvancedPinDisplay = ENodeAdvancedPins::Hidden;
}
for (int32 Index = 0; Index < EnumEntries.Num(); ++Index)
{
UEdGraphPin* NewPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, EnumEntries[Index]);
if (EnumFriendlyNames.IsValidIndex(Index))
{
NewPin->PinFriendlyName = EnumFriendlyNames[Index];
}
if (bShouldUseAdvancedView && (Index > 2))
{
NewPin->bAdvancedView = true;
}
}
}
#undef LOCTEXT_NAMESPACE