/ C++

UE4 Quickie: Controller Detection

When targeting the PC with your games, it's often useful to detect whether your player is using a controller or a keyboard & mouse. Being able to differentiate means you can show appropriate feedback to the user. You can customize your user feedback, for example, to say "Press 'X' to Pickup" for someone using a controller, but "Press 'Space' to Pickup" for players using keyboard/mouse.

Surprisingly, there isn't actually a built-in mechanism for detecting the use of a controller in UE4. If you look closely at the way most AAA games handle this, they base it off of whether they've received input from an analog stick or analog trigger within the last several seconds. For most PC games, if you put down your controller for a few minutes or press a key on your keyboard, the on-screen messages will revert to showing keyboard buttons rather than controller buttons.

Well, that, we can do pretty easily in UE4.

The best place to do it is in the PlayerController class. Every player gets their own instance and it's easy to determine if a controller instance is local.

We need a couple of variables to help us keep track of whether gamepad is being used, whether we should revert to not using gamepad if we haven't received input in a while, and how long we should wait before resetting. We also need to keep track of when the last input was received.

We also need a function for updating the variable used to track whether we're using gamepad. Because it will be called frequently -- potentially every single frame -- we'll make it FORCEINLINE to get rid of the overhead of a VTable lookup.

Here's what our controller-detecting player controller class header might look like:

#pragma once

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

/**
 * Player Controller
 *
 * Added features:
 *
 *  Gamepad tracking. Whenever input is received, we keep track of whether it came from a gamepad and make that available. If no input is received
 *      for a period of time, we optionally reset the status to false. This mimicks the behavior of many AAA games
 */
UCLASS()
class DARKNESS_API AMyPlayerController : public APlayerController
{
	GENERATED_BODY()
	
    AMyPlayerController();
    
public:
    
    /** Can be called to check and see if the player is currently using a gamepad */
    UPROPERTY(Transient, BlueprintReadOnly)
        bool bIsUsingGamepad;
    
    /** Specifies whether we set `bIsUsingGamepad` to `false` if we receive no input for a period of time. If set to 'true', GamepadTimeout will control how long
        we need to go without receiving input before we set `bIsUsingGamepad` to `false`. */
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
        bool bResetGamepadDetectionAfterNoInput;
    
    /** How long we can go without receiving a gamepad input before we assume they've stopped using the gamepad */
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta=(EditCondition="bResetGamepadDetectionAfterNoInput") )
        float GamepadTimeout;
	
    
    // Overrides
    virtual bool InputAxis(FKey Key, float Delta, float DeltaTime, int32 NumSamples, bool bGamepad) override;
    virtual bool InputKey(FKey Key, EInputEvent EventType, float AmountDepressed, bool bGamepad) override;
    virtual void PlayerTick(float DeltaTime) override;
    
protected:
    
    /** Common logic needed in both `InputAxis()` and `InputKey()` */
    FORCEINLINE void _UpdateGamepad(bool bGamepad)
    {
        bIsUsingGamepad = bGamepad;
        if (bGamepad)
        {
            LastGamepadInputTime = GetWorld()->TimeSeconds;
        }
    }
   
    // Used to keep track of when we last saw gamepad input
    UPROPERTY(Transient, BlueprintReadOnly)
        float LastGamepadInputTime;
};

Here's what the implementation file might look like:

#include "MylayerController.h"


AMyPlayerController::AMyPlayerController()
{
    bIsUsingGamepad = false;
    bResetGamepadDetectionAfterNoInput = true;
    GamepadTimeout = 5.f;
}

bool AMyPlayerController::InputAxis(FKey Key, float Delta, float DeltaTime, int32 NumSamples, bool bGamepad)
{
    bool ret = Super::InputAxis(Key, Delta, DeltaTime, NumSamples, bGamepad);
    _UpdateGamepad(bGamepad);
    return ret;
}

bool AMyPlayerController::InputKey(FKey Key, EInputEvent EventType, float AmountDepressed, bool bGamepad)
{
    bool ret = Super::InputKey(Key, EventType, AmountDepressed, bGamepad);
    _UpdateGamepad(bGamepad);
    return ret;
}

void AMyPlayerController::PlayerTick(float DeltaTime)
{
    Super::PlayerTick(DeltaTime);
    if (IsLocalController() && bResetGamepadDetectionAfterNoInput && bIsUsingGamepad)
    {
        float now = GetWorld()->TimeSeconds;
        if (now > LastGamepadInputTime + GamepadTimeout)
        {
            bIsUsingGamepad = false;
        }
    }
}

Once we've done this, every time a gamepad is used for input, we set bIsUsingGamepad to true. If we don't receive input for a configurable period of time that defaults to 5 seconds, we set it back to false.

Whenever we need to know if the player is using a gamepad, for example, in a UMG widget, we just have to grab the local player controller instance and check bIsUsingGamepad. Easy peazy.

UE4 Quickie: Controller Detection
Share this

Subscribe to Recursive Blueprints