ButtonRepeatDelay

ButtonRepeatDelay

#Overview

name: ButtonRepeatDelay

The value of this variable can be defined or overridden in .ini config files. 1 .ini config file referencing this setting variable.

It is referenced in 15 C++ source files.

#Summary

#Usage in the C++ source code

The purpose of ButtonRepeatDelay is to control the delay between repeated button press events when a button is held down on input devices such as controllers or gamepads.

This setting variable is primarily used in input handling systems across various platforms and input devices in Unreal Engine 5. Based on the callsites, it’s utilized in the following subsystems and plugins:

  1. OpenXR Input Plugin
  2. Steam Controller Plugin
  3. XInput Device Plugin (for Windows)
  4. Android Input Interface
  5. HID Input Interface (for Mac)

The value of this variable is typically set in the engine configuration files, specifically in the GInputIni file under the “/Script/Engine.InputSettings” section. It’s often read alongside another variable called InitialButtonRepeatDelay, which sets the initial delay before the first repeat event.

ButtonRepeatDelay interacts with other variables such as:

Developers should be aware of the following when using this variable:

  1. It affects the responsiveness of held button inputs across various input devices.
  2. The value is in seconds for some implementations and in nanoseconds for others (e.g., OpenXR uses nanoseconds).
  3. It’s platform-specific and may behave differently across different input systems.

Best practices when using this variable include:

  1. Ensure the value is appropriate for your game’s input requirements. A shorter delay increases responsiveness but may lead to unintended rapid input.
  2. Consider exposing this setting to players, allowing them to adjust it to their preferences.
  3. Test thoroughly across all supported input devices and platforms to ensure consistent behavior.
  4. Be mindful of the units (seconds vs. nanoseconds) when setting or reading this value in different subsystems.
  5. Consider the relationship between InitialButtonRepeatDelay and ButtonRepeatDelay for a smooth input experience.

#Setting Variables

#References In INI files

Location: <Workspace>/Engine/Config/BaseInput.ini:20, section: [/Script/Engine.InputSettings]

#References in C++ code

#Callsites

This variable is referenced in the following C++ source code:

#Loc: <Workspace>/Engine/Plugins/Runtime/OpenXR/Source/OpenXRInput/Private/OpenXRInput.cpp:971

Scope (from outer to inner):

file
function     void FOpenXRInputPlugin::FOpenXRInput::SendControllerEvents

Source code excerpt:

					// TODO: We should retrieve the current time rather than the display time
					MessageHandler->OnControllerButtonPressed(Action.Name, DeviceMapper.GetPrimaryPlatformUser(), DeviceMapper.GetDefaultInputDevice(), /*IsRepeat =*/true);
					Action.NextRepeatTime = OpenXRHMD->GetDisplayTime() + ButtonRepeatDelay;
				}
			}
			break;
			case XR_ACTION_TYPE_FLOAT_INPUT:
			{
				XrActionStateFloat State;

#Loc: <Workspace>/Engine/Plugins/Runtime/OpenXR/Source/OpenXRInput/Private/OpenXRInput.h:177

Scope (from outer to inner):

file
class        class FOpenXRInputPlugin : public IOpenXRInputPlugin
class        class FOpenXRInput : public IOpenXRInputModule, public IInputDevice, public FXRMotionControllerBase, public IHapticDevice, public TSharedFromThis<FOpenXRInput>

Source code excerpt:

		/** Repeat key delays */
		const XrTime InitialButtonRepeatDelay = 2e8;
		const XrTime ButtonRepeatDelay = 1e8;

		bool BuildActions(XrSession Session);
		void SyncActions(XrSession Session);
		void BuildLegacyActions(TMap<FString, FInteractionProfile>& Profiles);
		void BuildEnhancedActions(TMap<FString, FInteractionProfile>& Profiles);
		void DestroyActions();

#Loc: <Workspace>/Engine/Plugins/Runtime/Steam/SteamController/Source/SteamController/Private/SteamController.cpp:27

Scope (from outer to inner):

file
class        class FSteamController : public IInputDevice
function     FSteamController

Source code excerpt:

	FSteamController(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler) :
		InitialButtonRepeatDelay(0.2),
		ButtonRepeatDelay(0.1),
		MessageHandler(InMessageHandler),
		bSteamControllerInitialized(false),
		InputSettings(nullptr)
	{
		GConfig->GetDouble(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni);
		GConfig->GetDouble(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni);

		// Initialize the API, so we can start calling SteamController functions
		SteamAPIHandle = FSteamSharedModule::Get().ObtainSteamClientInstanceHandle();

		// [RCL] 2015-01-23 FIXME: move to some other code than constructor so we can handle failures more gracefully
		if (SteamAPIHandle.IsValid() && (SteamInput() != nullptr))

#Loc: <Workspace>/Engine/Plugins/Runtime/Steam/SteamController/Source/SteamController/Private/SteamController.cpp:153

Scope (from outer to inner):

file
class        class FSteamController : public IInputDevice
function     virtual void SendControllerEvents

Source code excerpt:

				{
 					MessageHandler->OnControllerButtonPressed(DigitalNamesToKeysMap[DigitalActionName].GetFName(), UserId, DeviceId, false);
					ControllerState.DigitalRepeatTimeMap[DigitalActionName] = FPlatformTime::Seconds() + ButtonRepeatDelay;
				}
				else if (ControllerState.DigitalStatusMap[DigitalActionName] == true && !DigitalActionData.bState)
				{
					MessageHandler->OnControllerButtonReleased(DigitalNamesToKeysMap[DigitalActionName].GetFName(), UserId, DeviceId, false);
				}
				else if (ControllerState.DigitalStatusMap[DigitalActionName] == true && DigitalActionData.bState && ControllerState.DigitalRepeatTimeMap[DigitalActionName] <= CurrentTime)
				{
					ControllerState.DigitalRepeatTimeMap[DigitalActionName] += ButtonRepeatDelay;
					MessageHandler->OnControllerButtonPressed(DigitalNamesToKeysMap[DigitalActionName].GetFName(), UserId, DeviceId, true);
				}

				ControllerState.DigitalStatusMap[DigitalActionName] = DigitalActionData.bState;
			}

#Loc: <Workspace>/Engine/Plugins/Runtime/Steam/SteamController/Source/SteamController/Private/SteamController.cpp:325

Scope (from outer to inner):

file
class        class FSteamController : public IInputDevice

Source code excerpt:


	/** Delay before sending a repeat message after a button has been pressed for a while */
	double ButtonRepeatDelay;

	/** handler to send all messages to */
	TSharedRef<FGenericApplicationMessageHandler> MessageHandler;

	/** SteamAPI initialized **/
	TSharedPtr<class FSteamClientInstanceHandler> SteamAPIHandle;

#Loc: <Workspace>/Engine/Plugins/Runtime/Windows/XInputDevice/Source/XInputDevice/Private/XInputInterface.cpp:43

Scope (from outer to inner):

file
function     XInputInterface::XInputInterface

Source code excerpt:

	bNeedsControllerStateUpdate = true;
	InitialButtonRepeatDelay = 0.2f;
	ButtonRepeatDelay = 0.1f;

	GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni);
	GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni);

	// In the engine, all controllers map to xbox controllers for consistency 
	X360ToXboxControllerMapping[0] = 0;		// A
	X360ToXboxControllerMapping[1] = 1;		// B
	X360ToXboxControllerMapping[2] = 2;		// X
	X360ToXboxControllerMapping[3] = 3;		// Y

#Loc: <Workspace>/Engine/Plugins/Runtime/Windows/XInputDevice/Source/XInputDevice/Private/XInputInterface.cpp:266

Scope (from outer to inner):

file
function     void XInputInterface::SendControllerEvents

Source code excerpt:

					MessageHandler->OnControllerButtonPressed( Buttons[ButtonIndex], PlatformUser, InputDevice, true );

					// set the button's NextRepeatTime to the ButtonRepeatDelay
					ControllerState.NextRepeatTime[ButtonIndex] = CurrentTime + ButtonRepeatDelay;
				}

				// Update the state for next time
				ControllerState.ButtonStates[ButtonIndex] = CurrentStates[ButtonIndex];
			}	

#Loc: <Workspace>/Engine/Plugins/Runtime/Windows/XInputDevice/Source/XInputDevice/Private/XInputInterface.h:134

Scope (from outer to inner):

file
class        class XInputInterface : public IInputDevice

Source code excerpt:


	/** Delay before sending a repeat message after a button has been pressed for a while */
	float ButtonRepeatDelay;

	/**  */
	FGamepadKeyNames::Type Buttons[MAX_NUM_CONTROLLER_BUTTONS];

	/**  */
	TSharedRef<FGenericApplicationMessageHandler> MessageHandler;

#Loc: <Workspace>/Engine/Source/Runtime/ApplicationCore/Private/Android/AndroidInputInterface.cpp:38

Scope: file

Source code excerpt:

FGamepadKeyNames::Type FAndroidInputInterface::ButtonMapping[MAX_NUM_CONTROLLER_BUTTONS];
float FAndroidInputInterface::InitialButtonRepeatDelay;
float FAndroidInputInterface::ButtonRepeatDelay;

FDeferredAndroidMessage FAndroidInputInterface::DeferredMessages[MAX_DEFERRED_MESSAGE_QUEUE_SIZE];
int32 FAndroidInputInterface::DeferredMessageQueueLastEntryIndex = 0;
int32 FAndroidInputInterface::DeferredMessageQueueDroppedCount   = 0;

TArray<FAndroidInputInterface::MotionData> FAndroidInputInterface::MotionDataStack

#Loc: <Workspace>/Engine/Source/Runtime/ApplicationCore/Private/Android/AndroidInputInterface.cpp:131

Scope (from outer to inner):

file
function     FAndroidInputInterface::FAndroidInputInterface

Source code excerpt:


	InitialButtonRepeatDelay = 0.2f;
	ButtonRepeatDelay = 0.1f;

	GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni);
	GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni);

	CurrentVibeIntensity = 0;
	FMemory::Memset(VibeValues, 0);
	
	FMemory::Memset(DeviceMapping, 0);
	FMemory::Memset(OldControllerData, 0);

#Loc: <Workspace>/Engine/Source/Runtime/ApplicationCore/Private/Android/AndroidInputInterface.cpp:1337

Scope (from outer to inner):

file
function     void FAndroidInputInterface::SendControllerEvents

Source code excerpt:

					MessageHandler->OnControllerButtonPressed(ButtonMapping[ButtonIndex], UserId, DeviceId, true);

					// Set the button's NextRepeatTime to the ButtonRepeatDelay
					NewControllerState.NextRepeatTime[ButtonIndex] = CurrentTime + ButtonRepeatDelay;
				}
			}

			// send controller force feedback updates if enabled
			if (GAndroidUseControllerFeedback != 0)
			{

#Loc: <Workspace>/Engine/Source/Runtime/ApplicationCore/Private/Mac/HIDInputInterface.cpp:37

Scope (from outer to inner):

file
function     HIDInputInterface::HIDInputInterface

Source code excerpt:

	bIsGamepadAttached = false;
	InitialButtonRepeatDelay = 0.2f;
	ButtonRepeatDelay = 0.1f;

	GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni);
	GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni);

	Buttons[0] = FGamepadKeyNames::FaceButtonBottom;
	Buttons[1] = FGamepadKeyNames::FaceButtonRight;
	Buttons[2] = FGamepadKeyNames::FaceButtonLeft;
	Buttons[3] = FGamepadKeyNames::FaceButtonTop;
	Buttons[4] = FGamepadKeyNames::LeftShoulder;

#Loc: <Workspace>/Engine/Source/Runtime/ApplicationCore/Private/Mac/HIDInputInterface.cpp:584

Scope (from outer to inner):

file
function     void HIDInputInterface::SendControllerEvents

Source code excerpt:

					MessageHandler->OnControllerButtonPressed(Buttons[ButtonIndex], UserId, DeviceId, true);

					// set the button's NextRepeatTime to the ButtonRepeatDelay
					ControllerState.NextRepeatTime[ButtonIndex] = CurrentTime + ButtonRepeatDelay;
				}

				// Update the state for next time
				ControllerState.ButtonStates[ButtonIndex] = CurrentButtonStates[ButtonIndex];
			}	
		}

#Loc: <Workspace>/Engine/Source/Runtime/ApplicationCore/Private/Mac/HIDInputInterface.h:116

Scope (from outer to inner):

file
class        class HIDInputInterface

Source code excerpt:


	/** Delay before sendign a repeat message after a button has been pressed for a while */
	float ButtonRepeatDelay;

	bool bIsGamepadAttached;

	IOHIDManagerRef HIDManager;

	TSharedRef<FGenericApplicationMessageHandler> MessageHandler;

#Loc: <Workspace>/Engine/Source/Runtime/ApplicationCore/Public/Android/AndroidInputInterface.h:362

Scope (from outer to inner):

file
class        class FAndroidInputInterface : public IInputInterface

Source code excerpt:


	static float InitialButtonRepeatDelay;
	static float ButtonRepeatDelay;

	static FDeferredAndroidMessage DeferredMessages[MAX_DEFERRED_MESSAGE_QUEUE_SIZE];
	static int32 DeferredMessageQueueLastEntryIndex;
	static int32 DeferredMessageQueueDroppedCount;

	static TArray<MotionData> MotionDataStack;