Dynamic FOV Script - Smooth Camera Zoom

What this script does (see video):

This script adjusts the field of view (FOV) of the camera based on the speed of the player’s character. The script uses a lerp function for a smooth transition between the current and target FOV values to create a more visually appealing effect. The FOV is continuously updated as the player moves, and if the character dies, the FOV is reset to the default value.

How to use:

Create a local script within StarterCharacterScripts.
Then paste the following code:

-- Roblox Lua script for controlling Field of View (FOV) based on player speed and zoom distance

-- Define variables
local player = game.Players.LocalPlayer
local humanoid, character
local RenderSteppedConnection = nil
local DiedConnection = nil
local AncestryChangedConnection = nil

local startFOV = 70 -- Initial FOV when not moving
local maxFOV = 90 -- Maximum FOV when moving at maximum speed
local startSpeed = 0 -- Initial speed
local maxSpeed = 100 -- Maximum speed
local fovSmoothness = 0.1 -- Smoothing factor for FOV transitions
local maxZoomDistance = 5 -- Maximum distance to zoom in

-- Linear interpolation function
-- The lerp function smoothly transitions between two values (Current FOV -> Target FOV) based on a specified parameter (fovSmoothness).
local function lerp(a, b, t)
	return a + (b - a) * t
end

-- Function to calculate FOV based on speed and distance
local function calculateFOV(speed, distance)
	local fov = startFOV + (speed / maxSpeed) * (maxFOV - startFOV)
	if distance < maxZoomDistance then
		local zoomFactor = 0.5 - (distance / maxZoomDistance)
		fov = fov + zoomFactor * (maxFOV - fov)
		fov = math.clamp(fov, startFOV, maxFOV) -- Clamp the FOV value between startFOV and maxFOV
	end
	return fov
end

-- Function to update FOV
local function updateFOV()
	local camera = game.Workspace.CurrentCamera
	if not camera or not character or not humanoid or not humanoid.Parent or not humanoid.RootPart or not humanoid.Parent:IsA("Model") or not humanoid.Parent:FindFirstChild("Head") or not character:FindFirstChild("Head") then
		return -- Exit if essential objects are not valid
	end
	local distance = (camera.CFrame.Position - character.Head.Position).Magnitude
	local speed = humanoid.RootPart.Velocity.magnitude
	local targetFOV = calculateFOV(speed, distance)
	local currentFOV = camera.FieldOfView
	local smoothedFOV = lerp(currentFOV, targetFOV, fovSmoothness)
	camera.FieldOfView = smoothedFOV -- Update the camera's FOV
end

local function handleCharacterRemoved()
	if RenderSteppedConnection then
		RenderSteppedConnection:Disconnect()
		RenderSteppedConnection = nil
	end
	if DiedConnection then
		DiedConnection:Disconnect()
		DiedConnection = nil
	end
	if AncestryChangedConnection then
		AncestryChangedConnection:Disconnect()
		AncestryChangedConnection = nil
	end
	humanoid = nil -- Clear humanoid reference
	character = nil -- Clear character reference
end

local function handleCharacterAdded(newCharacter)
	character = newCharacter
	humanoid = character:WaitForChild("Humanoid")
	RenderSteppedConnection = game:GetService("RunService").RenderStepped:Connect(updateFOV)
	
	DiedConnection = humanoid.Died:Connect(function()
		game.Workspace.CurrentCamera.FieldOfView = startFOV
	end)
	AncestryChangedConnection = character.AncestryChanged:Connect(function(_, parent)
		if parent == nil then
			handleCharacterRemoved()
		end
	end)
end

-- Function to handle player character addition
local function onCharacterAdded(newCharacter)
	handleCharacterAdded(newCharacter)
end

-- Function to handle player character removal
local function onCharacterRemoved(oldCharacter)
	handleCharacterRemoved()
end

-- Connect event handlers for player character changes
player.CharacterAdded:Connect(onCharacterAdded)
player.CharacterRemoving:Connect(onCharacterRemoved)

-- Check for an existing character when the script starts
if player.Character then
	onCharacterAdded(player.Character)
end

Credit or attribution not required. Feel free to use this in any project. And please share any improvements below

28 Likes

I just tested this out and I must say. It is very helpful!!!

I’m pretty new to Roblox dev so this helped me a ton! :slight_smile:
thank you br_cks for this script!

4 Likes

Appreciate it, and glad you found it useful. Also if you are currently using it, I’ve updated the thread with a newer version. Enjoy :slight_smile:

3 Likes

Have received a few dms regarding this, so figured I should post it

If you don’t want the FOV to change when the player jumps, you can implement the change below

In place of this line within the updateFOV function:

local speed = humanoid.RootPart.Velocity.magnitude

You would use this:

local speed = math.sqrt(humanoid.RootPart.Velocity.X^2 + humanoid.RootPart.Velocity.Z^2)

This change makes it so the information from the Y direction (up/down) is disregarded from the speed calculation

Won’t use this but it seems like a good module, ill refer this to some of my friends.

2 Likes

Any method to use it on a vehicle instead? Module seems excellent

This already works with vehicles.

If you want it to only work with vehicles then you would just connect/disconnect the loop when the player enters a vehicle

This script is super useful! Currently using for my upcoming FPS game.

1 Like

Updated the script & added comments to better explain what is going on. Enjoy :slight_smile: & let me know if you have any questions

Edit May 6, 2024
Updated script in original post again. Realized I was not properly disconnecting when the player died, but was connecting each time they spawned.

Thank you so much! Im gonna use this in so many of my games.

2 Likes