Check for increase in Input value over given period time?

Hey there,

So what I’m trying to accomplish is have method be triggered when the Input value from the controller increases rapidly over a given period time. Example: XAxis goes from 0.1f to 0.85f in say 0.5 Seconds. I’m trying to find a way to do this inside of the Tick function.

//Check to see if user's input happened fast enough to trigger a dash
float Input = InputComponent->GetAxisValue("LStick_XAxis");
UE_LOG(LogTemp, Warning, TEXT("Input: %f"), Input);
	
if (Input > 0.2f &&  Input < 0.3f || Input < -0.2f &&  Input > -0.3f) {
	UE_LOG(LogTemp, Warning, TEXT("Trigger: DASHCHECK"));
}

This will catch the value, however it only catches the Input sometimes, I think the reason for this is because if I quickly move the Left stick left or right, it jumps from one value to the next, which could be like a lot larger on the 2nd tick. Which is why I need to check for an increase in the Input value over time instead.

Any idea on how to approach this?
Thanks :slight_smile:

I would add an axis binding that stores the given Axis value to a float variable, and then build your ticking method to check that variable value rather than querying the input axis directly. You can then store the current value (and however many previous values you like) during your tick update. You could also set up an array to store the previous input values each tick.

First create your input binding, here I’m using one called “LeftStick_XAxis”; then you can do something like this:

//in MyPlayerController.h

/** Horizontal input value of the left-analog stick updated by input event */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
float LeftStick_XAxisValue;

/** Horizontal input value of the left-analog stick during the previous frame */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
float LastLeftStick_XAxisValue;

/** Horizontal input value of the left-analog stick during the current frame */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
float CurrentLeftStick_XAxisValue;

/** Minimum change in input over time necessary to trigger action */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
float ThresholdDelta;

/** Record of previous left analog stick axis values per frame */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
TArray<float> PastXAxisInputValues;

/** Maximum number of input values that should be saved  */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
int32 MaxInputValuesHeld;

//constructor
AMyPlayerController();

/** Allows the PlayerController to set up custom input bindings. */
virtual void SetupInputComponent() override;

// Called every frame
virtual void Tick(float DeltaSeconds) override;

/** Called when axis "LeftStick_XAxis" detects input */
UFUNCTION(BlueprintCallable, Category="Input Setup")
void INPUT_LeftStick_XAxis(float AxisValue);

/** Updates record of inputs over time with current frame data */
UFUNCTION(BlueprintCallable, Category="Input Record")
void UpdateInputOverTime(const float& DeltaSeconds);

/** Returns stored float left analog stick x-axis value from give number of frames previous (if available), or 0.0f; updates referenced boolean flag to indicate frame input data was found (true) or not found (false) */
UFUNCTION(BlueprintCallable, Category="Input Record")
float GetInputValueXFramesAgo(const int32& FramesAgo, bool& IsValid);    


//in MyPlayerController.cpp


AMyPlayerController::AMyPlayerController()
    :
    LeftStick_XAxisValue(0.0f),
    LastLeftStick_XAxisValue(0.0f),
    CurrentLeftStick_XAxisValue(0.0f),
    ThresholdDelta(0.2f),
    MaxInputValuesHeld(60)
{
    PastXAxisInputValues.Empty();

}

/** Allows the PlayerController to set up custom input bindings. */
void AMyPlayerController::SetupInputComponent() {
    Super::SetupInputComponent();
    if (InputComponent == NULL) {
        InputComponent = NewObject<UInputComponent>(this, TEXT("PC_InputComponent0"));
        InputComponent->RegisterComponent();

    }
    if (InputComponent != NULL) {
        InputComponent->BindAxis("LeftStick_XAxis", this, &AMyPlayerController::INPUT_LeftStick_XAxis).bExecuteWhenPaused = false;

    }

}

// Called every frame
void AMyPlayerController::Tick(float DeltaSeconds) {
    Super::Tick(DeltaSeconds);
    UpdateInputOverTime(DeltaSeconds);
    bool IsValid;
    //assuming 30fps, 15 frames is .5 seconds
    float ThresholdCheck = GetInputValueXFramesAgo(15, IsValid);
    if (IsValid && fabs(CurrentLeftStick_XAxisValue - ThresholdCheck) >= ThreshdoldValue) {
      //do your threshold actions here

    }

}

/** Called when axis "LeftStick_XAxis" detects input */
void AMyPlayerController::INPUT_LeftStick_XAxis(float AxisValue)
{
    LeftStick_XAxisValue = AxisValue;

}

/** Updates record of inputs over time with current frame data */
void AMyPlayerController::UpdateInputOverTime(const float& DeltaSeconds)
{
    LastLeftStick_XAxisValue = CurrentLeftStick_XAxisValue;
    CurrentLeftStick_XAxisValue = LeftStick_XAxisValue;
    PastXAxisInputValues.Emplace(CurrentLeftStick_XAxisValue);
    if (PastXAxisInputValues.Num() > MaxInputValuesHeld) {
        PastXAxisInputValues.RemoveAt(0, 1, true);

    }

}

/** Returns stored float left analog stick x-axis value from give number of frames previous (if available), or 0.0f; updates referenced boolean flag to indicate frame input data was found (true) or not found (false) */
void AMyPlayerController::GetInputValueXFramesAgo(const int32& FramesAgo, bool& IsValid)
{
    if (FramesAgo <= PastXAxisInputValues.Num()) {
        IsValid = true;
        //Max index is 1 frame ago (not zero), so add one to resulting index
        return PastXAxisInputValues[PastXAxisInputValues.Max() - FramesAgo + 1];

    }
    IsValid = false;
    return 0.0f;

}

See:
Input Action And Axis Mappings - Announcements - Unreal Engine Forums

Your logic here doesn’t detect the change in input at all, it’s only checking if the input value as it currently exists is between .2 and .3, or between -.2 and -.3. That could be true or false no matter how fast the stick is moving or if it is stationary.

To check the value over time you’ll have to store the values as they come in and check the current value against previous frames.

See my answer below for a detailed example of how to store the input values and query values 1 or more frames ago.

Hey there,

Thanks for this in-depth answer, really helped me along. I’m facing an issue currently however and was curious on what you thought the solution may be.
After implementing the code provided into my program, it has started crashing on start up.

if (FramesAgo <= PastXAxisInputValues.Num()) {
         IsValid = true;
         //Max index is 1 frame ago (not zero), so add one to resulting index
         return PastXAxisInputValues[PastXAxisInputValues.Max() - FramesAgo + 1];
 
     }

I had to manually debug it piece by piece to track down the crash to this part of the code (shown above). The return statement inside the if statement is causing the crash specifically. I think the reason it is crashing here is because we never used the ‘MaxInputValuesHeld’ variable to allocate the necessary memory for the ‘PastXAxisInputValues’ type Float Array in the constructor of the .cpp file?

I’m at work at the moment, so if you need to see all of my code, I can post it later on here tonight. Hopefully my assumed solution is correct though!

I would guess you’re having problems when the “FramesAgo” value is zero.

return PastXAxisInputValues[PastXAxisInputValues.Max() - FramesAgo + 1]

Becomes:

return PastXAxisInputValues[PastXAxisInputValues.Max() + 1]

Which will always pose a problem.

I should have included a check:

//make sure we aren't asking for the current frame
if (FramesAgo <= 0) {
    IsValid = true;
    return CurrentLeftStick_XAxisValue;

}

We also need to verify the array actually holds frame data. We can either include another check:

if (PastXAxisInputValues.Num() && FramesAgo <= PastXAxisInputValues.Num()) {
    IsValid = true;
    //Max index is 1 frame ago (not zero), so add one to resulting index
    return PastXAxisInputValues[PastXAxisInputValues.Max() - FramesAgo + 1];

}

Or pre-populate the array in the constructor:

AMyPlayerController::AMyPlayerController()
     :
     LeftStick_XAxisValue(0.0f),
     LastLeftStick_XAxisValue(0.0f),
     CurrentLeftStick_XAxisValue(0.0f),
     ThresholdDelta(0.2f),
     MaxInputValuesHeld(60)
 {
     PastXAxisInputValues.Empty();
     PastXAxisInputValues.Emplace(LastLeftStick_XAxisValue);
 
 }

Or both.

We could also include a valid index check before retrieving it:

//Max index is 1 frame ago (not zero), so add one to resulting index
int32 Index = PastXAxisInputValues.Max() - FramesAgo + 1;

if (PastXAxisInputValues.IsValidIndex(Index)) {
    IsValid = true;
    return PastXAxisInputValues[Index];

}

If that doesn’t fix it, include a log to check the current state when the error occurs.

    if (PastXAxisInputValues.Num() && FramesAgo <= PastXAxisInputValues.Num()) {
        UE_Log(LogTemp, Log, TEXT("GetInputValueXFramesAgo Array Count: %s"), *FString::FromInt( PastXAxisInputValues.Num() ));
        UE_Log(LogTemp, Log, TEXT("GetInputValueXFrameAgo FramesAgo: %s"), *FString::FromInt(FramesAgo));
        UE_Log(LogTemp, Log, TEXT("GetInputValueXFrameAgo RetrievalIndex: %s"), *FString::FromInt( (PastXAxisInputValues.Max() - FramesAgo + 1) ));

        IsValid = true;
        //Max index is 1 frame ago (not zero), so add one to resulting index
        return PastXAxisInputValues[PastXAxisInputValues.Max() - FramesAgo + 1];
    
    }

At that point it’s probably worth posting the results to a new question with more detail; most people will skip over this discussion after the question is marked resolved. It’s also helpful to keep even slightly different questions separate and mark each resolved as solutions are found so other people searching for the same, specific problem can find the answers they need.

You can post a comment here with a link to the new question and I’ll see it, and/or include @GigasightMedia.