Input System 1.1.0 preview 2 - Axis with modifier key composite

Hi

Using the latest preview, is there any way to add a 1D axis, let's say a Gamepad joystick Y direction + modifier key so that the Action only is activated when the a certain modifier key on the gamepad + the axis is used in conjunction? I've seen the button + modifier, but I'm after an axis with modifier.

Best regards

  • Björn

Hi

So I needed the same thing, and saw that even on the 1.1.0-preview 3, there was nothing like an axis with modifier, so looking at the documentation for creating a custom composite:

https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/ActionBindings.html?&_ga=2.110396358.274091956.1612840461-1813738166.1611803663#writing-custom-composites

as well as looking at the code of both the AxisComposite, and OneModifierComposite, I came up with this:

using System.ComponentModel;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Composites;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.Utilities;

#if UNITY_EDITOR
using UnityEditor;
[InitializeOnLoad] // Automatically register in editor.
#endif

[DisplayStringFormat("{modifier}+{negative}/{positive}")]
[DisplayName("Positive/Negative Binding with Modifier")]
public class AxisModifierComposite : AxisComposite
{
    // Each part binding is represented as a field of type int and annotated with
    // InputControlAttribute. Setting "layout" restricts the controls that
    // are made available for picking in the UI.
    //
    // On creation, the int value is set to an integer identifier for the binding
    // part. This identifier can read values from InputBindingCompositeContext.
    // See ReadValue() below.
    [InputControl(layout = "Button")] public int modifier;

    // This method computes the resulting input value of the composite based
    // on the input from its part bindings.
    public override float ReadValue(ref InputBindingCompositeContext context)
    {
        if (context.ReadValueAsButton(modifier))
            return base.ReadValue(ref context);
        return default;
    }

    // This method computes the current actuation of the binding as a whole.
    public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
    {
        if (context.ReadValueAsButton(modifier))
            return base.EvaluateMagnitude(ref context);
        return default;
    }

    static AxisModifierComposite()
    {
        InputSystem.RegisterBindingComposite<AxisModifierComposite>();
    }

    [RuntimeInitializeOnLoadMethod]
    static void Init() {} // Trigger static constructor.
}

This can be used when adding a binding, and it's basically a combination of both a binding with one modifier, and a 1D Axis

6822068--792548--image_2021-02-09_232035.png
6822068--792551--upload_2021-2-9_23-21-58.png

Hopefully it is helpful!

1 Like

As of 1.1-preview.2, OneModifierComposite should provide this functionality. Unlike with ButtonWithOneModifier, the non-modifier binding can be to anything, not just buttons.

Ok, I'll have a look a both your suggestions tomorrow! OneModifierComposite, is that available in the Add binding dropdown or by code or where can I find it?

(Btw, Preview3 that just came out messed up the Oculus touch bindings. )

But can it be bound to a 1Daxis? I seems on my end I can only choose a specific direction for the axis, not make use of the negative/positive composite parts together with a modifier?

I mean, I understand how I can bind it to for example a joystick axis like this:

But what if I want to bind a modfier key to a Keyboard composite equivalent like using Up/Down arrows as an axis but together with a modifier. SHIFT+Up/Down for example. Can that be achieved @Rene-Damm ?

This is very helpful and work great, thank you a bunch for that!

One question, which might not be directly related to your code but more how to think when using to modifier keys in general. Lets say I use Up/Down-arrows in composite for Action A. If I then want to add SHIFT as a modifier (using your script for example) together with Up/Down-arrows to engage Action B. Then it seems, Action A is also engaged whenever Action B is, since it reacty to the axis composite without considering any modifiers. What is the right way to plan around this? should I add inverted modfiers to gate the Action A bindings aswell?

Hi again

I ended up adding this boolean flag to check for an Inverted state of the modifier key. That is, whether the state of the modifier key NOT being pressed should gate the action in question.

public bool invertModifier;

    // This method computes the resulting input value of the composite based
    // on the input from its part bindings but gated by the modifier key.
    public override float ReadValue(ref InputBindingCompositeContext context)
    {
        float baseValue = base.ReadValue(ref context);

        if (!invertModifier)
        {
            if (context.ReadValueAsButton(modifier))
                return base.ReadValue(ref context);
            return default;
        }

        if (!context.ReadValueAsButton(modifier))
            return base.ReadValue(ref context);
        return default;
    }


1 Like

Thanks, I needed the same but for a button, not an axis, so I derived from OneModifierComposite, added a flag invertModifier and then overrode the methods in a similar way, inverting all conditions on modifier. Here is the script with long comments removed:

using System.ComponentModel;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Composites;
using UnityEngine.InputSystem.Utilities;

#if UNITY_EDITOR
using UnityEditor;
[InitializeOnLoad]
#endif
[DisplayStringFormat("{modifier}+{binding}")]
[DisplayName("Binding With Invertible Modifier")]
public class InvertibleModifierComposite : OneModifierComposite
{
    // In the editor, the static class constructor will be called on startup
    // because of [InitializeOnLoad].
    #if UNITY_EDITOR
    static InvertibleModifierComposite()
    {
        // Trigger our RegisterBindingComposite code in the editor.
        Initialize();
    }
    #endif

    // In the player, [RuntimeInitializeOnLoadMethod] will make sure our
    // initialization code gets called during startup.
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static void Initialize()
    {
        InputSystem.RegisterBindingComposite<InvertibleModifierComposite>();
    }

    /// <summary>
    /// When set to true, the modifier must be released for the binding to be considered.
    /// Else, the modifier must be pressed, and this behaves like OneModifierComposite.
    /// </summary>
    public bool invertModifier = false;

    // Below, we override all methods of OneModifierComposite that check modifier, and prefix the condition
    // with `invertModifier ^`

    public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
    {
        if (invertModifier ^ context.ReadValueAsButton(modifier))
            return context.EvaluateMagnitude(binding);
        return default;
    }

    public override unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize)
    {
        if (invertModifier ^ context.ReadValueAsButton(modifier))
            context.ReadValue(binding, buffer, bufferSize);
        else
            UnsafeUtility.MemClear(buffer, valueSizeInBytes);
    }

    public override object ReadValueAsObject(ref InputBindingCompositeContext context)
    {
        if (invertModifier ^ context.ReadValueAsButton(modifier))
            return context.ReadValueAsObject(binding);
        return null;
    }
}

Full script source (latest version): https://bitbucket.org/hsandt/unity-commons-helper/src/develop/InputSystem/InvertibleModifierComposite.cs

Usage example (taken from comment in source above):

Say you want Alt+Enter to toggle fullscreen, but only Enter (with Alt released) to trigger UI Submit, to avoid submitting and toggling fullscreen at the same time.
First, bind a OneModifierComposite input Alt+Enter to your action ToggleFullscreen. You can also use an InvertibleModifierComposite with invertModifier = false.
Then, bind an InvertibleModifierComposite with invertModifier = true to the UI Submit action (in this particular case, you'll also want to remove the default entry based on Usages "Submit [Any]" which overlaps Enter).

Another idea could be to mimic other input editors and instead of having a freestyle modifier field which can take an arbitrary key/button, focus on common modifiers i.e. Ctrl, Shift, Alt, Meta/Super and add a bool member (flag) for each.

Then, when bool is true, modifier must be pressed and when bool is false, modifier must be released. This way, there are no input conflicts arising as long as you never use the same set of modifiers twice (in the same input context).

Alternatively we could use a ternary state: Pressed, Released, Any (works with both Pressed and Released) if we don't mind user pressing modifiers that would be unused otherwise.

However, it requires some code change to check the common modifier keys specifically, so more work on the derived class. Also, it would not need Composite at all, as all info would be inside the flags, so I'm not sure which class we should derive from (InputBinding is a struct, it must be wrapped in some class when you add a simple Binding in InputActions, but I don't see which one in https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/api/). Worst case, we could make a dummy Composite with 0 child, but that seems a bit wasteful.

Is there a way to do this in-editor yet?


Yep! There actually is. It's a bit non-intuitive and took me a while to think backwards, but this is basically how you'd do it:
9056347--1251859--upload_2023-6-4_19-14-26.png

Where my first "One Modifier" has a Scale processor on it with a factor of -1.
Oh, and Zoom is Action Type: Value, Control Type: Axis

Why is this still not a thing?
Why isn't there just a "positive/negative binding with modified"?
It's literally so simple and it's been years.

1 Like