Search Unity

Feedback New Input System - Mouse Press and Hold - Drag And Move

Discussion in 'Input System' started by kolex023, Oct 6, 2021.

  1. kolex023

    kolex023

    Joined:
    Oct 16, 2017
    Posts:
    5
    Hello,

    I want to share my solution for an easy Mouse Drag and Move.

    Before all: The Drag and Move solution itself (with the old input system) comes from this youtube tutorial:

    Thank you Game Dev Guide for the solution!

    I wanted to use the same solution with the new input system. I've watched countless numbers of tutorials, but most of them was really complex, and in my project I want to change Control Schemes and Action Maps as well, therefore I could not allow hard coding of devices.

    In This case I'm moving a camera which is on a Camera Rig.
    The script itself has been attached to the camera Rig as a component.

    Here you can see the Actions configuration:
    upload_2021-10-6_17-26-56.png
    upload_2021-10-6_17-27-30.png
    upload_2021-10-6_17-27-42.png

    As You can see I'm using a Mouse Delta to keep the action triggering and separate the two different input types within code. I've not tried, but maybe mouse position could work as the same with the option of getting the mouse position as additional information.


    Here is the backbone of the Method which is being triggered by DragAndMove Action (via Player Input Component and Invoking Unity Events):

    Code (CSharp):
    1.    
    2.     public void ClickHoldRelease(InputAction.CallbackContext context)
    3.     {
    4.         System.Type vector2Type = Vector2.zero.GetType();
    5.  
    6.         string buttonControlPath = "/Mouse/leftButton";
    7.         //string deltaControlPath = "/Mouse/delta";
    8.  
    9.         Debug.Log(context.control.path);
    10.         //Debug.Log(context.valueType);
    11.  
    12.         if (context.started)
    13.         {
    14.             if (context.control.path == buttonControlPath)
    15.             //if (context.valueType != vector2Type)
    16.             {
    17.                 Debug.Log("Button Pressed Down Event - called once when button pressed");
    18.             }
    19.         }
    20.         else if (context.performed)
    21.         {
    22.             if (context.control.path == buttonControlPath)
    23.             //if (context.valueType != vector2Type)
    24.             {
    25.                 Debug.Log("Button Hold Down - called continously till the button is pressed");
    26.             }
    27.         }
    28.         else if (context.canceled)
    29.         {
    30.             if (context.control.path == buttonControlPath)
    31.             //if (context.valueType != vector2Type)
    32.             {
    33.                 Debug.Log("Button released");
    34.             }
    35.         }
    36.     }
    37.  
    As you can see, you can check Callbackcontext.valueType as well as Control path.


    and here is the Script with the Drag and Move :

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.InputSystem;
    3.  
    4. public class CamController : MonoBehaviour
    5. {
    6.     Camera cam;
    7.  
    8.     Vector3 newPosition;
    9.     [SerializeField] private float movementTime = 5f;
    10.  
    11.     Vector3 dragStartPosition = Vector3.zero;
    12.     Vector3 dragCurrentPosition = Vector3.zero;
    13.  
    14.     private void Start()
    15.     {
    16.         newPosition = transform.position;
    17.         cam = Camera.main;
    18.     }
    19.  
    20.     private void Update()
    21.     {
    22.         ApplyMovements();
    23.     }
    24.  
    25.    
    26.     public void DragAndMove(InputAction.CallbackContext context)
    27.     {
    28.         System.Type vector2Type = Vector2.zero.GetType();
    29.  
    30.         string buttonControlPath = "/Mouse/leftButton";
    31.         //string deltaControlPath = "/Mouse/delta";
    32.  
    33.         if (context.started)
    34.         {
    35.             if (context.control.path == buttonControlPath)
    36.             {
    37.                 Debug.Log("Button Pressed Down Event - called once when button pressed");
    38.  
    39.                 Ray dragStartRay = cam.ScreenPointToRay(Mouse.current.position.ReadValue());
    40.                 Plane dragStartPlane = new Plane(Vector3.up, Vector3.zero);
    41.                 float dragStartEntry;
    42.  
    43.                 if (dragStartPlane.Raycast(dragStartRay, out dragStartEntry))
    44.                 {
    45.                     dragStartPosition = dragStartRay.GetPoint(dragStartEntry);
    46.                 }
    47.             }
    48.         }
    49.         else if (context.performed)
    50.         {
    51.             if (context.control.path == buttonControlPath)
    52.             {
    53.                 Debug.Log("Button Hold Down - called continously till the button is pressed");
    54.  
    55.                 Ray dragCurrentRay = cam.ScreenPointToRay(Mouse.current.position.ReadValue());
    56.                 Plane dragCurrentPlane = new Plane(Vector3.up, Vector3.zero);
    57.                 float dragCurrentEntry;
    58.  
    59.                 if (dragCurrentPlane.Raycast(dragCurrentRay, out dragCurrentEntry))
    60.                 {
    61.                     dragCurrentPosition = dragCurrentRay.GetPoint(dragCurrentEntry);
    62.                     newPosition = transform.position + dragStartPosition - dragCurrentPosition;
    63.                 }
    64.             }
    65.  
    66.         }
    67.         else if (context.canceled)
    68.         {
    69.             if (context.control.path == buttonControlPath)
    70.             {
    71.                 Debug.Log("Button released");
    72.             }
    73.         }
    74.     }
    75.  
    76.     private void ApplyMovements()
    77.     {
    78.         transform.position = Vector3.Lerp(transform.position, newPosition, movementTime * Time.deltaTime);
    79.     }
    80. }
    81.  
    I know Mouse.current.position.ReadValue() is hard coding the Mouse Device, but keep in mind without mouse Device this could be not even called, because the binding requires a Mouse device itself.

    I don't think it's the best solution, but it's really simple and I wanted to share it.

    Thanks for reading! Enjoy Coding!
     
    Last edited: Oct 7, 2021
    myominsoe likes this.
  2. BBrown4

    BBrown4

    Joined:
    Feb 13, 2013
    Posts:
    15
    Nice work, you should check out this post if you haven't already.
     
    jordangrant3d likes this.
  3. kolex023

    kolex023

    Joined:
    Oct 16, 2017
    Posts:
    5
    Yes, I've seen that as well, but I wanted to have an even more simple solution.
    On the other hand, that could be better if you want to make difference between click or/then drag with the same mouse button.
     
    Deleted User likes this.
  4. HighCode

    HighCode

    Joined:
    Jun 11, 2014
    Posts:
    1
    Great tutorial and great writeup on how you updated it to work with the new input system!

    kolex023 did you ever end up re-writing how rotation or zoom should work with the new input system too? I took a stab at it unsuccessfully, I'll share any findings while I poke at it -- I'm still trying to learn my way around the new input system.
     
  5. kolex023

    kolex023

    Joined:
    Oct 16, 2017
    Posts:
    5
    Sorry for the late reply, but I just spotted your comment.
    I try to get rotation and zoom example soon for you. (1-2 week...)
     
  6. GetACookie

    GetACookie

    Joined:
    Mar 12, 2023
    Posts:
    1
    Um .... did i miss something? Your Script is not working for me, because the context.performed - part is not called continously.

    Working mouse controlled camera for the new input system:
    I implemented my mouse controlled camera for the new input type finally. I started with the mentioned script and i want to share my final implementation. With this setup i am able to move, rotate and zoom the camera smoothly with clamped values of movement, zoom and horizontally rotation axis. I control the camera with the mouse buttons + ctrl-modifier.

    Attention:
    I also added an optional rotation snapping feature which requires the DoTween asset. If not wanted, just remove the code.

    First the input map configuration:

    upload_2023-4-14_2-6-7.png

    upload_2023-4-14_2-6-46.png

    DragCamera and ZoomCamera are Action Type Button.

    The camera rig (called it camera pivot) holding the camera:
    upload_2023-4-14_2-5-42.png

    The camera itself is configured as following:
    upload_2023-4-14_2-8-8.png

    And here is finally the code of the PerspectiveCameraController class.

    Code (CSharp):
    1. using DG.Tweening;
    2. using UnityEngine;
    3. using UnityEngine.InputSystem;
    4.  
    5. namespace Environment.Scripts
    6. {
    7.     /// <summary>
    8.     /// A camera controller for the perspective view. Enables the player to move, rotate and zoom the camera.<br/>
    9.     /// Configurable with different speed and clamps. Additionally it offers a snap functionality for the rotation (requires DoTween asset).
    10.     /// </summary>
    11.     [RequireComponent(typeof(Transform))]
    12.     public class PerspectiveCameraController : MonoBehaviour
    13.     {
    14.         /// <summary>
    15.         /// The camera object of this controller.
    16.         /// </summary>
    17.         [SerializeField] public Camera mainCamera;
    18.  
    19.         /// <summary>
    20.         /// The speed of the camera movement.
    21.         /// </summary>
    22.         [SerializeField] private float moveSpeed = 5f;
    23.  
    24.         /// <summary>
    25.         /// The speed of the camera rotation.
    26.         /// </summary>
    27.         [SerializeField] private float rotationSpeed = 4F;
    28.  
    29.         /// <summary>
    30.         /// The speed of the camera zoom.
    31.         /// </summary>
    32.         [SerializeField] private float zoomSpeed = 0.3F;
    33.  
    34.         /// <summary>
    35.         /// Activates the snapping of the camera rotation.
    36.         /// </summary>
    37.         [SerializeField] private bool snapRotation;
    38.  
    39.         /// <summary>
    40.         /// The x position clamp. First value is the minimum, second value the maximum. <br/>
    41.         /// Used during x-axis position calculation of the camera pivot.
    42.         /// </summary>
    43.         [SerializeField] private Vector2 xPosClamp = new Vector2(40F, 60F);
    44.  
    45.         /// <summary>
    46.         /// The z position clamp. First value is the minimum, second value the maximum. <br/>
    47.         /// Used during z-axis position calculation of the camera pivot.
    48.         /// </summary>
    49.         [SerializeField] private Vector2 zPosClamp = new Vector2(40F, 60F);
    50.  
    51.         /// <summary>
    52.         /// The x angle (vertical axis) clamp. First value is the minimum, second value the maximum. <br/>
    53.         /// Used for the x axis rotation of the camera pivot to rotate vertically.
    54.         /// </summary>
    55.         [SerializeField] private Vector2 xAngleClamp = new Vector2(-40F, 40F);
    56.  
    57.         /// <summary>
    58.         /// The zoom clamp defined by the local z axis value of the camera. First value is the minimum, second value the maximum.
    59.         /// </summary>
    60.         [SerializeField] private Vector2 zoomClamp = new Vector2(10F, 35F);
    61.  
    62.         /// <summary>
    63.         /// The new movement position of the camera.
    64.         /// </summary>
    65.         private Vector3 _newCamPos;
    66.  
    67.         /// <summary>
    68.         /// The starting position of the drag movement.
    69.         /// </summary>
    70.         private Vector3 _dragStartPos = Vector3.zero;
    71.  
    72.         /// <summary>
    73.         /// The plane used to calculate the drag movement.
    74.         /// </summary>
    75.         private Plane _rayCastPlane;
    76.  
    77.         /// <summary>
    78.         /// Flag if the camera is currently dragged.
    79.         /// </summary>
    80.         private bool _isDragging;
    81.  
    82.         /// <summary>
    83.         /// The new rotation value of the camera.
    84.         /// </summary>
    85.         private Quaternion _newRotation;
    86.  
    87.         /// <summary>
    88.         /// Flag if the camera is currently rotated.
    89.         /// </summary>
    90.         private bool _isRotating;
    91.  
    92.         /// <summary>
    93.         /// Flag if the animation of the rotation snapping is still in progress.
    94.         /// </summary>
    95.         private bool _isBusy;
    96.  
    97.         /// <summary>
    98.         /// The new zoom position of the camera.
    99.         /// </summary>
    100.         private Vector3 _newZoomPos;
    101.  
    102.         /// <summary>
    103.         /// Initializes the fields.
    104.         /// </summary>
    105.         private void Start()
    106.         {
    107.             var tf = transform;
    108.             _newCamPos = tf.position;
    109.             _newRotation = tf.rotation;
    110.             _newZoomPos = mainCamera.transform.localPosition;
    111.             _rayCastPlane = new Plane(Vector3.up, Vector3.zero);
    112.         }
    113.  
    114.         /// <summary>
    115.         /// Smoothly moves the camera according to position, rotation and zoom.
    116.         /// </summary>
    117.         private void LateUpdate()
    118.         {
    119.             transform.position = Vector3.Lerp(transform.position, _newCamPos, moveSpeed * Time.deltaTime);
    120.  
    121.             if (!_isBusy)
    122.             {
    123.                 transform.rotation = Quaternion.Lerp(transform.rotation, _newRotation, rotationSpeed * Time.deltaTime);
    124.             }
    125.  
    126.             mainCamera.transform.localPosition = Vector3.Lerp(mainCamera.transform.localPosition, _newZoomPos,
    127.                 zoomSpeed * Time.deltaTime);
    128.         }
    129.  
    130.         /// <summary>
    131.         /// Calculates the dragging and rotating values depending on the mouse movement.
    132.         /// </summary>
    133.         /// <param name="ctx">The InputAction callback context</param>
    134.         public void OnMove(InputAction.CallbackContext ctx)
    135.         {
    136.             if (_isDragging)
    137.             {
    138.                 // Create a new position via ray cast.
    139.                 var dragCurrentRay = mainCamera.ScreenPointToRay(Mouse.current.position.ReadValue());
    140.  
    141.                 if (!_rayCastPlane.Raycast(dragCurrentRay, out var dragCurrentEntry)) return;
    142.  
    143.                 // Clamp the x and z coordinates before assigning the new position.
    144.                 var newPos = transform.position + _dragStartPos - dragCurrentRay.GetPoint(dragCurrentEntry);
    145.                 newPos.x = Mathf.Clamp(newPos.x, xPosClamp.x, xPosClamp.y);
    146.                 newPos.z = Mathf.Clamp(newPos.z, zPosClamp.x, zPosClamp.y);
    147.                 _newCamPos = newPos;
    148.             }
    149.             else if (_isRotating)
    150.             {
    151.                 // Retrieve mouse movement delta value and add it to the new rotation.
    152.                 var delta = ctx.ReadValue<Vector2>() / 10;
    153.  
    154.                 var eulerAngles = _newRotation.eulerAngles;
    155.  
    156.                 // Returned angles are in the range 0...360. Map that back to -180...180 for convenience.
    157.                 if (eulerAngles.x > 180f)
    158.                     eulerAngles.x -= 360f;
    159.  
    160.                 // Increment the pitch angle, respecting the clamped range.
    161.                 eulerAngles.x = Mathf.Clamp(eulerAngles.x - delta.y, xAngleClamp.x, xAngleClamp.y);
    162.                 eulerAngles.y += delta.x;
    163.                 _newRotation.eulerAngles = eulerAngles;
    164.             }
    165.         }
    166.  
    167.  
    168.         /// <summary>
    169.         /// Updates the dragging flag of the camera.
    170.         /// </summary>
    171.         /// <param name="ctx">The InputAction callback context</param>
    172.         public void OnDrag(InputAction.CallbackContext ctx)
    173.         {
    174.             // Set the start point of our dragging.
    175.             if (ctx.started)
    176.             {
    177.                 var dragStartRay = mainCamera.ScreenPointToRay(Mouse.current.position.ReadValue());
    178.  
    179.                 if (_rayCastPlane.Raycast(dragStartRay, out var dragStartEntry))
    180.                 {
    181.                     _dragStartPos = dragStartRay.GetPoint(dragStartEntry);
    182.                 }
    183.             }
    184.  
    185.             // We are dragging the camera.
    186.             _isDragging = ctx.started || ctx.performed;
    187.         }
    188.  
    189.         /// <summary>
    190.         /// Updates the rotation flag of the camera.
    191.         /// </summary>
    192.         /// <param name="ctx">The InputAction callback context</param>
    193.         public void OnRotate(InputAction.CallbackContext ctx)
    194.         {
    195.             if (_isBusy) return;
    196.  
    197.             // We are rotating the camera.
    198.             _isRotating = ctx.started || ctx.performed;
    199.  
    200.             // Do a snap to certain angles if feature activated
    201.             if (!ctx.canceled || !snapRotation) return;
    202.             _isBusy = true;
    203.             SnapRotation();
    204.         }
    205.  
    206.         /// <summary>
    207.         /// Updates the zoom of the camera.
    208.         /// </summary>
    209.         /// <param name="ctx">The InputAction callback context</param>
    210.         public void OnZoom(InputAction.CallbackContext ctx)
    211.         {
    212.             if (!ctx.performed || ctx.ReadValueAsObject() == null) return;
    213.  
    214.             var zoom = Mathf.Sign(((Vector2)ctx.ReadValueAsObject()).y);
    215.             // Clamp the new zoom value between min/max.
    216.             zoom = Mathf.Clamp(_newZoomPos.y - zoom, zoomClamp.x, zoomClamp.y);
    217.             _newZoomPos = new Vector3(0F, zoom, -zoom);
    218.         }
    219.  
    220.         /// <summary>
    221.         /// Snaps the rotation of the camera to specific angles and performs a short animation.
    222.         /// </summary>
    223.         private void SnapRotation()
    224.         {
    225.             // ReSharper disable once HeapView.DelegateAllocation
    226.             transform.DORotate(SnappedVector(), 0.2F).SetEase(Ease.OutBounce).OnComplete(() =>
    227.             {
    228.                 _isBusy = false;
    229.                 _newRotation = transform.rotation;
    230.             });
    231.         }
    232.  
    233.         /// <summary>
    234.         /// Generates a vector for the camera snapping to 8 pre set angles.
    235.         /// </summary>
    236.         /// <returns>A vector to snap the camera to.</returns>
    237.         private Vector3 SnappedVector()
    238.         {
    239.             var eulerAngles = transform.eulerAngles;
    240.             var endValue = eulerAngles.y switch
    241.             {
    242.                 >= 24 and <= 68 => 45.0F,
    243.                 >= 69 and <= 113 => 90.0F,
    244.                 >= 114 and <= 158 => 135.0F,
    245.                 >= 159 and <= 203 => 180.0F,
    246.                 >= 204 and <= 248 => 225.0F,
    247.                 >= 249 and <= 293 => 270.0F,
    248.                 >= 294 and <= 338 => 315.0F,
    249.                 _ => 0.0F
    250.             };
    251.  
    252.             return new Vector3(eulerAngles.x, endValue, 0F);
    253.         }
    254.     }
    255. }
    I just started with Unity and C# few days ago (coming from Java development), so if you have any improvements, please let me know.

    I hope this helps.
     
    Last edited: Apr 14, 2023
    jeepcreep, sciguy and profitdefiant like this.
  7. jeepcreep

    jeepcreep

    Joined:
    Oct 20, 2015
    Posts:
    7
    Thanks a million! This solved *exactly* what I was looking for actually for quite some time... well done!

    One typo: you were meaning to write: "DragCamera and RotationCamera are Action Type Button."

    Also for use in own projects, it might be worth noting that the clamp x and z values must be in relation to your respective project's dimensions. E.g. when just leaving the 40 and 60 figures as given in the examples, the whole thing literally blew me away off the planet :D

    Good work, thanks again!