How to change camera offset using tween service effectively

Hello! I have a local script in starter character scripts that allows players to toggle between first person and third person with a permanent shiflock system by pressing V. When switching between first and third person, the humanoid’s camera offset needs to be changed in order to follow how actual shiflock works (The permanent shiftlock system only locks the player’s mouse in the center of the screen and changes the humanoid’s camera offset). The script also has a sliding system where when the player holds Shift, they can do a slide that also requires changing the camera offset while sliding. I use TweenService to make the camera movement look smooth, but that’s where the problem arises. Since there are 2 tweens that affect the humanoid’s camera offset, if one is played while the other is also playing, the first one will get interrupted and the player’s camera offset will end up wrongly. How would I go about fixing this?

Add a bool value that interrupts the player for making 2 tweens at the same time

What do you mean by interrupts the player for making 2 tweens at the same time

local bool = false
local bool1 = false

local tween1 = -- your tween
local tween2 = --your second tween

--Action
if bool == false  then
bool = true
bool1 = true
tween1:Play()
tween1.Completed:Connect(Function()
bool = false
bool1 = false
End)

--Second action
if bool1 == false then
bool1 = true
bool = true
tween2:Play()
tween2.Completed:Connect(Function()
bool = false
bool1 = false

The tweens are not being played one at a time though, it is dependent on when the character enters/exits first person and when the character slides

I have something Very similiar for my game expect i let the player Swap between overhead and shoulder camera. How i solved it was by using Lerps.

Here is the stripped code i used for it. I will explain it roughly at the end.

local OffsetGoal = Vector3.new(0,3,0)
local oldOffsetGoal = OffsetGoal

RunService:BindToRenderStep("ShiftLock", Enum.RenderPriority.Camera.Value-1, function(DeltaTime)
	--Lerp camera Offset
	local newOffsetGoal = oldOffsetGoal:Lerp(OffsetGoal,DeltaTime * 8)
	oldOffsetGoal = newOffsetGoal
	
	-- Apply the new Offset
	Humanoid.CameraOffset = newOffsetGoal
end) 

You can change OffsetGoal to any vector and it will automatically lerp the camera to the given offset, change the “8” to any number to change the speed of the lerping.

Interpolation overall is a very powerful tool and WAY better than Tweening.
Tweens are still useful but i prefer lerps for most stuff.

1 Like

Sorry for the late reply, but firstly, why would I need an offset goal, an old offset goal and a new offset goal if they all represent the same thing? (or at least in my eyes they seem to be representing the same thing in the script correct me if im wrong) Also what does lerp do I have never used it before

To answer all your questions:
First you need to understand how interpolation works, basically what it does is it takes 2 numbers and then finds a number that is X amount in between of them for example a lerp from 2 to 4 with 0.5 alpha would be 3. Because we are trying to find the number that is half the distance of 2 from 4. Now, it gets interesting if we try to lerp a very tiny amount like 0.01. What happens is we move a tiny bit towards the Goalnumber in our case being 4 so now the first value is something like 2.03. If you keep lerping in a loop in our case inside a RunService you will keep slowly moving towards the 4 so it will be like 2.03, 2.08, 2.12 and so on.

The Goal variable is our 4 from the example or the value you want to move towards, the oldOffset is more an arbitrary variable i just called it that way because of the context of the script, but normally its our 2 or the number we are lerping, each frame you need to overwrite this old goal to the result of the lerp or the newOffsetGoal.
Its more of a temporary thing just to store the value because we cant reference a variable to itself.

Now to explain what happens inside the script, Inside A loop a variable gets defined, the value of the variable is the Result of the LERP from oldOffset(lets say 2) to OffsetGoal(lets say 4) with the step distance being the DeltaTime or 1 frame.
Then we overwrite the oldOffset(2) as the result of The lerp(2.01)
In the next iteration same thing happens except oldOffset is now 2.01 and the result is the lerp of that with our goal.

Something like this is a very bad approach, its good for prototyping but for a longer term project is very bad as such code is hard to maintain and work with. Tweens should only be used for Things as Non responsive animation or VFX stuff. Bascially things where goal doesnt change suddenly. A tween would be good for something like door opening and closing if they are on a cooldown. Something like that doesnt disrupt players experience as its expected for a door to function like that in a game already.
For a thing such as camera swap, its very bad to put cooldowns or interruptions as it makes for worse less smooth experience. Thats where LERPS should be used. They feel way smoother and the goal doesnt have to be constant. Interpolation is the core ingredient of any procedural animation system.

BTW on a side note its better to store variables like that in an array, so instead of declaring bool1 and bool2 do Bools = {true,false}

For tweens its best to create and play them instantly because the garbage collector will see them as undeclared and remove them from memory when unneeded TweenService:Create():Play()

Thanks for explaining, btw lerping works! It gets rid of the interrupting problem, and the transitition is very smooth. It also adds a neat slow down at the end of the transition making it look more smooth, something I wanted to accomplish with tween service but couldnt.

How would I sync the camera zooming in and out with the lerp? When toggling between first and third person, I change the maximum and minimum zoom distance of the camera using tweens, as lerps cannot change numbers apparently.

1 Like

Oh right i forgot to mention, the LERP function only works for Vectors as its a built in method, for Lerping numbers you need to use a separate function here is the one i use.

function Lerp(a: number, b: number, t: number): number
	return a + (b - a) * t
end

a is the starting number, b is the goal number and t is the distance.
I recommend putting it into a module script and just requiring it whenever you need it.
it would look something like this.

--Inside Module Script
function module.Lerp(a: number, b: number, t: number): number
	return a + (b - a) * t
end
--Inside Server or Local Script.
local ReplicatedStorage = game:getService("ReplicatedStorage")
local MyModule = require(ReplicatedStorage.ModuleScript)

--To use the function just do this
local LerpResult = MyModule.Lerp(a,b,t)
--Lerp Result is the return value of the function.
1 Like