Mouse-Looking Better


In the first Foxhunt, I handled input, cursor-movement and mouse-looking all in the same block of code. I have since learned that these should be thought about separately, but processed together. Mouse interaction was okay before, but some jitter inevitably crept in. Especially in the situation of circle-strafing: where the player moves sideways while keeping focused on a central point. Circle strafing involves both cursor-movement and mouse-looking, so if the two are not working together, you will jitter. [1]

The first step is to separate input (taking raw user input) and processing (doing stuff to the input like smoothing or accelerating it) and output (applying the processed input to actually move the player). Input should be taken on the Update() cycle. I have learned that FixedUpdate will sometimes miss a click or keydown event, and LateUpdate is too late (unless your entire code executes there, which mine cannot). Here is the part of Update which takes mouse input multiplied by sensitivity (a user preference) and something called pitchMult (I wanted pitch to be more sensitive than yaw)...

    private void Update() { // for taking input
        mouseRaw = new Vector2(Input.GetAxis("Mouse X") * sensitivity, Input.GetAxis("Mouse Y") * sensitivity * pitchMult);
    }

I did some processing of the input in old Foxhunt, but now I am planning to add a certain amount of mouse smoothing and acceleration, and to be able to turn both OFF, if the user chooses. So here is the processing of my raw mouse input into usable absolute pitch and yaw, dependent on user settings. This is its own function because I wanted to play around with where to run it: Fixed, Late, or Update. Right now it is called from Fixed, along with the cursor-movement code. 

    void ProcessRot() {
        Vector2 accel = Vector2.one;
        if (MouseAccelerate) {
            accel = new Vector2(Mathf.Clamp(1f + Mathf.Pow((mouseRaw.x * rotAccel), 2), 1f, accLimit), Mathf.Clamp(1f + Mathf.Pow((mouseRaw.y * rotAccel), 2), 1f, accLimit));
            accel *= 0.6f; // adjust for increased sensitivity
        }
        if (MouseSmooth) {
            inputSmoother = Vector2.Lerp(inputSmoother, mouseRaw, snappiness * Time.deltaTime);
            Vector2 mouseDelta = Vector2.Scale(inputSmoother, new Vector2(accel.x * rotSpeed * rotSpeedMult, accel.y * inverseY * rotSpeed * rotSpeedMult));
            pitch = Mathf.Clamp(pitch - mouseDelta.y, pitchLimit.x, pitchLimit.y);
            yaw = yaw + mouseDelta.x;
        } else { // or reg mouse
            pitch = Mathf.Clamp(pitch - mouseRaw.y * accel.y * inverseY * rotSpeed * rotSpeedMult, pitchLimit.x, pitchLimit.y);
            yaw = yaw + mouseRaw.x * accel.x * rotSpeed * rotSpeedMult;
        }
    }

Let's look closer at the mouse smoothing option. The inputSmoother line takes raw mouse input and Lerps between that and whatever the smoothed input was last frame [2].  The closer (snappiness*time.deltaTime) is to 1, the closer the smoothed input will hew to the raw input. The closer to zero, the longer smoothed will take to catch up to raw.  See below what a snappiness factor of 8 can do to my circle-strafing problem. The mouseDelta line is then where I factor in all the modifiers to the smoothed mouselook, like rotSpeed (my own base preference), rotSpeedMult (in case I want to slow down the mouse's responsiveness during the game), acceleration if it is happening, and inverseY (in case the player likes that option). Finally, I add mouseDelta to the previous frame's yaw and pitch while clamping the pitch (so user can't look absolutely straight up or straight down.

Circle-strafing without smooth.Circle-strafing with smoothing.

Finally, it's time to output the processed mouse input to the actual transforms' rotation. This takes place on FixedUpdate. Yaw and pitch are separated over two different objects so that I always have a clean transform.forward no matter how far up or down the player is looking. (The user camera that pitches is hierarchically below the transform, so rotation must be local)

if (rotating) {
    transform.eulerAngles = new Vector2(0, yaw); // rotate Y the player move transform
    cam.localRotation = Quaternion.Euler(pitch, 0, 0); // rotate X the camera within player
}

Now that I have as smooth a mouselook as I can get without sacrificing too much responsiveness, let's look at another possible source of jitter in the next d'log: a carried object smoothly following that mouselook...


I found the following posts useful for writing this post and integrating into my own code:

1. For update cycle issues and overall jitter reduction discussion.

2. For a simple mouse-smoothing solution.

Leave a comment

Log in with itch.io to leave a comment.