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!