How to make a Dolly Zoom effect?

So I’ve been trying to make a Dolly Zoom effect and I can’t seem to get it right.
I’ve tried messing around with the camera’s FieldOfView and the character’s HumanoidOffset but it just barely gives the effect I want.
Code:

function getPlayerCharacter()
	return(game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait())
end
local Character=getPlayerCharacter()
game:GetService("RunService"):BindToRenderStep("GetChar",1,function()
	Character=getPlayerCharacter()
end)
game:GetService("TweenService"):Create(workspace.CurrentCamera,TweenInfo.new(2,Enum.EasingStyle.Quad,Enum.EasingDirection.Out,math.huge,true,2),{FieldOfView=100}):Play()--}):Play()
game:GetService("TweenService"):Create(Character:WaitForChild("Humanoid"),TweenInfo.new(2,Enum.EasingStyle.Quad,Enum.EasingDirection.Out,math.huge,true,2),{CameraOffset=Character:WaitForChild("Humanoid").CameraOffset-(Character:WaitForChild("Head").CFrame.LookVector*15)}):Play()

The effect it gives:

Any help would be appreciated.

4 Likes

Almost forgot. If it’s pointed behind the head of the character, the camera is very far away from the head and the dolly zoom effect doesn’t work as good.

1 Like

You can’t use tweens since the relationship between the FOV and distance isn’t linear/quadratic/etc. You’ll need to calculate the distance of the camera for any given FOV. You also have to set the CameraType to Scriptable (which also means that you can’t use Humanoid.CameraOffset!) since the default camera scripts will mess with the effect that you’re trying to achieve.


According to the wikipedia article on Dolly Zoom, to calculate the distance of the camera…

The independent variables here are the width of the scene (which would be fixed; because FieldOfView property in roblox is vertical, not horizontal, this is actually the height of your scene) and the FOV (which will be tweened to achieve the zooming effect) and the dependent variable is distance from the subject.

Here’s the formula written in Lua:

local function calcDistForDollyZoom(width, fov)
	return width / (2 * math.tan(0.5 * math.rad(fov)))
end

Here’s the result. The focus is the Dummy’s head. The camera is moved along the head’s lookVector. The width is fixed at 20 and the FOV is linearly interpolated from 120 to 20 (and back).

As for your original script, this is what you should do instead. You’ll probably need to move some stuff around (e.g. re-setting the camera type to custom after the effect is done) for this to work with your game.:

function getPlayerCharacter()
	return(game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait())
end

local function calcDistForDollyZoom(width, fov)
	return width / (2 * math.tan(0.5 * math.rad(fov)))
end

-- Width of the scene. In Roblox, this is actually the height of the thing you're focusing on.
local SCENE_WIDTH = 20
local CurrentCamera = workspace.CurrentCamera
-- Has to be Scriptable because default camera scripts will refuse to cooperate with you.
CurrentCamera.CameraType = Enum.CameraType.Scriptable
local startFOV = 120 -- start FOV
local goalFOV = 20 -- end FOV
local timeToZoom = 2 -- 2 seconds to complete zoom
local start = tick() -- the tween starts immediately
-- RenderStep is unnecessary for the effect. Heartbeat is perfectly fine.
local hConn
hConn = game:GetService("RunService").Heartbeat:Connect(function()
	local Character=getPlayerCharacter()
	local Head = Character:FindFirstChild("Head")
	-- How far along the dolly zoom are we?
	local per = math.clamp((tick() - start)/timeToZoom, 0, 1)
	if Head then
		-- Get the FOV of the camera.
		local targetFOV = startFOV + (goalFOV-startFOV) * per
		-- Get the distance of the camera
		local dist = calcDistForDollyZoom(SCENE_WIDTH, targetFOV)
		-- Adjust the camera given the stuff we calculated above.
		CurrentCamera.FieldOfView = targetFOV
		CurrentCamera.CFrame = CFrame.new(
			Head.Position + Head.CFrame.lookVector  * dist,
			Head.Position
		)
	end
	-- If the dolly zoom is over, then disconnect from heartbeat.
	if per == 1 then
		hConn:Disconnect()
	end
end)
37 Likes

Thank you! This works fine. (although the RenderStep was for getting the character)

1 Like