Fixing Roblox's Camera (Interpolation)

The Camera System

Tutorial Level: Intermediate

Roblox's camera system is unique, in that most experiences use the same cameras. The camera system is solid, works with most games, and for the most part serves it's purpose in creating fun and engaging experiences.

A Disclaimer

This tutorial is made under the assumption that you have not created a modified starter player script. If you have, then you must make the necessary change wherever your code equivalent of the camera system is.
Along with this, the PlayerModule code is subject to change. Line numbers are given, but be aware to look instead for function names in case of updates to the codebase.

An Issue

While the camera system might be decent, it’s not perfect.
For people that play lots of first person experiences, or have large monitors, you might notice one thing about the camera - it’s choppy. For quicker movements this is hardly noticeable, but for the fine grained, slower movements, you might notice that it becomes nearly unbearable.

A Case Example

If you’ve played a survival game, any with a sniper in it, you might have noticed that aiming at people from far away is actually very disorienting - the farther away that the user is, the worse that this effect gets. This is because the backend code is running in segments equal in distance to the sensitivity number in your settings.

A Solution!

You might have picked up that from this information that if you were to simply lower your in-game sensitivity, and then raise your mouse DPI, it would smooth the camera. This works! However, once you begin to navigate UIs and tab back into desktop, you might see an issue arise - this sensitivity works everywhere.
Fret not though! With a couple of lines of code, you can not only lessen this issue for yourself, but also
for anyone that plays your experience as well!

A Guide

You’ll want to start by modifying the StarterPlayerScript - this is hidden by default, and only shows once you start a game. To get this script, play your game in the Studio environment.
When your character loads in, copy the script by navigating to

StarterPlayer > StarterPlayerScripts > PlayerModule

PlayerModule is the module that you’ll want to copy. Copy this so this is in your clipboard, and then stop the play session.

Once you have stopped the session, go back into the StarterPlayerScripts folder that you had navigated to originally. Paste the module from your clipboard into this folder and it will show with all of its sub modules. The code that you’ll want to change is inside of CameraInput. This module is a child of the CameraModule. Double click the module to edit it.

There are two important segments of code that are being modified here - the code which returns our camera rotation, and the code which takes a callback from the UserInputService to set our rotation.

First Part

Navigate down to line 180 of CameraInput. You should find a function named CameraInput.getRotation(boolean?)
This is the function we will be modifying.
At the top of the function, add the following lines:

local cameraDiff = Vector2.zero:Lerp(mouseState.Movement, worldDt * 10)
mouseState.Movement = mouseState.Movement - cameraDiff

Once you have added those two lines at the top of the function, go down another couple lines. In the same function, there is a variable kMouse being declared… Overwrite this is such:

local kMouse = cameraDiff

Once you are finished, you should have something like somewhat like this:

function CameraInput.getRotation(disableKeyboardRotation: boolean?): Vector2
	local cameraDiff = Vector2.zero:Lerp(mouseState.Movement, worldDt * 10)
	mouseState.Movement = mouseState.Movement - cameraDiff
	local inversionVector = Vector2.new(1, UserGameSettings:GetCameraYInvertValue())
	-- keyboard input is non-coalesced, so must account for time delta
	local kKeyboard = Vector2.new(keyboardState.Right - keyboardState.Left, 0)*worldDt
	local kGamepad = gamepadState.Thumbstick2
	local kMouse = cameraDiff
	local kPointerAction = mouseState.Pan
	local kTouch = adjustTouchPitchSensitivity(touchState.Move)

	if disableKeyboardRotation then
		kKeyboard = Vector2.new()
	end

	local result =
		kKeyboard*ROTATION_SPEED_KEYS +
		kGamepad*ROTATION_SPEED_GAMEPAD +
		kMouse*ROTATION_SPEED_MOUSE +
		kPointerAction*ROTATION_SPEED_POINTERACTION +
		kTouch*ROTATION_SPEED_TOUCH
		
	return result*inversionVector
end

You’re now halfway there! If you run the game now, you might notice that the camera feels a bit smoother, but we’re not finished yet - you might notice that sometimes the mouse will stop dead in it’s tracks, or jump regardless. Move onto the next part to help mitigate this.

Second Part

Now that you are finished with the rotation function, navigate down a couple more lines till you find a function that is named mouseMovement(input) (around line 220)
In this function, the change is much more simple. Go ahead and modify the final line to this snippet:

mouseState.Movement = mouseState.Movement + Vector2.new(delta.X, delta.Y)

Now, this function should look like this:

local function mouseMovement(input)
	local delta = input.Delta
	mouseState.Movement = mouseState.Movement + Vector2.new(delta.X, delta.Y) 
end

Congratulations! When you start moving your character’s mouse, you will notice it is much more smooth and easier on the eyes!

Breakdown

So what's happening here?

This modification can be simply described as interpolation. The original Roblox code takes the raw input from the user input service, and uses that to apply angles to the camera. This works in larger amounts, but becomes very apparent once you are using fine grained movements.
What you have just done is applied interpolation to the angles, which creates an average of the change in camera angle over a period of a couple of frames.

Unmentioned Code

In the code, there are 2 unmentioned parts:

  • worldDt
  • mouseState

worldDt is a variable assigned early on in the script, and changes every frame. This is the equivalent of asking “how much time has elapsed since the last frame?”
mouseState is an object also created earlier on in the script, and stores some more information about the mouse’s movement. You can see this inside the code for mouse rotation, but for our case we do not need it.
If you’re curious about these, try CTRL+Left Click on the variables to see their definitions!

Code Changes

Just to explain more about what’s happening, let’s go on a line-by-line breakdown.

local cameraDiff = Vector2.zero:Lerp(mouseState.Movement, worldDt * 10)

This creates a variable assigned to an interpolation from a zero’ed vector (0,0) to the target mouse movement. This is multiplied by a number (in our case, 10) to lessen the smoothing, which makes the interpolation more true to the actual mouse sensitivity in game. Change this to whatever you’d like! This is what creates our smoothing effect, but by itself would not be doing us much of a favor. This variable needs state! We need to know how much we’ve already moved, so that we can move the full distance given by the user. That’s where the next line comes in:

mouseState.Movement = mouseState.Movement - cameraDiff

This line subtracts the interpolated amount from the original amount that we had! This makes it so if we move 5 degrees and interpolate by 1, the camera still knows that it still has to move by 4 additional degrees, and to continue rotating in the next frame.
So how do we keep the state if it gets reset every input change? Simple! Let’s move onto the next line:

mouseState.Movement = mouseState.Movement + Vector2.new(delta.X, delta.Y)

This code modifies mouse state again. In the old code, it was only setting mouseState.Movement to the next difference in movement. This is no good for interpolation! Instead, we now add this value to the existing value, so that the camera knows it still has to move a certain amount of distance in addition to this new change.
Voila! That’s the entire system demystified, and done in as few lines as possible! If you’d like to try modifying this code, you can try different types of interpolation / easing, it’s all up to you!

24 Likes

No comments yet??? Man this is rly cool!! I’ll see if i can use it in my game!

Is there a place to get the direct code for this or do I need to recreate using your steps?

The reduced sensitivity isn’t great, but it got me wondering at the origin of the issue. In my game, I once had an issue caused by the lack of precision from the mouse coordinates. Basically the mouse coordinate is calculated in pixels, so any movement smaller than a pixel is not considered.

I don’t even know if it would be possible to get the position of the mouse between pixels in windows

The issue described seems to be a “big” jump when moving the mouse around when zoomed in? Correct me if I am wrong.
If so, the issue is likely the cause of the lack of precision with mouse location

2 Likes

You are correct! This is exactly the issue.
Unfortunately, this stems from within the UserInputService itself - when you set the sensitivity setting, it then uses that number in steps - meaning that if you move one pixel and have sensitivity set to 4, instead of interpolating THAT, it will wait until you move 4 pixels altogether before registering as an input. This leads to the choppy affect. This will ‘fix’ the camera but remains inaccurate altogether. If it is done at the UserInputService level, interpolation would not be required and all inputs would appear smooth and still be accurate - though changing the InputChanged event level would be quite the ask of Roblox.

3 Likes