Tutorial - How to dynamically bind input using C++

Hi Guys ,

This week I worked on dynamically binding input keyboard and mouse values based on user’s choice in keyboard customization section. Here is how I did it.
First I created an excel table containing the following columns. I only gave two examples of keyboard actions, you will have as many rows as the number of different action event and key binding you want to have in your game.
[table=“width: 500, class: grid”]

**Action**
**DefaultInput**
**Input**
**Category**


Primary Attack
MouseLeftButton
MouseLeftButton
Combat


Move Forward
W
W
Movement

Next I created a struct for my datatable in my HUD header class in C++. Make sure to add the following namespace
“Runtime/Engine/Classes/Engine/DataTable.h” in the header class.


USTRUCT(Blueprintable)
struct FKeyBoardBindingTable : public FTableRowBase
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(BlueprintReadOnly, Category = "KeyBinding")
	FString Action;

	UPROPERTY(BlueprintReadOnly, Category = "KeyBinding")
	FString DefaultInput;

	UPROPERTY(BlueprintReadOnly, Category = "KeyBinding")
	FString Input;

	UPROPERTY(BlueprintReadOnly, Category = "KeyBinding")
	FString Category;

};

Now define your datatable in HUD header -


UDataTable* KeyBoardBindingTable;

and initialize it the HUD constructor class


static ConstructorHelpers::FObjectFinder<UDataTable> KeyBoardBindingTable_BP(TEXT("DataTable'/Game/DataTables/KeyboardConfig.KeyboardConfig'"));
	KeyBoardBindingTable = KeyBoardBindingTable_BP.Object;


I imported this excel as datatable in UE4 , inherited from the struct FKeyBoardBindingTable.

Then , I created userinterface class for keyboard configuration menu. , deriving from SCompounWidget , and wrote the UI in slate architecture. The UI is simple key customization UI that you can see in any game , when user clicks on an input , it asks you to bind it to a new value , then you have to press a key to bind to it. See below.


BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SKeyConfigUI::Construct(const FArguments& InArgs)
{
	
	GameHUD = InArgs._GameHUD;
	MenuStyle = &FMenuStyles::Get().GetWidgetStyle<FGlobalStyle>("Global");
	
	
	
	ChildSlot.Padding(50 , 10)
		
			SNew(SOverlay)
			+ SOverlay::Slot()
			.HAlign(HAlign_Fill)
			.VAlign(VAlign_Fill)
			.Padding(40)
			
				SNew(SBorder)
				.ColorAndOpacity(FLinearColor::White)
				.BorderImage(&MenuStyle->BorderBrush1)
				.Content()
				
					SNew(SOverlay)
					+ SOverlay::Slot()
					.HAlign(HAlign_Fill)
					.VAlign(VAlign_Fill)
					
						SAssignNew(ParentWidget , SScrollBox)
						+ SScrollBox::Slot()
						.HAlign(HAlign_Left)
						.Padding(40, 80, 0, 0)
						
							SNew(STextBlock)
							.Text(FText::FromString("KeyBoard Configuration"))
							.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/jancient.ttf"), 30))
							.ColorAndOpacity(FLinearColor(0.8, 0.7, 0.3, 1))
							.ShadowColorAndOpacity(FLinearColor(0.16, 0.06, 0.05, 1))

						]
						+ SScrollBox::Slot()
							.HAlign(HAlign_Fill)
							.Padding(40, 60, 40, 0)
							
								SNew(SHorizontalBox)
								+ SHorizontalBox::Slot()
								.HAlign(HAlign_Fill)
								.VAlign(VAlign_Top)
								
									SAssignNew(GeneralButton, SButton)
									.ButtonStyle(&MenuStyle->KeyConfigHeaderStyle)
									.ContentPadding(2)
									.HAlign(HAlign_Center)
									.VAlign(VAlign_Top)
									.Content()
									
										SAssignNew(GeneralText, STextBlock)
										.Text(FText::FromString("General"))
										.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 15))
										.ColorAndOpacity(FLinearColor(0.1, 0.1, 0.03, 1))
										.ShadowColorAndOpacity(FLinearColor(0.93, 1, 0.6, 1))
										.ShadowOffset(FVector2D(1, 1))

									]
									.OnClicked(this, &SKeyConfigUI::InputCaterGoryButonClicked, FString("General"))
								]
								+ SHorizontalBox::Slot()
									.HAlign(HAlign_Fill)
									.VAlign(VAlign_Top)
									
										SAssignNew(MovementButton, SButton)
										.ButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle)
										.ContentPadding(2)
										.HAlign(HAlign_Center)
										.VAlign(VAlign_Top)
										.Content()
										
											SAssignNew(MovementText, STextBlock)
											.Text(FText::FromString("Movement"))
											.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 15))
											.ColorAndOpacity(FLinearColor(0.12, 0.1, 0.03, 1))
											.ShadowColorAndOpacity(FLinearColor(0.93, 1, 0.6, 1))
											.ShadowOffset(FVector2D(1, 1))
										]
										.OnClicked(this, &SKeyConfigUI::InputCaterGoryButonClicked, FString("Movement"))
									]
								+ SHorizontalBox::Slot()
									.HAlign(HAlign_Fill)
									.VAlign(VAlign_Top)
									
										SAssignNew(CombatButton, SButton)
										.ButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle)
										.ContentPadding(2)
										.HAlign(HAlign_Center)
										.VAlign(VAlign_Top)
										.Content()
										
											SAssignNew(CombatText, STextBlock)
											.Text(FText::FromString("Combat"))
											.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 15))
											.ColorAndOpacity(FLinearColor(0.12, 0.1, 0.03, 1))
											.ShadowColorAndOpacity(FLinearColor(0.93, 1, 0.6, 1))
											.ShadowOffset(FVector2D(1, 1))
										]
										.OnClicked(this, &SKeyConfigUI::InputCaterGoryButonClicked, FString("Combat"))
									]
								+ SHorizontalBox::Slot()
									.HAlign(HAlign_Fill)
									.VAlign(VAlign_Top)
									
										SAssignNew(MiscellaneousButton, SButton)
										.ButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle)
										.ContentPadding(2)
										.HAlign(HAlign_Center)
										.VAlign(VAlign_Top)
										.Content()
										
											SAssignNew(MiscellaneousText, STextBlock)
											.Text(FText::FromString("Miscellaneous"))
											.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 15))
											.ColorAndOpacity(FLinearColor(0.12, 0.1, 0.03, 1))
											.ShadowColorAndOpacity(FLinearColor(0.93, 1, 0.6, 1))
											.ShadowOffset(FVector2D(1, 1))
										]
										.OnClicked(this, &SKeyConfigUI::InputCaterGoryButonClicked, FString("Miscellaneous"))
									]
							]
				]
				+ SOverlay::Slot()
					.HAlign(HAlign_Fill)
					.VAlign(VAlign_Fill)
					.Padding(FMargin(100, 250, 0, 50))
					
						SNew(SScrollBox)
						+ SScrollBox::Slot()
						.HAlign(HAlign_Fill)
						
							SAssignNew(KeyBindingWidget, SWidgetSwitcher)
							+ SWidgetSwitcher::Slot()
							.HAlign(HAlign_Fill)
							.VAlign(VAlign_Top)
							
								SAssignNew(GeneralWidget, SScrollBox)
							]
							+ SWidgetSwitcher::Slot()
								.HAlign(HAlign_Fill)
								.VAlign(VAlign_Top)
								
									SAssignNew(MovementWidget, SScrollBox)
								]
							+ SWidgetSwitcher::Slot()
								.HAlign(HAlign_Fill)
								.VAlign(VAlign_Top)
								
									SAssignNew(CombatWidget, SScrollBox)
								]
							+ SWidgetSwitcher::Slot()
								.HAlign(HAlign_Fill)
								.VAlign(VAlign_Top)
								
									SAssignNew(MiscellaneousWidget, SScrollBox)
								]
						]
					]
				+ SOverlay::Slot()
					.HAlign(HAlign_Center)
					.VAlign(VAlign_Bottom)
					.Padding(10)
					
						SNew(SHorizontalBox)
						+ SHorizontalBox::Slot()
						.HAlign(HAlign_Fill)
						.VAlign(VAlign_Fill)
						.Padding(10, 0)
					     
						
							SNew(SButton)
							.ButtonStyle(&MenuStyle->GeneralButtonStyle1)
							.ContentPadding(2)
							.HAlign(HAlign_Fill)
							.VAlign(VAlign_Fill)
			     			.Content()
							
								SNew(STextBlock)
								.Text(FText::FromString("  Cancel  "))
								.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 15))
								.ColorAndOpacity(FLinearColor(0.7, 0.6, 0.1, 1))
							]
							.OnClicked(this, &SKeyConfigUI::CancelButtonClicked)

						]
						+ SHorizontalBox::Slot()
							.HAlign(HAlign_Fill)
							.VAlign(VAlign_Center)
							.Padding(10, 0)
							
								SNew(SButton)
								.ButtonStyle(&MenuStyle->GeneralButtonStyle1)
								.ContentPadding(2)
								.HAlign(HAlign_Fill)
								.VAlign(VAlign_Fill)
								.Content()
								
									SNew(STextBlock)
									.Text(FText::FromString("  Save  "))
									.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 15))
									.ColorAndOpacity(FLinearColor(0.7, 0.6, 0.1, 1))
								]
								.OnClicked(this, &SKeyConfigUI::SaveButtonClicked)
							]
						+ SHorizontalBox::Slot()
							.HAlign(HAlign_Fill)
							.VAlign(VAlign_Center)
							.Padding(10, 0)
							
								SNew(SButton)
								.ButtonStyle(&MenuStyle->GeneralButtonStyle1)
								.ContentPadding(2)
								.HAlign(HAlign_Center)
								.VAlign(VAlign_Center)
								.Content()
								
									SNew(STextBlock)
									.Text(FText::FromString("  Reset  "))
									.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 15))
									.ColorAndOpacity(FLinearColor(0.7, 0.6, 0.1, 1))
								]
								.OnClicked(this, &SKeyConfigUI::ResetButtonClicked)
							]

					]
			]
		]
			
		];
	
		DrawKeyBindingWidget();
	
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

void SKeyConfigUI::DrawKeyBindingWidget()
{
	int32 rowindex = 1; 
	GeneralWidget->ClearChildren();
	MovementWidget->ClearChildren();
	CombatWidget->ClearChildren();
	MiscellaneousWidget->ClearChildren();

	while (true)
	{
		FKeyBoardBindingTable* LookUpRow = GameHUD->KeyBoardBindingTable->FindRow<FKeyBoardBindingTable>(FName(*FString::FromInt(rowindex)), TEXT("Look Up"));
		if (LookUpRow)
		{
			if (LookUpRow->Category == "General")
			{
				GeneralIndex++;
				GeneralWidget->AddSlot().HAlign(HAlign_Fill).Padding(10)
					
						DrawKeyBindingWidgetRow(LookUpRow->Action, LookUpRow->Input, LookUpRow->Category, rowindex).ToSharedRef()
					];
			}
				
			else if (LookUpRow->Category == "Movement")
			{

				MovementIndex++;
				MovementWidget->AddSlot().HAlign(HAlign_Fill).Padding(10)
					
						DrawKeyBindingWidgetRow(LookUpRow->Action, LookUpRow->Input, LookUpRow->Category, rowindex).ToSharedRef()
					];
			}
			else if (LookUpRow->Category == "Combat")
			{
				CombatIndex++;
				CombatWidget->AddSlot().HAlign(HAlign_Fill).Padding(10)
					
						DrawKeyBindingWidgetRow(LookUpRow->Action, LookUpRow->Input, LookUpRow->Category, rowindex).ToSharedRef()
					];
			}
			else if (LookUpRow->Category == "Miscellaneous")
			{
				MiscellaneousIndex++;
				MiscellaneousWidget->AddSlot().HAlign(HAlign_Fill).Padding(10)
					
						DrawKeyBindingWidgetRow(LookUpRow->Action, LookUpRow->Input, LookUpRow->Category, rowindex).ToSharedRef()
					];
			}

			rowindex++;
		}
		else
		{
			break;
		}
	}
}

TSharedPtr<class SHorizontalBox> SKeyConfigUI::DrawKeyBindingWidgetRow(FString Action, FString Input, FString Category , int32 Index)
{
	return
		 SNew(SHorizontalBox)
		+ SHorizontalBox::Slot().HAlign(HAlign_Center).VAlign(VAlign_Center)
		
			SAssignNew(BindingText, STextBlock).Text(FText::FromString(Action))
			.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/jancient.ttf"), 15))
			.ColorAndOpacity(FLinearColor(0.8, 0.7, 0.3, 1))
			.ShadowColorAndOpacity(FLinearColor(0.93, 1, 0.6, 1))

		]
	+ SHorizontalBox::Slot().HAlign(HAlign_Fill).VAlign(VAlign_Fill)
		
			SAssignNew(BindingButton, SButton).ButtonStyle(&MenuStyle->KeyBindingButtonStyle).HAlign(HAlign_Center).VAlign(VAlign_Center)
			.Content()
			
				SAssignNew(BindingText, STextBlock).Text(FText::FromString(Input))
				.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/jancient.ttf"), 15))
				.ColorAndOpacity(FLinearColor(0.8, 0.7, 0.3, 1))
				.ShadowColorAndOpacity(FLinearColor(0.93, 1, 0.6, 1))
			]
			.OnClicked(this, &SKeyConfigUI::KeyCustomizationButtonClicked, FString(Action), FString(Input) , Index)
		];
	
	
}


FReply SKeyConfigUI::SaveButtonClicked()
{
	
	int32 rowindex;
	for (rowindex = 1; rowindex < TempIndex.Num(); rowindex++)
	{
		FKeyBoardBindingTable* LookUpRow = GameHUD->KeyBoardBindingTable->FindRow<FKeyBoardBindingTable>(FName(*TempIndex[rowindex]), TEXT("Look Up"));
		if (LookUpRow)
		{
			LookUpRow->Input = TempInput[rowindex];
		}
		else
		{
			break;
		}
	}
	return FReply::Handled();
}

FReply SKeyConfigUI::CancelButtonClicked()
{
	DrawKeyBindingWidget();
	return FReply::Handled();
}

FReply SKeyConfigUI::ResetButtonClicked()
{
	
	int32 rowindex = 1;
	while (true)
	{
		FKeyBoardBindingTable* LookUpRow = GameHUD->KeyBoardBindingTable->FindRow<FKeyBoardBindingTable>(FName(*FString::FromInt(rowindex)), TEXT("Look Up"));
		if (LookUpRow)
		{
			LookUpRow->Input = LookUpRow->DefaultInput;
			DrawKeyBindingWidget();
			rowindex++;
		}
		else
		{
			break;
		}
	}
	return FReply::Handled();
}

FReply SKeyConfigUI::InputCaterGoryButonClicked(FString Category)
{
	
	if (Category == "General")
	{
		GeneralButton->SetButtonStyle(&MenuStyle->KeyConfigHeaderStyle);
		MovementButton->SetButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle);
		CombatButton->SetButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle);
		MiscellaneousButton->SetButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle);
		KeyBindingWidget->SetActiveWidgetIndex(0);
	}
	else if (Category == "Movement")
	{
		GeneralButton->SetButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle);
		MovementButton->SetButtonStyle(&MenuStyle->KeyConfigHeaderStyle);
		CombatButton->SetButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle);
		MiscellaneousButton->SetButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle);
		KeyBindingWidget->SetActiveWidgetIndex(1);
	}
	else if (Category == "Combat")
	{
		GeneralButton->SetButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle);
		MovementButton->SetButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle);
		CombatButton->SetButtonStyle(&MenuStyle->KeyConfigHeaderStyle);
		MiscellaneousButton->SetButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle);
		KeyBindingWidget->SetActiveWidgetIndex(2);
	}
	else if (Category == "Miscellaneous")
	{
		GeneralButton->SetButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle);
		MovementButton->SetButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle);
		CombatButton->SetButtonStyle(&MenuStyle->KeyConfigNotSelectedStyle);
		MiscellaneousButton->SetButtonStyle(&MenuStyle->KeyConfigHeaderStyle);
		KeyBindingWidget->SetActiveWidgetIndex(3);
		TSharedPtr<class SWidget> border = SNew(SCanvas);
	 
	}
		
	return FReply::Handled();
}

FReply SKeyConfigUI::KeyCustomizationButtonClicked(FString Key, FString Value , int32 Index)
{
	Selectedkey = Key;
	SelectedOldValue = Value;
	SelectedIndex = Index-1;
	CustomKeyWidget = SNew(SOverlay)
		+ SOverlay::Slot()
		.HAlign(HAlign_Fill)
		.VAlign(VAlign_Fill).Padding(400)
		
		
			SNew(SBorder)
			.BorderImage(&MenuStyle->BorderBrush2)
			.VAlign(VAlign_Center)
			.Content()
			
				SNew(SScrollBox)
				+ SScrollBox::Slot()
				.HAlign(HAlign_Center)
				
					SNew(STextBlock)
					.Text(FText::FromString("Press any key to map to"))
					.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 15))
					.ColorAndOpacity(FLinearColor(0.7, 0.6, 0.1, 1))
				]
				+ SScrollBox::Slot()
					.HAlign(HAlign_Center)
					
						SNew(STextBlock)
						.Text(FText::FromString(Key))
						.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 17))
						.ColorAndOpacity(FLinearColor(0.46, 0.77, 0.73, 1))
					]
				+ SScrollBox::Slot()
					.HAlign(HAlign_Center)
					
						SNew(STextBlock)
						.Text(FText::FromString("Press ESC to cancel"))
						.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 15))
						.ColorAndOpacity(FLinearColor(0.46, 0.77, 0.73, 1))
						.ColorAndOpacity(FLinearColor(0.7, 0.6, 0.1, 1))
					]
			]

		];
	
	GEngine->GameViewport->AddViewportWidgetContent(CustomKeyWidget.ToSharedRef());
	KeyBindingWidget->SetEnabled(false);
	ParentWidget->SetEnabled(false);
	
    return FReply::Handled();
}

FReply SKeyConfigUI::OnKeyDown(const FGeometry& MyGeometry, const FKeyboardEvent& InKeyboardEvent)
{
	GEngine->GameViewport->RemoveViewportWidgetContent(CustomKeyWidget.ToSharedRef());
	if (InKeyboardEvent.GetKey() == EKeys::Escape)
	{
		KeyBindingWidget->SetEnabled(true);
		ParentWidget->SetEnabled(true);
	}
	else
	{
		NewKey = InKeyboardEvent.GetKey();
	    KeyBindingWidget->SetEnabled(true);
		ParentWidget->SetEnabled(true);
		FKeyBoardBindingTable* LookUpRow = GameHUD->KeyBoardBindingTable->FindRow<FKeyBoardBindingTable>(*FString::FromInt(SelectedIndex+1), TEXT("Look Up"));
		int32 widgetIndex;
		if (NewKey != FKey(FName(*LookUpRow->Input)))
		{
			TempIndex.Add(FString::FromInt(SelectedIndex + 1));
			TempInput.Add(NewKey.ToString());
			int index =KeyBindingWidget->GetActiveWidgetIndex();
			switch (index)
			{
			case 0: 
				GeneralWidget->RemoveSlot(SelectedIndex);
				GeneralWidget->InsertSlot(SelectedIndex).HAlign(HAlign_Fill).Padding(10)
					
						
						
						DrawKeyBindingWidgetRow(LookUpRow->Action, NewKey.ToString(), LookUpRow->Category, SelectedIndex + 1).ToSharedRef()
					];
				break;
			case 1:
				widgetIndex = SelectedIndex - GeneralIndex;
				MovementWidget->RemoveSlot(widgetIndex);
				MovementWidget->InsertSlot(widgetIndex).HAlign(HAlign_Fill).Padding(10)
					

						DrawKeyBindingWidgetRow(LookUpRow->Action, NewKey.ToString(), LookUpRow->Category, SelectedIndex + 1).ToSharedRef()
					];
				break;
			case 2:
				widgetIndex = SelectedIndex - GeneralIndex - MovementIndex;
				CombatWidget->RemoveSlot(widgetIndex);
				CombatWidget->InsertSlot(widgetIndex).HAlign(HAlign_Fill).Padding(10)
					

						DrawKeyBindingWidgetRow(LookUpRow->Action, NewKey.ToString(), LookUpRow->Category, SelectedIndex + 1).ToSharedRef()
					];
				break;
			case 3:
				widgetIndex = SelectedIndex - GeneralIndex - MovementIndex  - CombatIndex;
				MiscellaneousWidget->RemoveSlot(widgetIndex);
				MiscellaneousWidget->InsertSlot(widgetIndex).HAlign(HAlign_Fill).Padding(10)
					

						DrawKeyBindingWidgetRow(LookUpRow->Action, NewKey.ToString(), LookUpRow->Category, SelectedIndex + 1).ToSharedRef()
					];
				break;
			default:
				break;
			}
		}
	}

	return FReply::Handled();
}

OK , and the below is my header class , in case you are confused with the above code.


class FORGOTTENLEGION_API SKeyConfigUI : public SCompoundWidget
{

	SLATE_BEGIN_ARGS(SKeyConfigUI)
	{
	}

	SLATE_ARGUMENT(TWeakObjectPtr<class AGameHUD>, GameHUD);
	SLATE_END_ARGS()

	

public:
	/** Constructs this widget with InArgs */
	void Construct(const FArguments& InArgs);
	bool FileIO__SaveStringTextToFile(FString SaveDirectory, FString FileName, FString SaveText, bool AllowOverWriting);
	bool FileIO_ReadFileToString(FString FileDirectory, FString FilePath);

	/** Overridden from SWidget: Called when a key is pressed down - capturing copy */
	virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyboardEvent& InKeyboardEvent) override;
private:
	
	SScrollBox::FSlot& InsertSlot(int32 Index);
	FReply InputCaterGoryButonClicked(FString Category);
	
	TSharedPtr<class SHorizontalBox> DrawKeyBindingWidgetRow(FString Action, FString Input, FString Category , int32 Index);
	void DrawKeyBindingWidget();

	const struct FGlobalStyle* MenuStyle;
	FReply SaveButtonClicked();
	FReply CancelButtonClicked();
	FReply ResetButtonClicked();
	FReply KeyCustomizationButtonClicked(FString Key, FString Value , int32 Index);
	
	TWeakObjectPtr<class AGameHUD> GameHUD;
	TSharedPtr<class SWidgetSwitcher> KeyBindingWidget = SNew(SWidgetSwitcher);
	TSharedRef<FSlateGameResources> SlateResources = FSlateGameResources::New(FName("BorderBrush1"), "/Game/Blueprints/Widgets/Styles", "/Game/Blueprints/Widgets/Styles");
	FSlateGameResources& Style = SlateResources.Get();
	const FSlateBrush* BorderBrush1 = Style.GetBrush("BorderBrush1");
	FLinearColor SelectedButtonColor = FLinearColor(0.58, 0.74, 0.89, 1);

	FString Selectedkey;
	FString SelectedOldValue;
	int32 SelectedIndex;
	
	int32 GeneralIndex = 0;;
	int32 MovementIndex = 0;
	int32 CombatIndex = 0;
	int32 MiscellaneousIndex = 0;

	TArray<FString> TempIndex;
	TArray<FString> TempInput;
	
	FKey NewKey;
	TSharedPtr<class SButton> GeneralButton = SNew(SButton);
	TSharedPtr<class SButton> MovementButton = SNew(SButton);
	TSharedPtr<class SButton> CombatButton = SNew(SButton);
	TSharedPtr<class SButton> MiscellaneousButton = SNew(SButton);

	TSharedPtr<class STextBlock> GeneralText = SNew(STextBlock);
	TSharedPtr<class STextBlock> MovementText = SNew(STextBlock);
	TSharedPtr<class STextBlock> CombatText = SNew(STextBlock);
	TSharedPtr<class STextBlock> MiscellaneousText = SNew(STextBlock);
	
	TSharedPtr<class SScrollBox> ParentWidget = SNew(SScrollBox);
	
	TSharedPtr<class SScrollBox> GeneralWidget = SNew(SScrollBox);
	TSharedPtr<class SScrollBox> MovementWidget = SNew(SScrollBox);
	TSharedPtr<class SScrollBox> CombatWidget = SNew(SScrollBox);
	TSharedPtr<class SScrollBox> MiscellaneousWidget = SNew(SScrollBox);

	TSharedPtr<class STextBlock> BindingText = SNew(STextBlock);
	TSharedPtr<class SButton> BindingButton = SNew(SButton);
	TSharedPtr<class SOverlay> CustomKeyWidget = SNew(SOverlay);
	
};


Now you can easily add this UI in your viewport in your HUD class , whenever required , using the below line


SAssignNew(KeyConfigUI, SKeyConfigUI).GameHUD(this);
	if (GEngine && GEngine->GameViewport)
	{
	     Viewport = GEngine->GameViewport;

		Viewport->AddViewportWidgetContent(
			SNew(SWeakWidget).PossiblyNullContent(KeyConfigUI.ToSharedRef())
			);
		}


Now our keyconfiguraiton UI is ready(please let me know if you find any bug). SO next I need to tell unreal engine , that whatever current value is present in the Input column of the datatable , I am supposed to use that for desired set of action. So for that I wrote the following code in my playercharacter class. Here , make sure you include the below namespaces


#include "Runtime/Engine/Classes/GameFramework/PlayerInput.h"
#include "Runtime/Engine/Classes/GameFramework/InputSettings.h"
#include "Runtime/CoreUObject/Public/UObject/UObjectGlobals.h"



void AForgottenLegionCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	// Set up gameplay key bindings
	check(InputComponent);
	const UInputSettings* InputSettings = GetDefault<UInputSettings>();
	int rowindex = 1;
	while (true)
	{
		FKeyBoardBindingTable* LookUpRow = KeyBoardBindingTable->FindRow<FKeyBoardBindingTable>(FName(*FString::FromInt(rowindex)), TEXT("Look Up"));
		if (LookUpRow)
		{
			if (LookUpRow->Action == "Move Forward")
			{
				const FInputAxisKeyMapping axismapping(FName("Move Forward"), FKey(FName(*LookUpRow->Input)) , 1);
		    	((UInputSettings*)InputSettings)->AddAxisMapping(axismapping);
		}
			else if (LookUpRow->Action == "Move Backward")
			{
				const FInputAxisKeyMapping axismapping(FName("Move Forward"), FKey(FName(*LookUpRow->Input)), -1);
		    	((UInputSettings*)InputSettings)->AddAxisMapping(axismapping);
		
			}
			else if (LookUpRow->Action == "Move Left")
			{
				const FInputAxisKeyMapping axismapping(FName("Move Right"), FKey(FName(*LookUpRow->Input)), -1);
		    	((UInputSettings*)InputSettings)->AddAxisMapping(axismapping);
			}
			else if (LookUpRow->Action == "Move Right")
			{
				const FInputAxisKeyMapping axismapping(FName("Move Right"), FKey(FName(*LookUpRow->Input)), 1);
			 	((UInputSettings*)InputSettings)->AddAxisMapping(axismapping);
			}
			
		   else
			{
			   const FInputActionKeyMapping actionmapping(FName(*LookUpRow->Action), FKey(FName(*LookUpRow->Input)), false , false , false , false);
			   ((UInputSettings*)InputSettings)->AddActionMapping(actionmapping);
		   }
			rowindex++;
		
		}
		else
		{
			break;
		}
	}

	const FInputAxisKeyMapping turnaxismapping(FName("Turn"), FKey(FName("MouseX")), 1);
	((UInputSettings*)InputSettings)->AddAxisMapping(turnaxismapping);
	
	const FInputAxisKeyMapping lookupaxismapping(FName("LookUp"), FKey(FName("MouseY")), 1);
	((UInputSettings*)InputSettings)->AddAxisMapping(lookupaxismapping);

	((UInputSettings*)InputSettings)->SaveKeyMappings();
	
	InputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);

	InputComponent->BindAxis("Move Forward", this, &AForgottenLegionCharacter::MoveForward);
	InputComponent->BindAxis("Move Right", this, &AForgottenLegionCharacter::MoveRight);
	InputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
	InputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);

	
}

And thats it, when you hit on play , all this settings will be saved and you can verify by going to the project settings->input section.

Thanks and Regards ,
Sameek Kundu

2 Likes

Use code tag, it’s impossible to read anything.

Done. Didn’t know this. :slight_smile: Thanks

Thanks for the code and the time put into this document! There were a few things not mentioned so just to let you know. Please let me know if the references are incorrect.

missing from hud

#include “Engine\GameViewportClient.h”

UGameViewportClient* Viewport;
SKeyConfigUI* KeyConfigUI; <<----- ?

missing within KeyConfigUI.h

#include “Slate.h”
#include “SlateGameResources.h”
#include “GlobalMenuStyle.h”
#include “MenuStyles.h”

The creation of the MenuStyles and GlobalStyle classes are not mentioned but the necessary code is @ HugeDomains.com

Missing within GlobalMenuStyle.h

FButtonStyle KeyConfigNotSelectedStyle;
FButtonStyle KeyConfigHeaderStyle;
FButtonStyle KeyBindingButtonStyle;
FButtonStyle GeneralButtonStyle1;

FSlateBrush BorderBrush1;
FSlateBrush BorderBrush2;

Missing from Character

UDataTable* KeyBoardBindingTable;
void MoveForward(float Val);
void MoveRight(float Val)

AddSlot and RemoveSlot takes type "const TSharedRef<SWidget>& " not an int32 .

Thank you! I have gotten it to build although I haven’t tested it’s functionality yet.

Hi ,

Sorry for not mentioning this , but insertslot and removeslot are my own functions , which are given as follows :

SScrollBox.h

SScrollBox::FSlot& InsertSlot(int32 Index);
SScrollBox::FSlot& RemoveSlot(int32 Index);

SScrollBox.cpp

SScrollBox::FSlot& SScrollBox::InsertSlot(int32 Index)

{

SScrollBox::FSlot& NewSlot = SScrollBox::Slot();

ScrollPanel-&gt;Children.Insert(&NewSlot , Index);


    return NewSlot;

}

SScrollBox::FSlot& RemoveSlot(int32 Index)
{
SScrollBox::FSlot& NewSlot = SScrollBox::Slot();
ScrollPanel->Children.RemoveAt(Index);
return NewSlot;
}

Thank you very much!