Handle FPS game input in Unity

4 minute read

I wanted to know how unity realizes viewpoint operation and movement of FPS and TPS games, so I read the tutorial code and studied.

[FPS Microgame / Templates Unity Asset Store](https://assetstore.unity.com/packages/templates/fps-microgame-156015?_ga=2.176853990.2137146024.1596953246-2127250233.1596953244)

version is 1.1.2
The version of unity is 2019.4.7f1

The input receiving part is handled by Scripts / PlayerInputHandler.cs.

Start()

    private void Start()
    {
        m_PlayerCharacterController = GetComponent<PlayerCharacterController>();
        DebugUtility.HandleErrorIfNullGetComponent<PlayerCharacterController, PlayerInputHandler>(m_PlayerCharacterController, this, gameObject);
        m_GameFlowManager = FindObjectOfType<GameFlowManager>();
        DebugUtility.HandleErrorIfNullFindObject<GameFlowManager, PlayerInputHandler>(m_GameFlowManager, this);

        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }

here
m_PlayerCharacterController is the controller that operates the main character.

m_GameFlowManager is a controller that manages the entire game.

GetMoveInput()

This is a function related to character movement.

    public Vector3 GetMoveInput()
    {
        if (CanProcessInput())
        {
            Vector3 move = new Vector3(Input.GetAxisRaw(GameConstants.k_AxisNameHorizontal), 0f, Input.GetAxisRaw(GameConstants.k_AxisNameVertical));

            // constrain move input to a maximum magnitude of 1, otherwise diagonal movement might exceed the max move speed defined
            move = Vector3.ClampMagnitude(move, 1);

            return move;
        }

        return Vector3.zero;
    }

Let’s look at them in order.

CanProcessInput()

In this game you can unlock the mouse by pressing ʻesc`.
When the lock is unlocked, the character cannot be operated (moved, changed direction).

If you check the source code inside,

    public bool CanProcessInput()
    {
        return Cursor.lockState == CursorLockMode.Locked && !m_GameFlowManager.gameIsEnding;
    }

In other words, if the cursor is locked ** and the game is not ending **, you can enter it.

Input.GetAxisRaw(string axisName)

Input-GetAxisRaw –Unity Script Reference

Returns a value that has not been filtered by the virtual axis smoothing identified by axisName
The value will be in the range -1…1 for keyboard and joystick input. Since input is not smoothed, keyboard input will always be either -1, 0 or 1. This is useful if you want to do all smoothing of keyboard input processing yourself.

I’m not sure from this alone, but it normalizes the input of the game pad and the input of the keyboard and mouse in the range of [-1, 1] for each axis and returns it.
In other words, a convenient function that returns the direction with a normalized number for multiple input devices.

By the way, ʻInput.GetAxisRaw returns either -1 or 1 for keyboard input, but ʻInput.GetAxis returns a smoothed value.
If you want to reproduce realistic behavior, is ʻInput.GetAxis` more suitable?
ref: http://albatrus.com/main/unity/7209

Vector3.ClampMagnitude(Vector3 vector, float maxLength)

A function that allows the size of vector to be clamped with maxLength.

GetMoveInput()

Going back to the beginning, about GetMoveInput ().
This means

** A function that returns a direction vector so that the magnitude of the input value is within 1 if input is possible, otherwise it returns a 0 vector **

It can be seen that it is.

GetLookInputsHorizontal() / GetLookInputsVertical()

It is a function related to the rotation of the viewpoint.

Since the axes are different, we will only look at Horizontal.

    public float GetLookInputsHorizontal()
    {
        return GetMouseOrStickLookAxis(GameConstants.k_MouseAxisNameHorizontal, GameConstants.k_AxisNameJoystickLookHorizontal);
    }

You can see that we are just calling GetMouseOrStickLookAxis.

GetMouseOrStickLookAxis(string mouseInputName, string stickInputName)

    float GetMouseOrStickLookAxis(string mouseInputName, string stickInputName)
    {
        if (CanProcessInput())
        {
            // Check if this look input is coming from the mouse
            bool isGamepad = Input.GetAxis(stickInputName) != 0f;
            float i = isGamepad ? Input.GetAxis(stickInputName) : Input.GetAxisRaw(mouseInputName);

            // handle inverting vertical input
            if (invertYAxis)
                i *= -1f;

            // apply sensitivity multiplier
            i *= lookSensitivity;

            if (isGamepad)
            {
                // since mouse input is already deltaTime-dependant, only scale input with frame time if it's coming from sticks
                i *= Time.deltaTime;
            }
            else
            {
                // reduce mouse input amount to be equivalent to stick movement
                i *= 0.01f;
#if UNITY_WEBGL
                // Mouse tends to be even more sensitive in WebGL due to mouse acceleration, so reduce it even more
                i *= webglLookSensitivityMultiplier;
#endif
            }

            return i;
        }

        return 0f;
    }

It’s a little complicated, but I’ll go through it step by step.

            // Check if this look input is coming from the mouse
            bool isGamepad = Input.GetAxis(stickInputName) != 0f;
            float i = isGamepad ? Input.GetAxis(stickInputName) : Input.GetAxisRaw(mouseInputName);

Here, it is decided whether to use the input of the gamepad or the input of the mouse.
See Input.GetAxisRaw () for a description of ʻInput.GetAxis (), ʻInput.GetAxisRaw ().
If there is gamepad input, use that input, otherwise use mouse input.

It’s interesting that mouse uses ʻInput.GetAxisRaw, but gamepad uses ʻInput.GetAxis. UX problem?

            // handle inverting vertical input
            if (invertYAxis)
                i *= -1f;

invertAxis is a constant that can be defined from the editor.
If you want to invert the y-axis, you can invert it by setting ʻinvertYAxis = true ... However, since this function does not judge the X-axis and Y-axis, setting ʻinvertYAxis = true also operates the x-axis. The opposite (v1.1.2)

Next, set the sensitivity

            // apply sensitivity multiplier
            i *= lookSensitivity;

After that, the scales of gamepad and mouse are aligned.

            if (isGamepad)
            {
                // since mouse input is already deltaTime-dependant, only scale input with frame time if it's coming from sticks
                i *= Time.deltaTime;
            }
            else
            {
                // reduce mouse input amount to be equivalent to stick movement
                i *= 0.01f;
#if UNITY_WEBGL
                // Mouse tends to be even more sensitive in WebGL due to mouse acceleration, so reduce it even more
                i *= webglLookSensitivityMultiplier;
#endif
            }

Since mouse is the amount of movement in frame units, gamepad also applies Time.deltaTime to match it (?). I wasn’t sure about this area, but it seems good to recognize that the scales are just aligned for the time being.