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.
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.
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
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