The key to making camera systems is relatively a simple 3-step process:
- Define a CameraSubject and CameraOffset
- Use user input to control the camera’s rotation
- Apply the offsets and rotations in a meaningful order
For example, you may want to use HumanoidRootPart as the camera subject, as it will be the most stable part of a character, and from there you can create offsets.
For the camera rotations, you should almost always use the following function:
CFrame.fromEulerAngles(Pitch, Yaw, Roll, Enum.RotationOrder.YXZ)
This is because the order in which the rotation axis are applied is crucial, as otherwise you end up with a skewed or broken camera. You want the camera to pan first, pitch second, and tilt last in order to properly perserve camera manipulations. You should also avoid using CFrame:Lerp() on the rotations whenever possible and opt to math.lerp() or Vector3:Lerp() individual axis components to prevent unwanted camera motions.
A simple pseudo-implementation can be as follows:
-- Framerate independent lerp smoothing parameters
local function smoothlerp(rate: number, dt: number): number
return 1 - math.exp(dt * -rate)
end
-- Wrap a rotation value around [-180, 180)
local function wrapRotation(angle: number): number
return (angle + 180) % 360 - 180
end
function OnMouseInput(io: InputObject)
cameraPitch += io.Delta.Y * cameraSensitivity
cameraPitch = math.clamp(
cameraPitch,
-pitchLimit,
pitchLimit
)
cameraYaw += io.Delta.X * cameraSensitivity
cameraYaw = wrapRotation(cameraYaw)
end
function OnRenderStep(delta: number)
-- You can use smoothing here as well if this is what you need for your game
camPos = cameraSubjectPosition + cameraOffset
-- Here is where camera shakes can be added
camSmoothPos = dynamicOffset
-- The camera smoothing process as an example:
camSmoothedPos = camSmoothedPos:Lerp(
dynamicOffset,
smoothlerp(10, delta)
)
cameraRotation = CFrame.fromEulerAngles(
math.rad(cameraPitch),
math.rad(cameraYaw),
math.rad(cameraRoll),
Enum.RotationOrder.YXZ
)
camera.CFrame = cameraRotation + camPos + camSmoothedPos
end
From there you can add more features to your liking as always by adjusting the angles or positions in real-time.
If you need to smooth the Yaw value, I would recommend uncapping it or wrapping it against a larger value.
local function wrapRotationBig(angle: number): number
return (angle + 1_800) % 3_600 - 1_800
end
This is to prevent the camera from unexpectedly rotating in the opposite direction if the user rotates the camera too quickly.
And instead of math.lerp(smoothYaw, targetYaw, smoothlerp(rate, delta))
you would use this:
smoothYaw += wrapRotationBig(targetYaw - smoothYaw) * smoothlerp(rate, delta)
smoothYaw = wrapRotationBig(smoothYaw)
as this would wrap the difference as well, so it wont get confused when going between -1,800 and 1,799 and will properly use the smallest rotation of -1 degree as opposed to +3,599
While this wrapping behavior isnt required, it is recommended, as if you were to continuously spin the camera in one direction, or spin it one way more than another, you may end up having odd behaviors with your rotations once they get too large.