Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Resolved Self-resolved First person FPS Input System Movement and Camera, multiple schemes

Discussion in 'Input System' started by Joseph-CF-Rothwell, Apr 3, 2021.

  1. Joseph-CF-Rothwell


    Mar 30, 2021
    SELF RESOLVED (roughly speaking. See own reply)

    Good morning,
    I'm relatively new to coding, making a 1st-person game for fun. I've spent the past 4 days trying to implement Input System, without good knowledge of the older Input schema before this. Using Unity 2020.3.2f1 with Input System v1.0.2.

    I have an Input asset, action map, and various actions set up with two schemes, Keyboard+Mouse and Gamepad. A sample of current bindings so you can see how I'm handling things:

    Move [Val V2] - WASD [2D Vec], Left Stick
    Look Gamepad [Val V2] - Right Stick
    Look Mouse [Val V2] - Delta [Mouse] <- Currently handle differently in code
    Crouch Toggle [Button] - C Key, Left Stick Button
    Crouch Hold [Button] - Ctrl Key, Button West

    Other inputs include Sprint hold/toggle, Zoom hold/toggle, actions like interact/jump.

    Right now I have a player object with character controller on it, main camera child to that, and a monolithic PlayerController script, which I plan to refactor when I've got everything working (and when I've learnt some best practices for how to refactor!).

    Some problems I have:
    • I don't know if I'm doing any of this right in any way. I could be using completely wrong methodologies and wouldn't have a clue. I don't know how to fix or improve it.
    • Mouse sensitivity is still too high, despite the fact I'm
      • modifying incoming vec2 by 1.1f, then by 0.01f, then even by 0.1f if zooming. Even when I remove that 1.1f, it's still too high.
      • Contrast with input from the gamepad, which is too slow even when being multiplied by 2.0f.
    • When I manage to slow down the mouse, it jitters across the screen; even the smallest mouse movement can jump many pixels on screen, but it seems inconsistent
    • When I tried combining the two Look actions,
      • I had no easy way to apply a different sensitivity for the mouse (except,
      • Meant that the mouse was being called in FixedUpdate, which made it janky
      • When I tried pulling the LookUpdate method out of FixedUpdate (so it would be only used from the registered Awake statements for both Mouse and Gamepad) the Gamepad look stopped working (would move a tiny amount for only the frame of input).
    • All the tutorials I can find is for the old input get axis stuff, and the few tutorials which do use the new input system either don't demonstrate both mouse and gamepad, or are for non-FPS type games/controls
    • Documentation is too hard to understand

    Relevant code:
    I'm attaching the cs file as well, but putting the relevant code here. The cs file has other unfinished code in it like sprinting and zooming, but I'm including anyway. Some of the methods and variables have different names in there, because I changed some things to make the relevant code below clearer.

    Code (CSharp):
    1. //There is other code in this class for things like sprinting, zooming, crouching
    2. //So I've cut those bits out and commented where relevant
    3. //All the comments in this class aren't indicative of how I actually comment my code, they're there because I've stripped out some of the context. Also put some simple things on one line instead of 2.
    5. (using UnityEngine and UnityEngine.InputSystem)
    6. namespace ProjectAlpha.Player
    7. {
    8.     public class PlayerController : MonoBehaviour
    9.     {    // ---References
    10.         private CharacterController _characterController;
    11.         private InputMap _inputMap;
    12.         private InputMap.AMGameplayActions _imgmap;
    13.         public Transform playerCamera;
    15.         // ---Movement variables
    16.         private const float gravity = -13.0f;
    17.         private float _playerVelocityY, _playerSpeed;
    18.         private const float MoveSmoothTime = 0.1f;
    20.         private Vector2 _targetMoveDirection =;
    21.         private Vector2 _currentDirection =;
    22.         private Vector2 _currentDirectionVelocity =;
    24.         // ---General Look variables
    25.         private float _calculateSensitivity;
    26.         private Vector2 _currentLookDelta =;
    27.         private Vector2 _currentLookDeltaVelocity =;
    28.         private const float LookSmoothTime = 0.03f;
    29.         private float _cameraPitch;
    30.         private Vector2 _look =;
    32.         // ---Mouselook variables
    33.         [Range(0.0f, 5.0f)] private float mouseLookSpeed = 1.1f;
    34.         private const float MouseSpeedMagicModifier = 0.01f;
    35.         private float _conditionalMouseZoomSpeedModifier; //switches between 0.1 or 1f
    36.         private float _conditionalNonMouseZoomSpeedModifier = 1.0f; //same but 0.4 or 1f
    37.         private float _totalMouseSensitivity;
    39.         // ---Gamepadlook variables - generically named for future
    40.         private float nonMouseLookSpeed = 2f;
    42.         private void Awake()
    43.         {
    44.             _characterController = GetComponent<CharacterController>();
    45.             _inputMap = new InputMap();
    46.             _imgmap = _inputMap.AMGameplay;
    47.             _totalMouseSensitivity = mouseLookSpeed * MouseSpeedMagicModifier;
    49.             _imgmap.Move.performed += ctx => _targetMoveDirection = ctx.ReadValue<Vector2>();
    50.             _imgmap.Move.canceled += ctx => _targetMoveDirection =;
    52.             //If mouse, pass context to LookUpdate and say yes am a mouse
    53.             _imgmap.MouseLook.performed += ctx => LookUpdate(ctx.ReadValue<Vector2>(), true);
    54.             //If not a mouse, set _look (which gets handled in FixedUpdate)
    55.             _imgmap.Look.performed += ctx => _look = ctx.ReadValue<Vector2>();
    56.             _imgmap.Look.canceled += ctx => _look =;
    57.         }
    59.         private void FixedUpdate()
    60.         {
    61.             MovementUpdate();
    62.             LookUpdate(_look, false); //Updating using _look, tell method not a mouse
    63.         }
    65.         private void MovementUpdate()
    66.         {    //Some code here modifies _playerSpeed if sprinting,zooming,crouching bools are set
    68.             //Handling gravity
    69.             if (_characterController.isGrounded) { _playerVelocityY = 0.0f; }
    70.             _playerVelocityY += gravity * Time.deltaTime;
    72.             _targetMoveDirection.Normalize();
    73.             _currentDirection = Vector2.SmoothDamp(_currentDirection, _targetMoveDirection, ref _currentDirectionVelocity, MoveSmoothTime);
    75.             var velocity = (transform.forward * _currentDirection.y + transform.right * _currentDirection.x) * _playerSpeed + (Vector3.up * _playerVelocityY);
    76.             _characterController.Move(velocity * Time.deltaTime);
    77.         }
    79.         private void LookUpdate(Vector2 targetDelta, bool isMouse)
    80.         {
    81.             _calculateSensitivity = isMouse switch
    82.             {
    83.                 //The _conditional floats get modified by a zoom method (not included)
    84.                 true => _totalMouseSensitivity * _conditionalMouseZoomSpeedModifier,
    85.                 false => nonMouseLookSpeed * _conditionalNonMouseZoomSpeedModifier
    86.             };
    88.             _currentLookDelta = Vector2.SmoothDamp(_currentLookDelta, targetDelta, ref _currentLookDeltaVelocity,
    89.                 LookSmoothTime);
    91.             _cameraPitch -= _currentLookDelta.y * _calculateSensitivity;
    92.             _cameraPitch = Mathf.Clamp(_cameraPitch, -90.0f, 90.0f);
    94.             playerCamera.localEulerAngles = Vector3.right * _cameraPitch;
    95.             transform.Rotate(Vector3.up * (_currentLookDelta.x * _calculateSensitivity));
    96.         }
    97.     }
    98. }
    The first shows gamepad look first, then mouse look after.

    The second shows changing the mouse speed mid-game, and the jitter is more noticeable.

    Any advice, corrections, suggestions, anything to help me improve this. My main goal is to have functioning move and look with both Gamepads and Keyboard+Mouse. I want to continue using the InputSystem rather than switching back to the old system, because that seems to be the future for Unity, and because I'd like the benefits of being able to extend controller support later as the project progresses.

    Any advice would be greatly appreciated. Thanks!

    Attached Files:

    Last edited: Apr 3, 2021
  2. Joseph-CF-Rothwell


    Mar 30, 2021
    Self resolved
    So I kept going with this, after having a good think. I think I've managed to iron out some of the issues I was having.

    Taking in move and look to code
    Before, I was using this sort of expression in Awake:
    _inputMap.AMGameplay.Look.performed += ctx => LookUpdate(ctx.ReadValue<Vector2>(), true);

    The problem with this is that if it is from a gamepad, even on pass-through, it doesn't seem to work, so you have to then have separate commands for mouse and gamepad.
    This is fixed by instead taking it directly from Update with:
    _look = _inputMap.AMGameplay.Look.ReadValue<Vector2>();

    This way, you end up with much more manageable and useful input that can be handled mostly the same depending on whether it is a mouse or gamepad.

    However, I also wanted to be able to independently modify the sensitivity depending on mouse or gamepad, so I ended up doing it this way, which seems to work:

    Code (CSharp):
    1. bool _LookDeviceIsMouse;
    2. float NonMouseSensitivityModifier = 1.0f;
    3. float MouseSensitivityModifier = 0.9f;
    5. private void Awake()
    6. {
    7.     _gameplayMap.Look.performed += ctx => LookDeviceSet(ctx);
    8. }
    10. private void Update()
    11. {
    12.     LookUpdate(_gameplayMap.Look.ReadValue<Vector2>();
    13. }
    15. private void LookDeviceSet(InputAction.CallbackContext context)
    16. {
    17.     _lookDeviceIsMouse = == "Mouse";
    18. }
    20. private void LookUpdate(vector2 lookValue)
    21. {
    22.     var modifier = NonMouseSensitivityModifier;
    23.     if (_lookDeviceIsMouse) modifier = MouseSensitivityModifier;
    24.     lookValue *= modifier;
    25. }
    ^ Basically, you're always pulling the look delta through Update, but any time there is input from mouse or gamepad you find out through the registered action in Awake. I dunno if this will break if you get input from both at the same time or not, though.

    Another headache was the jittering. It seems that this was somehow related to my SmoothDamp on the look, the value was too high; bumping it up even higher makes it spin into orbit too.

    I rewrote some other things too, but really once I did the above (which I also did to the movement controls) things improved immediately.

    This was definitely a case of having overcomplicated it all, too. With so much going on, my brain wasn't on the procedure, instead was stuck on values. Simplifying the problem made me see the problem stemmed from how I was getting the data (continuously or snapshotted, I guess).
    Last edited: Apr 3, 2021
    attilabacsa64 and Baris-B like this.