Smooth camera movement with offset

How can I make the camera have the same behaviour as in the video (0:15-0:22) https://www.youtube.com/watch?v=3fDuCU1-5J4

create a part, then set the camerasubject to this part
in a function bound to render step with camera.priority + 1:
lerp the part to rootPart.Position + rootPart.CFrame.LookVector * offset (adjust offset as needed)

or you can also look into camera spring modules i think ive seen before

--untested pseudocode

local RunService = game:GetService("RunService")

local cameraPart = Instance.new("Part", workspace)
cameraPart.Size = Vector3.new(1,1,1)

RunService:BindToRenderStep(Enum.RenderPriorty.Camera.Value + 1, function()
  local targetPosition = rootPart.Position + rootPart.CFrame.LookVector * 2 -- adjust the 2 as needed
  local newPosition = cameraPart.Position:Lerp(targetPosition, 0.5) -- adjust the 0.5 as needed
  cameraPart.Position = newPosition
end)
3 Likes

I would look into a custom camera controller since it is pretty hard to have this effect with the default camera.

I’ll try to make a custom camera script but it’s pretty complicated.

EDIT: Just finished the script but I gotta eat lunch, will post soon!

1 Like

Ok, this method uses a custom camera controller to kind of create the effect you wanted. It does have a few issues such as the camera being able to phase through walls and welded objects (like seats) moving with the player. It wouldn’t be too difficult to fix them though.

Here’s a video of the script:


It has to be short because the file size was too large :(

And here’s the script itself:

local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

-- Settings (quite a lot) --

local MAX_ZOOM = 128
local MIN_ZOOM = 4
local DEFAULT_ZOOM = 16

local SCROLL_SPEED = 10

local CAMERA_OFFSET_Y = 3

local ROTATION_SPEED_X = 10
local ROTATION_SPEED_Y = 10

local CAMERA_FX_OFFSET = 3 -- This is the setting you wanted
local CAMERA_SMOOTH_FACTOR = 0.25 -- This is the smoothing for that setting

local CAMERA_ROTATE_MAX_ANGLE = 50

local player = Players.LocalPlayer

local camera = workspace.CurrentCamera
camera.CameraType = Enum.CameraType.Scriptable

-- Get Character --

local character = player.Character or player.CharacterAdded:Wait()
local humanoidRootPart: Part = character:WaitForChild("HumanoidRootPart")
local humanoid: Humanoid = character:FindFirstChild("Humanoid")

-- Internal Properties --

local zoom = DEFAULT_ZOOM
local rotateOffset = Vector3.zero
local cameraRotateOffset = Vector3.zero
local cameraOffset = Vector3.zero

-- Make Camera Follow Character --

-- Updates the position of the camera
local function UpdateCamera()
	-- Rotate the character
	humanoidRootPart.CFrame = CFrame.new(humanoidRootPart.CFrame.Position) * CFrame.Angles(rotateOffset.X, rotateOffset.Y, rotateOffset.Z)

	local cameraRotateOffsetCFrame = CFrame.Angles(cameraRotateOffset.X, cameraRotateOffset.Y, cameraRotateOffset.Z)
	local rotatedHumanoidRootPartCFrame = humanoidRootPart.CFrame * cameraRotateOffsetCFrame

	local lookVector = rotatedHumanoidRootPartCFrame.ZVector * zoom -- Technically actually uses Z Vector, not look vector

	local extraOffset = Vector3.new(0, CAMERA_OFFSET_Y, 0) + cameraOffset

	camera.CFrame = (humanoidRootPart.CFrame * cameraRotateOffsetCFrame) + lookVector + extraOffset
end

-- Camera Zoom --

UserInputService.InputChanged:Connect(function(input: InputObject)
	if input.UserInputType == Enum.UserInputType.MouseWheel then
		local zoomAmount = -input.Position.Z
		local zoomDelta =
			zoomAmount *
			SCROLL_SPEED *
			(0.2 + (math.map(zoom, MIN_ZOOM, MAX_ZOOM, 0, 1) * 0.8)) -- Smoothing factor
		
		zoom = math.clamp(zoom + zoomDelta, MIN_ZOOM, MAX_ZOOM)
	end
end)

-- Character Turn --

local isRightClicking = false

UserInputService.InputBegan:Connect(function(input: InputObject)
	if input.UserInputType == Enum.UserInputType.MouseButton2 then
		isRightClicking = true
		UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
	end
end)

UserInputService.InputEnded:Connect(function(input: InputObject)
	if input.UserInputType == Enum.UserInputType.MouseButton2 then
		isRightClicking = false
		UserInputService.MouseBehavior = Enum.MouseBehavior.Default
	end
end)

UserInputService.InputChanged:Connect(function(input: InputObject)
	if UserInputService.MouseBehavior == Enum.MouseBehavior.LockCenter then
		isRightClicking = true
	end
	
	if input.UserInputType == Enum.UserInputType.MouseMovement and isRightClicking then
		local mouseDeltaX = -input.Delta.X / camera.ViewportSize.X
		local mouseDeltaY = -input.Delta.Y / camera.ViewportSize.Y
		
		rotateOffset += Vector3.new(0, mouseDeltaX * ROTATION_SPEED_X, 0)
		cameraRotateOffset += Vector3.new(mouseDeltaY * ROTATION_SPEED_Y, 0, 0)
		
		-- Clamp the X
		cameraRotateOffset = Vector3.new(
			math.rad(math.clamp(math.deg(cameraRotateOffset.X), -CAMERA_ROTATE_MAX_ANGLE, CAMERA_ROTATE_MAX_ANGLE)),
			cameraRotateOffset.Y,
			cameraRotateOffset.Z
		)
	end
end)

-- Camera Strafe/Walk FX --

-- Run this before UpdateCamera() for strafe FX
local function UpdateCameraOffset(deltaTime: number)
	-- Average the inverse move vector and the camera offset
	local moveVector = -humanoid.MoveDirection
	
	-- Returns the weighted average between 2 vector3s
	local function AverageWeightedVectors(firstVector: Vector3, firstWeight: number, secondVector: Vector3, secondWeight: number)
		local AXES = {"X", "Y", "Z"}
		
		local newVectorParameters = {}
		
		for index, axis in AXES do
			newVectorParameters[index] = ((firstVector[axis] * firstWeight) + (secondVector[axis] * secondWeight)) / (firstWeight + secondWeight)
		end

		local newVector = Vector3.new(table.unpack(newVectorParameters))
		
		return newVector
	end
	
	cameraOffset = AverageWeightedVectors(cameraOffset, CAMERA_SMOOTH_FACTOR, moveVector * CAMERA_FX_OFFSET, deltaTime)
end

-- Run Service Events --

RunService.PreRender:Connect(function(deltaTime: number)
	UpdateCameraOffset(deltaTime)
	UpdateCamera()
end)

There’s a bunch of settings you can change to make this work to your liking!

Forgot to mention, the only way the camera moves is when you right click and drag so you would have to modify this to make it mobile friendly.