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

Hey everyone! So basically, in my local script, I have a menu, and when a person clicks play, I want the camera to move to the persons head, so then they can start playing, I thought it would be a cool and smooth way to enter a game, I’m just not too sure of how to do that. Here is my script… (If you want, you can type inside of the script of where the tween would go and what not)

local player = game.Players.LocalPlayer
local PlayerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local character = player.Character or player.CharacterAdded:Wait()
local Camera = workspace.CurrentCamera
character:WaitForChild("HumanoidRootPart").Anchored = true
local head = character:WaitForChild("Head")

local TweenService = game:GetService("TweenService")

repeat wait()
	   Camera.CameraType = Enum.CameraType.Scriptable
	until Camera.CameraType == Enum.CameraType.Scriptable 
Camera.CFrame = workspace.Camerapart.CFrame

local info = TweenInfo.new(20,Enum.EasingStyle.Sine,Enum.EasingDirection.Out)
local rotating = true

local MainMenu = PlayerGui.MainMenu

local PlayButton = MainMenu.TextButton

local newinfo = TweenInfo.new(0.5,Enum.EasingStyle.Quad,Enum.EasingDirection.Out,0,false,0)

PlayButton.MouseButton1Click:Connect(function()
	print("PlayButton Clicked! Now the Tween will play!")
	local goal = {};
	goal.Position = UDim2.new(-0.2, 0, 0.71, 0)
	TweenService:Create(PlayButton, newinfo, goal):Play()
	
	wait(3)
	print("Player Spawned!")
	character.HumanoidRootPart.Anchored = false
	character.HumanoidRootPart.CFrame = workspace.SpawnPart.CFrame

	Camera.CameraType = Enum.CameraType.Custom
    
	MainMenu:Destroy()
end)

while rotating do
	local cameraTween = TweenService:Create(Camera, info, {CFrame = Camera.CFrame * CFrame.fromEulerAnglesXYZ(0, 360, 0);})
	cameraTween:Play()
	local blur = Instance.new("BlurEffect")
	blur.Parent = workspace.CurrentCamera
	blur.Enabled = true
	blur.Size = 10
	wait(0.02)
end

I have tried to do it myself but I always get an error saying that Position is not a member of ‘Camera’ Which is true.

2 Likes

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.

4 Likes

Yes, I did write all of that code, for me, the way I learn, is when someone writes code for me, and adds comments, I understand more, understanding is apart of devfourm right? So I’m sorry if you don’t like the way I learn, you also got a little confused of what I was asking, I wanted the camera to go from the set camera that it was at, to the head. The part on the while loop I did was so I didn’t have this weird slow stop, I’m using this for a menu, because if I didn’t add that wait, it would stop and then play again, I wanted it not to do that so I came up with just adding a short wait.

This is disappointing, you really didn’t read my entire post.

My code can do exactly this if you just change some values.

2 Likes

Oh, my apologies, it’s 1 am where I live, i don’t read things perfectly, again my apologies. I also said that because of the example you gave me, so again I’m very sorry.

I used your code and it works great, but I ran into a problem
If you tween the camera from a moving part to a stationary part, sometimes there’s still a “jump” after you set the camera subject to the new part: GIF | Gfycat

For comparison, if the part you’re tweening from isn’t moving, the tween works fine and there’s no jump: GIF | Gfycat

Did you come across this problem? I’m having a hard time trying to figure out what’s wrong
note: the jump isn’t as noticeable with an FPS unlocker, but I recorded the clips above with it turned off

I see the problem here, but it’s quite subtle. The issue is that the very last DeltaTime, calculated before setting the CameraSubject, is actually greater than the TweenTime, meaning the camera goes past the new CameraSubject on the last frame, before the loop terminates.

Ordinarily I would suggest that the solution here would be to calculate the DeltaTime and then immediately break the loop if it’s greater than the TweenTime, basically just doing the same check as is in the loop condition itself while DeltaTime <= TweenTime do. Having that would make the check redundant since we would be doing it twice (1st in the loop condition, 2nd after the DeltaTime calculation). Because of this, it would actually make more sense here to just use a while true do loop and then just evaluating the loop’s continuance inside the loop just after the frame-wait and DeltaTime calculation.

HOWEVER, when I tried using this solution, there was an even worse problem where, between the frame-wait and setting the CameraSubject after breaking the loop, the camera would briefly flicker back to the initial position. This leads me to believe there’s perhaps an additional frame-wait when setting the CameraSubject. The easiest solution here was then to just stick to the original code while clamping the DeltaTime to max out at 1, the max percentage, as in the middle line here:

local Percent = DeltaTime / TweenTime -- Get a number from 0 to 1 representing a percentage of how close we are to finishing the animation
	
Percent = math.clamp(Percent, 0, 1) -- Ensure the camera never gets put before or beyond the goal position
	
local DeltaCFrame = InitialCFrame:Lerp(GoalPositionInitialOrientation, Percent) -- The CFrame the camera will snap to during this frame

This improves the situation slightly, but if I’m right to say that setting the CameraSubject induces an additional frame-wait time, then there’s nothing that can be done about that final extra frame considering we can’t predict the motion of the part, especially if it’s a player’s head as one example. Honestly I just wouldn’t worry about it.

1 Like