How to get a camera to move to a heads CFrame with TweenService

I’ve done this exact type of camera tweening before. Tweens only really work seamlessly when you know what the exact value of the object in question will be at the end of the tween.

If you tween the camera to someone’s head, and the tween lasts 5 seconds, you do not know where that person’s head will be from the beginning of the tween to after those 5 seconds pass. What will happen is your camera will move to where the head was at the beginning of the tween. This isn’t what we want. In addition, if you have the script change the CameraSubject just after this, then right as the tween ends you will see the camera teleport to the head’s actual location. Not smooth.

In this gif, you can see my spectate tool working in Crossroads to actually smoothly move the camera from the previous head to the next head without any jerky teleportation: https://gyazo.com/b6523b0fc1621476ffa86e369c61db22
It’s a bit hard to see since the travel time is so short but I assure you there’s no problem.

The trick here is to not use TweenService at all. This uses a while loop that yields at each RenderStep, figuring out what the new value will be at every frame by using time(). It gets the latest Head.Position at each frame as well. Another hurdle when you’re gradually moving the camera to a new position, then changing the CameraSubject just afterward, is that we still want to preserve the zoom distance between the last part and the camera so we don’t just end up inside of the new part then have the camera jerk to the correct place.

Here is some code that will do all of this perfectly. Copy and paste this into a LocalScript and read some of comments I’ve sprinkled in:

local TweenTime = 3 -- How long the camera will be moving for
local GoalPart = workspace.Part -- The part the camera will move toward. It's important to use a part here rather than a position because we need to get the part's position at every frame in case it changes.
local InitialCFrame = workspace.CurrentCamera.CFrame

-- Do not change the below variables.
local DeltaTime = 0 -- The time at each frame between when the animation starts and ends.
local InitialTime = time() -- The time the animation started.
local RenderStepped = game:GetService("RunService").RenderStepped

local Success, CameraSubjectPosition = pcall(function() -- Here we are trying to get the current position of the CameraSubject part so we can use it to preserve the distance between the camera and the current CameraSubject.
	return workspace.CurrentCamera.CameraSubject.Position -- If CameraSubject is a part
end)

if not Success then
	Success, CameraSubjectPosition = pcall(function() -- Here we are trying again a different way if the first one didn't work, reusing the last attempt's variables.
		return workspace.CurrentCamera.CameraSubject.Parent.Head.Position -- If CameraSubject is a humanoid in a typical character
		-- Roblox really needs a Humanoid.CameraPart or some such property
	end)
end

if not Success then
	return -- Give up if we couldn't find the actual CameraSubject part.
end

local ZoomVector = CameraSubjectPosition - workspace.CurrentCamera.CFrame.Position -- This is the distance between the camera and initial part (most likely your head).
local InitialOrientation = InitialCFrame - InitialCFrame.Position -- We record the initial camera CFrame's orientation so the camera rotation doesn't change during the animation.

while DeltaTime <= TweenTime do -- The loop continues until the TweenTime has finished
	RenderStepped:Wait() -- Yield for the next frame.
	DeltaTime = time() - InitialTime -- Refresh the time differential ("between-time")
	local GoalPositionInitialOrientation = InitialOrientation + (GoalPart.Position - ZoomVector)
	-- In the above line, we construct a CFrame where an imaginary camera is looking at the goal part from the same distance and with the same rotation as our actual camera was looking at the previous CameraSubject.
	local Percent = DeltaTime / TweenTime -- Get a number from 0 to 1 representing a percentage of how close we are to finishing the animation
	local DeltaCFrame = InitialCFrame:Lerp(GoalPositionInitialOrientation, Percent) -- The CFrame the camera will snap to during this frame
	workspace.CurrentCamera.CFrame = DeltaCFrame -- Actually move the camera here.
end

workspace.CurrentCamera.CameraSubject = GoalPart

Here is a gif of the above code running where the goal part workspace.Part is moving while the camera moves toward it. When the while loop finishes, I zoom out and move the camera around.
https://gyazo.com/2666fd39b8caa5ebf701c7c464da3f3e


This confuses me, because there is no reference to a Camera.Position property in the script. There is, however, a reference to Camera.CFrame through the tween’s PropertyTable on line 42. Are you sure you wrote this script or did someone else?


Here I’ve (roughly) quoted an excerpt from your script that’s confusing me. The CameraTween is supposed to play, which takes 20 seconds to complete. However, after only 1/50th of a second it plays a copy of the same tween again, without having yielded for the last tween to finish. I’m not sure what you’re trying to do with this tween in the first place since it doesn’t actually go towards someone’s head (it seems that it’s supposed to just spin the camera around), but I can tell this wouldn’t work either way. Could you elaborate on what this is supposed to do? Re-reading your post, it’s clear you didn’t know where to start for your tween and just asked the DevForum to write it for you. While I’m generally against people asking for the forum to do their homework, I’ve already written a lengthy post here that gives you the answer. Try implementing it yourself, and post any successes or failures you have with it here.

5 Likes