Can I adjust input actions via c++?

And if so, how?

I’m using the enchanced input action system. What I want to do is set an input action’s Pulse parameters using code. Basically say something like “When X is equipped, the pulse limit is 10. When Y is equipped, the pulse limit is 5.”

Is there a way to do this in code? I’ve tried checking the documentation, googling and chat gpting. If there was an obvious place where this information was listed I’d appreciate a heads up of what I missed please.

I think there’s a few ways to do it, but here’s what I would do.

  1. Create 2 input actions: One for when the fire button is pressed, and one for when the the fire button is released.
  2. In the function called by the pressed delegate, do something repeatedly based on what you have equipped. You can use the tick function for this or the timer manager.
  3. When the released delegate is called, stop firing.

Here’s the general idea:

void AMyPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();
	auto enhancedInput = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(this->GetLocalPlayer());
	if (enhancedInput)
	{
		enhancedInput->AddMappingContext(playerInputContext, 0);
		auto enhancedInputComponent = Cast<UEnhancedInputComponent>(InputComponent);
		if (enhancedInputComponent)
		{
			enhancedInputComponent->BindAction(beginFireAction, ETriggerEvent::Triggered, this, &AMyPlayerController::OnBeginFireAction);
			enhancedInputComponent->BindAction(endFireAction, ETriggerEvent::Triggered, this, &AMyPlayerController::OnEndFireAction);
		}
	}
}

void AMyPlayerController::OnBeginFireAction(const FInputActionValue& value)
{
	if (IsUsingPistol())
	{
		fireRate = 5.f;
	}
	else if (IsUsingRailGun())
	{
		fireRate = 0.5f;
	}

	BeginFiring();
}

void AMyPlayerController::OnEndFireAction(const FInputActionValue& value)
{
	StopFiring();
}

beginFireAction InputAction:
BeginFire

endFireAction InputAction:
EndFire

That does give me a good idea of a work around if it’s really impossible to call and adjust trigger parameters in code and I appreciate it.

I do also feel like there should be a simpler way though, especially when just being able to tweak a couple values (that already exist and are defined) would cleanly suffice.

I think because the InputAction is a DataAsset that is bound to a delegate, it’s not so simple to just adjust a value on the fly.

You would have to unmap the input, map the input again, and then attach the new pulse trigger with different settings.

Another way to change things if you don’t want to use the method I mentioned earlier is to have multiple mapping contexts, and switch them based on the weapon you are using.

I’ve just come across this:

It looks like worst case I can just create my own accessible trigger. Though I feel like I should be able to access the premade triggers this way too. I don’t have the time to fully dive into it tonight but will update if this proves to lead me to the solution.

He’s creating a custom trigger, but he isn’t setting it in realtime…
That’s no different than changing the values on the existing pulse trigger when you set up the InputAction before running the game.

Not sure how I could have an accessible file with the trigger parameters and not be able to modify them at run as desired, but I’ll update when I have the time to dive in.

And of course the video isn’t going to have my exact use case. I find few rarely do and I often have to find something that opens up a path to me figuring out the answer nowadays.

Edit: Even if I don’t update the trigger I don’t see why I couldn’t add a switch based on inserting an outside variable into the input action itself.

Figured out a way to change triggers and their values dynamically in realtime, but even then the engine itself just oddly acts like you didn’t. So guess you win.

Don’t understand the idea of public variables that can’t be changed dynamically but guess it’s a thing.

@LockeKosta, I know this is an old post, but you can modify an Input Action at run time, but you have to Flush and Rebuild the Input Mapping Context. Flush being the key part.

For example, if you have an Input Action with a Pulse Trigger. In blueprints get a reference to the Input Action get the array called Triggers from it. Find the Pulse Trigger in the array. From the Pulse Trigger Set Interval to something new(Default value is 1.0 for pulse intervals). Then call Request Rebuild Control Mapping with the Target being the Enhanced Input Local Player Subsystem and the Rebuild Type set to Rebuild with Flush.

The process has to be similar in C++ though I haven’t tried it yet. If I have any success, I’ll share my code here for posterity.

1 Like

Here’s the main snippet of code I wrote and haven’t refactored. The element 0 of the array is an UInputTriggerPulse because that’s the only member of the array I added in the Editor. If you had more elements, or just wanted to do things correctly you need to make sure the element was valid etc. Once you have the Pulse Trigger element from the array you simple set it. Then Get the Local Player Subsystem and Rebuild with Flush.

UInputTrigger* PulsePtr = IA_PrimaryFire->Triggers[0];
        Cast<UInputTriggerPulse>(PulsePtr)->Interval = 1.f;

        ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>
            (GetLocalPlayer())->RequestRebuildControlMappings
            (
                Options, 
                EInputMappingRebuildType::RebuildWithFlush
            );

Options is a FModifyContextOptions struct defined in the EnhancedInputSubsystemInterface.

You sure that works at runtime and packaged? I got a good bit of false positives when I was attempting it myself.

If it does work, that’s cool but also wholly unrealistic to go through every time for the use-case I was working on. That was kinda the end conclusion I came to. Even if I did get it to work I’d be going around my elbow to scratch my ■■■.

I appreciate the further insight though!

2 Likes

It packages and works at run time in the package. I agree that there is definitely a more sensible way of accomplishing this like just using timers. However, if you really need the input to control the frequency at which an action occurs because the action needs to be decoupled in that way. Then this works fine.

My understanding is that the Input Action data asset is a config file(?) when you make a change to the config file it doesn’t do anything until UE flushes what it knows about the input mapping context, and rebuilds it from the config file. Could be wrong, though.

Here’s a quick player controller I threw together so you can look at it yourself. Sorry, I don’t know a better way to easily share this.

In the editor make an Input Action named IA_PrimaryFire with a pulse trigger, and an Input Mapping Context named IMC_InGame with IA_PrimaryFire mapped to it.

Derive a blueprint from the MyPlayerController.cpp class. In the World Settings of the level make sure you are setting this derived player controller as the player controller to use. In the details populate it with the IMC and IA you just made. Then on BeginPlay call UpdateFiringInterval pass in IA_PrimaryFire, and set the float to 0.5. Then add a delay for a few seconds and call the function again with a different float like 5.0. In the log while the input action is active it’ll output 0.5 every 0.5 seconds. After the delay is done it will output 5.0 every 5 seconds.

MyPlayerController.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "InputActionValue.h"
#include "EnhancedInputSubsystemInterface.h"
#include "MyPlayerController.generated.h"

class UInputMappingContext;
class UInputAction;
class UEnhancedInputComponent;

UCLASS()
class YOUR_API AMyPlayerController : public APlayerController
{
	GENERATED_BODY()

protected:

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Enhanced Inoput")
	FModifyContextOptions Options;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Enhanced Input")
	class UInputMappingContext* IMC_InGame;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Enhanced Input")
	UInputAction* IA_PrimaryFire;

public:

	// Called to bind functionality to input
	virtual void SetupInputComponent() override;

	/**
	* Take in an Input Action that has a Pulse Trigger. 
	* Change the Pulse Triggers Interval
	* Flush and rebuild the Input Mapping Context to apply the changes to the Pulse Trigger
	*
	* @param IA_Trigger Input Action that contains a Pulse Trigger in its Trigger Array at element 0
	* @param NewInterval The new timing in seconds to set the Pulse Trigger Interval to
	*/
	UFUNCTION(BlueprintCallable, Category = "State")
	void UpdateFiringInterval(UInputAction* IA_Trigger, float NewInterval);
	
	UFUNCTION(BlueprintCallable, Category = "Input Actions")
	void PrimaryFire(const FInputActionValue& Value);

private:

	UEnhancedInputComponent* EnhancedInputComponent;
};

MyPlayerController.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "MyPlayerController.h"
#include "InputMappingContext.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"
#include "Components/InputComponent.h"
#include "InputTriggers.h"
#include "Kismet/GameplayStatics.h"

void AMyPlayerController::SetupInputComponent()
{
    Super::SetupInputComponent();

    // Get/Validate Local Player Subsystem
    // Add Mapping Context -- Default is IMC_InGame
    UEnhancedInputLocalPlayerSubsystem* Subsystem =
        ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>
        (GetLocalPlayer());
    if(Subsystem)
    {
        Subsystem->ClearAllMappings();
        Subsystem->AddMappingContext(IMC_InGame, 0);
    }

    // Get/Validate Enhanced Input Component
    // Set Input Action Bindings
    EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent);
    if(EnhancedInputComponent)
    {
        EnhancedInputComponent->BindAction(
            IA_PrimaryFire,
            ETriggerEvent::Triggered,
            this,
            &AMyPlayerController::PrimaryFire
        );
    }
    
}

void AMyPlayerController::UpdateFiringInterval(UInputAction* IA_Trigger, float NewInterval)
{
        // Need to validate parameters

        // Set the Pulse Trigger to the new Interval
        Cast<UInputTriggerPulse>(IA_Trigger->Triggers[0])->Interval = NewInterval;

        // Flush and rebuild the IMC - Must be flushed in order for Interval change to take effect
        ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>
            (GetLocalPlayer())->RequestRebuildControlMappings
            (
                Options, 
                EInputMappingRebuildType::RebuildWithFlush
            );
}

void AMyPlayerController::PrimaryFire(const FInputActionValue &Value)
{
    {
        const bool CurrentValue = Value.Get<bool>();

	    if(CurrentValue)
	    {
	    	UE_LOG(LogTemp, Warning, TEXT("%f"), Cast<UInputTriggerPulse>(IA_PrimaryFire->Triggers[0])->Interval);
	    }
    }
}

For testing a package just launch the .exe from a command line with the -log flag. :+1:

1 Like

That’s really good, thank you