Search code examples
c#unity-game-engine

How to access the click (desktop) / tap (mobile) position using the new input system?


I created a new Unity project and installed the package for the new Input system. Basically I just want to store the position of a click (desktop) / tap (mobile), that's it.

I know the old system provides solutions

but I want to solve it with the new input system.

I started with this input map configuration (I will show the configuration for each selected item)

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

I created a new script logging each click/tap position

public class FooBar : MonoBehaviour
{
    public void Select(InputAction.CallbackContext context)
    {
        Vector2 selectPosition = context.ReadValue<Vector2>();
        Debug.Log($"Select position is: {selectPosition.x}|{selectPosition.y}");
    }
}

In the scene I created an empty gameobject and configured it in the inspector

enter image description here

Unfortunately when running the playmode I get these errors each time when moving the mouse around

enter image description here

This is the stacktrace of the first error message

enter image description here

and this is the stacktrace of the second error message

enter image description here

So I'm assuming my input map configuration is wrong.

Would someone mind helping me setting up an input configuration passing the click/tap position to the script?


So for a quick workaround I currently use this code with the old input system but I really don't like it ;)

    public sealed class SelectedPositionStateController : MonoBehaviour
    {
        private void Update()
        {
#if UNITY_ANDROID || UNITY_IOS
            if (UnityEngine.Input.touchCount > 0)
            {
                Touch touch = UnityEngine.Input.GetTouch(0);
                
                // do things with touch.position
            }
#elif UNITY_STANDALONE
            if (UnityEngine.Input.GetMouseButtonDown(0))
            {
                // do things with Input.mousePosition
            }
#endif
        }
        
        // !!! USE THIS CODE BECAUSE IT'S OBVIOUSLY BETTER !!!
        // 
        // public void SelectPosition(InputAction.CallbackContext context)
        // {
        //     Vector2 selectedPosition = context.ReadValue<Vector2>();
        //     
        //     // do things with selectedPosition
        // }
    }

Solution

  • To achieve the desired outcome you will need 2 InputAction. One for the Click, and another for the Position.

    Your MonoBehaviour will listen to the performed event of InputAction related to the Click and read the Position from the one related to Position.

    The InputAction related to Position is optional; as you may catch it with the input system API.

    • Touchscreen.current.primaryTouch.position.ReadValue()
    • Mouse.current.position.ReadValue()

    For your use case I suggest either:

    1. Serialize the InputAction directly in the MonoBehaviour
    2. Use an InputActionAsset and get the InputAction from there

    OPTION 1 - Direct InputAction

    Setup one InputAction for the Click, and another for the Position.
    Then you might listen to the Click and catch the position from the Position.

    public class InputWithAction : MonoBehaviour
    {
        // --------------- FIELDS AND PROPERTIES --------------- //
        [SerializeField] private InputAction _click;
        [SerializeField] private InputAction _pos;
    
        // --------------- INITIALIZATION --------------- //
        private void Process(InputAction.CallbackContext callback)
        {
            //get the value from the position
            Debug.Log($"Input action: {_pos.ReadValue<Vector2>()}");
            //use the items below if you want to get the input directly
    #if UNITY_ANDROID || UNITY_IOS
            //gets the primary touch position using the new input system
            Debug.Log(Touchscreen.current.primaryTouch.position.ReadValue());
    #elif UNITY_STANDALONE
            //gets the current mouse position using the new input system
            Debug.Log(Mouse.current.position.ReadValue());
    #endif
        }
    
        // --------------- LISTENER SETUP --------------- //
        private void OnEnable()
        {
            _click.Enable();
            _pos.Enable();
            _click.performed += Process;
        }
    
        private void OnDisable()
        {
            _click.performed -= Process;
            _click.Disable();
            _pos.Disable();
        }
    }
    

    The Click action requires 2 bindings.

    • Add a Bidning with the + button => set the Path as: Mouse/Left Button
    • Add a Bidning with the + button => set the Path as: TouchScreen/Primary Touch/Tap

    The Position requires 2 bindings.

    • Add a Bidning with the + button => set the Path as: Mouse/Position
    • Add a Bidning with the + button => set the Path as: TouchScreen/Primary Touch/Position

    Your component will look like this:

    enter image description here

    OPTION 2 - The InputActionAsset

    1. Create the InputMapAsset.
      From the Editor: Assets -> Create -> InputActions. Call it "YourMap"

    2. Remove all the actions and then add 2 actions.

    A. "Click" action Action Type: Button
    Binding Mouse => Mouse/LeftButton
    Binding TouchScreen => TouchScreen/Primary Touch/Tap

    B. "Position" action Action Type: Value
    Binding Mouse => Mouse/Position
    Binding TouchScreen => TouchScreen/Primary Touch/Position

    It will look like this:

    enter image description here

    You can reference "YourMap" as InputActionAsset as a field of your MonoBehaviour and catch the InputAction with the FindAction method, using the string names ("Click" and "Position").

        public class InputWithMap : MonoBehaviour
        {
            // --------------- FIELDS AND PROPERTIES --------------- //
            [SerializeField] private InputActionAsset _inputMap;
            private InputAction _click;
            private InputAction _pos;
    
            private void Start()
            {
                //enable is required only if you're not using PlayerInput anywhere else
                _inputMap.Enable();
    
                _click = _inputMap.FindAction("Click");
                _pos   = _inputMap.FindAction("Position");
    
                //listen from clicks
                _click.performed += Process;
            }
    
            // --------------- INITIALIZATION --------------- //
            private void Process(InputAction.CallbackContext callback)
            {
                //get the value from the position
                Debug.Log($"Input action: {_pos.ReadValue<Vector2>()}");
            }
    
            private void OnDestroy() { _click.performed -= Process; }
        }
    

    A FEW CONSIDERATIONS

    • The new input system was designed so that designers might do everything with PlayerInput class, without coding. The result wouldn't be as performant as the solutions offered above.
    • I setup the mouse/touch to catch one click. You might catch also hold, double click or scroll adding Interactions to the Bindings.
    • In theory you might achieve the same result using just one InputAction for the Touch Screen using ReadOnlyArray<TouchControl> from TouchScreen class, but in that case Mouse and Touch would have a different setup. Read more for Mouse and Touch support.