How to make a reverse "switch on enum" macro?

Following my “reverse branch” thread, now I’m wondering about a reversed “switch on enum”.

I have this:

which isn’t really necessary, I shouldn’t really need to set the enum as a variable for each event if I had and inverted switch like this: (sorry for bad photoshop)

Can this be made? I suppose the difficult part would be to set the number of inputs based on the number of entries in the enum?

1 Like

The best way to do this is to make the code after your switch a function, with an EBoxTypeEnum parameter.

2 Likes

hmm I don’t think I follow? the switch node is photoshopped, it doesn’t exist, the actual switch looks like this:

reason why I was wondering if a “reversed” switch would be possible to make

1 Like

I am aware the node you photoshopped doesn’t exist.

My point is that a “reversed” switch is almost identical to creating a function with a parameter.

2 Likes

Afaik functions can’t have multiple input execution pins though?

Risking sounding like a broken record… :smiley:


The buttons should be standalone, configurable widgets dispatching their assigned data:

image

The parent container can fetch something from a Dictionary:

But, at this point, you do not even need an Enumerator or a Dictionary - each button may simply hold & dispatch an int Index. You assign the data to the button itself.


What’s more, you can make the data Instance Editable and even use the autobind:

Although, this is not always convenient, depends on the scenario. Here, clicking a button produces an index.

that would add even more stuff than I currently have though

what I have works fine, just wanted to simplify it since situations like this could be recurring

Well. It’s always a matter of:

  • do I spend 5 hours now connecting wires manually
  • do I spend 5 hours writing script that automates it

You’re doing a simple menu, perhaps trying to find a shortcut is not good use of time! Interestingly enough, it took less time to set the wire the script than to type my messages. The thing is, if I now want to expand it, it’s virtually free.

Pick your poison. :blush: My humble recommendation is to choose something that is easy to maintain and something you do not need to crack open to fix up. Script something that works now, optimise later.

So I think you’re more than fine here. But you asked, so there’s an answer.

That’s fair, I just thought of asking here in case somebody knew from the tip of their tongue how to make such a macro, since I surely don’t.

Would your idea even work though? cuz I can only connect one incoming index into my “set box type” event, would still require an inverted switch I suppose.

That’s why I suggested this as the first thing, all buttons execute the same event but the data is unique:

Use an int here. This button can have an Index built-in:

image


So the whole thing, with even fewer wires, could look like so:


I assumed all your buttons share the same container, if they do not and are scattered all over the place, one can:

instead, and no need to cast here.


The only downside is that it’s not as easy to read. So comment what it does, do it for anyone else who may look at your script but, most importantly, do if for the future you.

1 Like

Gotcha, loud and clear.

This is sure one way of doing it. For now I’m keeping what I already have since it’s working, but will keep this in mind as I can see other situations where this would be more convenient

Will also keep this thread open in case anyone looking for a challenges would like to crack up the initially suggested macro :slightly_smiling_face:

thanks

2 Likes

Use macros and


get enum selection

Thanks, that’s what I did though, but that’s simply moving the same nodes into a macro.

It kinda works in this case but only for this specific enum.

What I was wondering if it is possible to set the number of inputs and their labels based on the entries in the enum?

1 Like

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
3 Likes

Thanks! This is precisely what I want yes, however this code isn’t quite working I believe.

I’m not very good with C++, could you give it a look?

Not sure I understand your question. I think you are asking a way to take in all those enum states and do something. If so then first get the enum, drag out to an = = (enum) and set it to the states you need to check for, run those to a boolean, use or booleans to add all of your enum states to your boolean.

you know the switch on enum node right, it has one in node and X outputs, based on the number of entries in the selected enumerator.

I want to reverse it, I want to have X number of input execution pints based on the enum, and only one exec output, and the enum pin. so based on what input pin it comes from, it’ll output the selected enum option.

it would end up looking like this:

image

Dobacetr’s code works as you want

The build file needs some extra modules
All parts after EnhancedInput are needed for it to compile

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput" , "BlueprintGraph" , "Slate", "SlateCore", "Kismet" , "UnrealEd", "KismetCompiler" });


works2

just be sure to set BLUEPRINTGRAPH_API to YOURGAMENAME_API where YOURGAMENAME is the name of your game but in capitalized letters.

1 Like

I do not think such a node exists. The solution I suggested should work in about the same manner as you desire but will surely look more bloated and will need to be tweaked.

Right, and where do i put this line?

PublicDependencyModuleNames.AddRange(new string { “Core”, “CoreUObject”, “Engine”, “InputCore”, “EnhancedInput” , “BlueprintGraph” , “Slate”, “SlateCore”, “Kismet” , “UnrealEd”, “KismetCompiler” });

oh the .h file, after all the #includes ?

and in fact, where do I start? can I create a new project, add this conde to it, and then just export the node to use in my other project? (kinda want to keep it blueprint only, hence why I’m trying to make a custom node)

or do have to convert my project to code for it to work?

also, do I create it as a blueprint function library or something else?