Check if an action is pressed on a specific input device

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By zep

I want to implement splitscreen functionality in my game. For that, I need to handle inputs from different devices. Because reasons, I cannot use _input or _unhandled_input for this.

Is there a way to do something like Input.IsActionPressed("MyAction", 1) in order to query for the input on the second device? I would like to avoid making input mappings for each of the 8 devices. I could also make a wrapper around _input, but would rather not reinvent the wheel if I can.

Thus, is it possible to check the state of an input on a specific device?

Not sure if this is what you are looking for, but you could map action as follows "<deviceId>_actionName", for example "PLAYER1_JUMP"
Then, you just do as follows to determine what player did what action:

var deviceNames=["PLAYER1","PLAYER2","PLAYER3"]
for deviceId in deviceNames:
    var inputActionId = deviceId+"_JUMP"
    if Input.is_action_just_pressed(inputId):
        pass
	

godot_dev_ | 2023-06-19 23:10

I would like to avoid making input mappings for each of the 8 possible devices

zep | 2023-06-19 23:12

:bust_in_silhouette: Reply From: zhyrin

If you are already at a stage where is_action_pressed(<action_name>) is useful, then yes.
Besides the Input singleton, there’s also the InputMap singleton.
You can use InputMap.action_get_events(<action_name>) to get every individual InputEvent associated with an action.
You can iterate through these events, check wether they are pressed/released, and they store a device id for the event. If every player has an associated device id, you can match them here.

Note about InputEvents: they have is_action_pressed/released() functions, although they don’t have “just pressed/released” variations, you’ll have to rely on Input for that.

:bust_in_silhouette: Reply From: zep

While zhyrin’s solution works to an extent (does not correctly account for devices, at least for me), I ended up just implementing the dreaded wrapper around _input like so;

public partial class BetterInput : Node
{
    private static Dictionary<StringName, Dictionary<int, InputActionData>> ActionData { get; }

    static BetterInput()
    {
        ActionData = new Dictionary<StringName, Dictionary<int, InputActionData>>();
    }

    public override void _Input(InputEvent inputEvent)
    {
        foreach (var action in InputMap.GetActions())
        {
            if (InputMap.EventIsAction(inputEvent, action))
            {
                var actionData = GetActionData(action, inputEvent.Device);

                if (inputEvent.IsPressed())
                    actionData.PressedFrame = Engine.GetFramesDrawn();

                actionData.Strength = inputEvent.GetActionStrength(action);
            }
        }
    }

    public static bool IsActionPressed(StringName action, int device)
    {
        return GetActionData(action, device).PressedFrame == Engine.GetFramesDrawn();
    }

    public static float GetAxis(StringName negativeAction, StringName positiveAction, int device)
    {
        return GetActionData(positiveAction, device).Strength - GetActionData(negativeAction, device).Strength;
    }

    private static InputActionData GetActionData(StringName action, int device)
    {
        if (!ActionData.TryGetValue(action, out var innerDict))
        {
            innerDict = ActionData[action] = new Dictionary<int, InputActionData>();
        }

        if (!innerDict.TryGetValue(device, out var deviceActionData))
        {
            deviceActionData = innerDict[device] = new InputActionData();
        }

        return deviceActionData;
    }

    private class InputActionData
    {
        public int PressedFrame { get; set; } = -1;
        public float Strength { get; set; } = 0f;
    }
}