How to Add "Weight" to the Camera [Camera Tutorial]

Here is a simple tutorial on how to add weight to the camera so that it drags behind the character as they move instead of being rigidly attached to them.

This works by setting the camera subject to an invisible part which attempts to reach the character through linear interpolation. This has the effect of the dragging the camera behind the character as they move. One benefit to this is that it has minimal conflict with usual camera function and you can do things like pivot the camera as normal

Step 1:
Create a LocalScript inside StarterCharacterScripts, and insert a Part to the script.
4d7d274738411030f68c9bbf04240bd6

This part will be the camera subject which floats around behind the player, so change the properties so that it interferes with the rest of the workspace as little as possible.
b0d2d4414699c1db48924633604c4689
3448239829ac6837064d87a7cc3e59c1
bc41fe27ebce1dc96683ada6c1daca5a
The most important thing here is that the part is anchored so that it stays where we set it

Step 2:
Inside the LocalScript, set the camera subject to the Subject part, then create a function which :Lerp()'s the position of Subject to the position of the character’s head. This function will be connected to the render step of the game’s RunService so that the Subject part will inch closer and closer to the character every tick.

Source Code
local RunService = game:GetService("RunService")

local camera = workspace.CurrentCamera
local head = script.Parent:WaitForChild("Head")
local subject = script:WaitForChild("Subject")

subject.Position = head.Position
camera.CameraSubject = subject

local WEIGHT = 20

local function updateSubject()
	subject.Position = subject.Position:Lerp(head.Position,1/WEIGHT)
end

RunService:BindToRenderStep("UpdateSubject", Enum.RenderPriority.Camera.Value, updateSubject)

The WEIGHT variable determines how ‘heavy’ the camera is and can be adjusted to your liking, with 1 being no camera weight.

How to add different weights to horizontal and vertical camera movement

You might want to weight vertical motion differently from horizontal motion since your screen is likely shorter than it is wide.

To do this, separate the Subject position and the head position into vertical and horizontal components, Lerp them with separate weights, then add the results together for your total position.

Source Code
local RunService = game:GetService("RunService")

local camera = workspace.CurrentCamera
local head = script.Parent:WaitForChild("Head")
local subject = script:WaitForChild("Subject")

subject.Position = head.Position
camera.CameraSubject = subject

local WEIGHT_XZ = 20
local WEIGHT_Y = 5

local function updateSubject()
	local subPos = subject.Position
	local headPos = head.Position

	local xzPos = Vector3.new(subPos.X,0,subPos.Z):Lerp(Vector3.new(headPos.X,0,headPos.Z),1/WEIGHT_XZ)
	local yPos = Vector3.new(0,subPos.Y,0):Lerp(Vector3.new(0,headPos.Y,0),1/WEIGHT_Y)

	subject.Position = xzPos + yPos
end

RunService:BindToRenderStep("UpdateSubject", Enum.RenderPriority.Camera.Value, updateSubject)
How to make it work with shift lock

The above script causes the shift lock camera mode to break because shift lock only rotates the character if they are the camera subject (which has now been set to the Subject part). This can be fixed by manually rotating the character in the update function according to the camera LookVector:

Source Code
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")

local camera = workspace.CurrentCamera
local rootPart = script.Parent:WaitForChild("HumanoidRootPart")
local head = script.Parent:WaitForChild("Head")
local subject = script:WaitForChild("Subject")

subject.Position = head.Position
camera.CameraSubject = subject

local WEIGHT = 20

local function updateSubject()
	subject.Position = subject.Position:Lerp(head.Position,1/WEIGHT)
	
	if UserInputService.MouseBehavior == Enum.MouseBehavior.LockCenter then
		local lookXZ = Vector3.new(camera.CFrame.LookVector.X,0,camera.CFrame.LookVector.Z)
		rootPart.CFrame = CFrame.lookAt(rootPart.Position,rootPart.Position + lookXZ)
	end
end

RunService:BindToRenderStep("UpdateSubject", Enum.RenderPriority.Camera.Value, updateSubject)

UserInputService is used to check if the mouse is locked to the center, indicative of shift lock being active. If it is active, then the CFrame of the root part is set to point in the direction of the XZ component of the camera’s LookVector (only the XZ component since the root part should only follow the camera’s horizontal movement).

How to disable it in first person mode

Camera weight is undesirable in first person mode since your character will block most of the screen once the subject part lags behind, and the character also isn’t automatically made invisible in first person anymore since that requires the humanoid to be the camera subject. To fix both of these issues, you can set the camera subject to the humanoid if the camera is zoomed in to first person, and then back to the Subject part once it’s zoomed out again:

Source Code
local RunService = game:GetService("RunService")

local camera = workspace.CurrentCamera
local humanoid = script.Parent:WaitForChild("Humanoid")
local head = script.Parent:WaitForChild("Head")
local subject = script:WaitForChild("Subject")

subject.Position = head.Position
camera.CameraSubject = subject

local WEIGHT = 20

local function updateSubject()
	if (camera.CFrame.Position - subject.Position).Magnitude < 1 or (camera.CFrame.Position - head.Position).Magnitude < 1 then
		camera.CameraSubject = humanoid
		subject.Position = head.Position
	else
		camera.CameraSubject = subject
		subject.Position = subject.Position:Lerp(head.Position,1/WEIGHT)
	end
end

RunService:BindToRenderStep("UpdateSubject", Enum.RenderPriority.Camera.Value, updateSubject)

The distance between the camera and the head/subject part is checked to determine if the camera is zoomed into first person. If it’s less than 1 (first person mode), the camera subject is set back to the humanoid, which essentially returns the camera back to the Roblox default (no weight), and the Subject part is locked to the head. Otherwise (third person mode), the camera subject is set to the Subject part again and the part is Lerped.

(I would implement these fixes in the main tutorial but I’m keeping it separate for the sake of simplicity lol)

Now you’re done :sunglasses:

The reason this works is because :Lerp() updates the position of the subject by a fraction (1/WEIGHT) of the remaining distance to the root part, so that it moves fast when it’s far away but slows down the closer it gets, kind of like a spring.

Anyways, I hope this helped :slight_smile:

152 Likes

Woah, this is really cool dude thanks for sharing!!

I have one suggestion though, you should make the camera move faster on the Y axis so the character is always in view

5 Likes

Would always wonder why people used lerp over tweening a value. This helped me out a lot. Thank you for the contribution :slight_smile:

I have a question on lerping. How come when you stop moving, the Subject lerps to the exact position of the HumanoidRootPart? Wouldn’t it not move all the way as you only told it to move 0.05 percent (1/20) of the way there to its goal, being the HumanoidRootPart?

Amazing short and informational stuff being posted here. Ty

2 Likes

Man, you made my day with this post!

I’m working on a game where I use a player following camera (no first person view allowed) - and the camera view was a bit boring to me.

In addition, I may learned sg about Lerp() finally :slight_smile:

3 Likes

its 0.05 of the remaining distance every render step. So the distance does decrease as time goes on, but at a slower and slower speed as it gets closer. The decreasing speed is what gives it the nice effect.

It doesnt actually reach the exact position of the root part because it only moves a fraction of the remaining distance each time, so its impossible to cross the entire distance, but the rest of the distance eventually gets extremely small as time goes on so that you cant even notice it

2 Likes

tween is definitly smoother, but tweens are a bit inconvienient for this purpose because the goal is constantly changing, so that you would have to destroy the current tween, remake it with the new goal, and play it again every time the root part position changes.

for that reason i think Lerp is the natural choice for this

1 Like

thanks for the suggestion :slight_smile: I added a section at the bottom of how to do this

1 Like

Putting this In other words, the interpolation is asymptotic.

1 Like

Awesome, here’s something you can definitely use Math for Game Programmers: Juicing Your Cameras With Math - YouTube

2 Likes

is there a way to make it work with shift lock?(in the click, I activate shift lock on -00:02, in case the video doesn’t load, it basically makes the character be sideways while running forward)

1 Like

Humanoid.AutoRotate?

Thanks for pointing that out. I added a section at the bottom to fix this

1 Like

I am kind of new to vectors and CFrame but what does this mean and why does this work?

I have a guide for CFrames if you wanted to learn in general, but I’ll explain this thing specifically: A CFrame encodes the position and orientation of parts and cameras. The LookVector of a cameras CFrame is the direction of it’s view, and the LookVector of a part is the direction it’s front side is facing.

Basically what this code does is set the root part’s CFrame so that it’s position stays the same, but its front side points in the same horizontal direction as the camera. The CFrame.lookAt() constructor takes the position as the first argument, and a point in space to look at in the 2nd argument. For the second argument I took the horizontal component of the camera LookVector and added it to the position of the root part which creates a point in space which is displaced from the character in the direction we want them to look

1 Like

Hello @xXSanrioSlayer99, thank you for taking your time to make this tutorial for us.
I didn’t have any problems with the code. But, as I’m an amateur programmer, I couldn’t figure out a way to implement my camera offset values (0,0.05,0) to our new CurrentCamera.
Can you help me on this? Thanks!

1 Like

All you have to do is offset the goal position of the lerp based on your chosen offset:

We originally have this:

local function updateSubject()
	subject.Position = subject.Position:Lerp(rootPart.Position,1/WEIGHT)
end

but instead of lerp to rootPart.Position you can add your offset to get this:

local function updateSubject()
	local offset = Vector3.new(0, 0.05, 0)
    subject.Position = subject.Position:Lerp(rootPart.Position + offset,1/WEIGHT)
end

this will make it so the camera works to point towards the root part, plus your offset value.

1 Like

Question if i go in first person the whole thing breaks is there any way to fix this?

Whoops that was a big oversight by me. I added a section to disable camera weight while in first person

1 Like