How do I configure sensitivity of mouse and keyboard individually on the same action ?

In my game, you can move the camera either by using WASD or by holding a mouse button and moving the mouse. Similar for rotation and zoom - they all have both keyboard and mouse controls.

That's easy enough to set up in the new input system. I have set up three actions (move, rotate, zoom) and given them both mouse and keyboard bindings.

However, I'd like to configure sensitivity of the mouse. People have set their mice up differently. I know how to add a processor to the mouse binding to give mouse and keyboard different sensitivities using a scale processor.

I can't figure out how to access that processor in code to change it based on a player configuration option. I want to change ONLY the mouse scale processor, NOT the keyboard one - keyboard is just 1/0 so it's the same for everyone, but mice are configured differently and I want players to set their own MOUSE sensitivity.

I've read the documentation, various tutorials, the closest I found was this, but it refers to the whole action, not an individual binding: https://gamedevbeginner.com/input-in-unity-made-easy-complete-guide-to-the-new-system/#ineractions_and_processors

[quote=“Tom163”, post:1, topic: 867433]
In my game, you can move the camera either by using WASD or by holding a mouse button and moving the mouse. Similar for rotation and zoom - they all have both keyboard and mouse controls.

That’s easy enough to set up in the new input system. I have set up three actions (move, rotate, zoom) and given them both mouse and keyboard bindings.

However, I’d like to configure sensitivity of the mouse. People have set their mice up differently. I know how to add a processor to the mouse binding to give mouse and keyboard different sensitivities using a scale processor.

I can’t figure out how to access that processor in code to change it based on a player configuration option. I want to change ONLY the mouse scale processor, NOT the keyboard one - keyboard is just 1/0 so it’s the same for everyone, but mice are configured differently and I want players to set their own MOUSE sensitivity.

I’ve read the documentation, various tutorials, the closest I found was this, but it refers to the whole action, not an individual binding: https://gamedevbeginner.com/input-in-unity-made-easy-complete-guide-to-the-new-system/#ineractions_and_processors
[/quote]
The InputBinding class has the overrideProcessors property, setting that you can override the called processor. Obviously you will set it to something like $"scale(factor={userScaleValue})" or something similar (if you used the processor template in the docs).
Getting the bindings of an InputAction is easy, the class has a bindings property which gives a readonly, generic array with the type of InputBinding.

[quote]
Getting the bindings of an InputAction is easy, the class has a bindings property which gives a readonly, generic array with the type of InputBinding.
[/quote]

I couldn't get that to work. Now I see "
Note that on the first call, the list may have to be extracted from the action map first which may require allocating GC memory. However, once initialized, no further GC allocation hits should occur. If the binding setup on the map is changed, re-initialization may be required."

Which I frankly don't understand. If I just access bindings, I get this:

UnityEngine.InputSystem.Utilities.ReadOnlyArray`1[[UnityEngine.InputSystem.InputBinding, Unity.InputSystem, Version=1.2.0.0, Culture=neutral, PublicKeyToken=null]]

despite having multiple bindings assigned.

[quote=“Tom163”, post:3, topic: 867433]
Which I frankly don’t understand.
[/quote]
Okay, here is my way of doing this, just adapted to your case, more or less.

using System.Linq;
using UnityEngine;
using UnityEngine.InputSystem;

public class Test : MonoBehaviour
{
    [SerializeField]
    private InputActionAsset inputActionAsset;

    private const string MOUSE_PATH = "<Mouse>";

    private void Awake()
    {
        var action = inputActionAsset.FindAction("TestAction");
        DebugBindings(action);
        SetScale(action, MOUSE_PATH, new Vector2(666, 666));
        DebugBindings(action);
    }

    private static void SetScale(InputAction action, string bindingPathStart, Vector2 scale)
    {
        var bindings = action.bindings;
        for(var i = 0; i < bindings.Count; i++)
        {
            if (bindings[i].isPartOfComposite || !bindings[i].path.StartsWith(bindingPathStart)) continue;
            action.ApplyBindingOverride(i,
                new InputBinding { overrideProcessors = $"ScaleVector2(x={scale.x},y={scale.y})" });
            return;
        }
    }

    private static void DebugBindings(InputAction action)
    {
        foreach(var binding in action.bindings.Where(binding => !binding.isPartOfComposite))
            Debug.Log($"{binding.path}, {binding.effectiveProcessors}");
    }
}

The point is the SetScale method, obviously, the rest are just to run things.
7787904--983217--screenshot.png

Tried that, doesn't work for me, error message is not helpful:

InvalidOperationException: Cannot find public field 'down' used as parameter of binding composite 'UnityEngine.InputSystem.Composites.OneModifierComposite' of type 'UnityEngine.InputSystem.Composites.OneModifierComposite'

I think I understood how about it should work. Frankly speaking: It's an order of magnitude more complicated than it should be. What a mess.

1 Like

Finally figured it out. Here's my code, in case it helps anyone who has a similar issue. Frankly speaking, this ought to be much, much better a) supported and b) documented.

Thanks to @Lurking-Ninja who pointed me in the right direction.

        public void SetZoomSpeed(float m_value) { SetZoomSpeed(m_value, true); } // stupidity because the OnValueChanged inspector control doesn't do it any other way
        public void SetZoomSpeed(float m_value, bool update_prefs) {
            SetScale(ZoomCamera, "<Mouse>/scroll/y", m_value);
            if (update_prefs) PlayerPrefs.SetFloat("ZoomSpeed", m_value);
        }
        public void SetMoveSpeed(float m_value) { SetMoveSpeed(m_value, true); } // stupidity because the OnValueChanged inspector control doesn't do it any other way
        public void SetMoveSpeed(float m_value, bool update_prefs) {
            SetScale(MoveCamera, "<Mouse>/delta", m_value);
            if (update_prefs) PlayerPrefs.SetFloat("MoveSpeed", m_value);
        }
        public void SetRotateSpeed(float m_value) { SetRotateSpeed(m_value, true); } // stupidity because the OnValueChanged inspector control doesn't do it any other way
        public void SetRotateSpeed(float m_value, bool update_prefs) {
            SetScale(RotateCamera, "<Mouse>/delta/x", m_value);
            if (update_prefs) PlayerPrefs.SetFloat("RotateSpeed", m_value);
        }

        private void SetScale(InputAction action, string bindingPath, Vector2 scale) {
            var bindings = action.bindings;
            for(var i = 0; i < bindings.Count; i++) {
                Debug.Log("checking "+action.GetBindingDisplayString(i)+" ("+bindings[i].path+")");
                if (bindingPath.ToLower() == bindings[i].path.ToLower()) {
                    scale = scale*scale;
                    Debug.Log("setting scale on "+bindings[i]+" - "+bindings[i].path+" to "+scale);
                    action.ApplyBindingOverride(i, new InputBinding { overrideProcessors = $"ScaleVector2(x={scale.x},y={scale.y})" });
                    return;                  
                }
            }
        }
        private void SetScale(InputAction action, string bindingPath, float scale) {
            var bindings = action.bindings;
            for(var i = 0; i < bindings.Count; i++) {
                Debug.Log("checking "+action.GetBindingDisplayString(i)+" ("+bindings[i].path+")");
                if (bindingPath.ToLower() == bindings[i].path.ToLower()) {
                    scale = scale*scale;
                    Debug.Log("setting scale on "+bindings[i]+" - "+bindings[i].path+" to "+scale);
                    action.ApplyBindingOverride(i, new InputBinding { overrideProcessors = $"scale(factor={scale})" });
                    return;                  
                }
            }
        }
1 Like

This is one of the most obtuse, horrible implementations I've had to deal with. I can't, for my life, understand how to edit processors in code. Why the hell must they be these "strings" concatenated instead of actual serialized properties? WHY?

I am trying to write a UI in-game that simply allows me to modify a scale processor for an analog controller. I get as far as being able to display the dreaded "scale(factor=whatever)" devil on a label; how do I edit it? Why must I create an override?

It all feels like, indeed, it's more complicated than it should be. Someone please help me understand how to edit these processor strings before I lose my mind...

2 Likes

Frankly speaking, I've dumped the new input system and sworn to never touch it again, not with a long pole and gloves.

I'm using Rewired now and am mostly happy. I'm somewhat sure the new input system will soon land on the pile of abandoned Unity pieces and they'll make something better from scratch, because I can't imagine it's any fun to maintain or update this trainwreck.

1 Like