Inconsistent Camera Tweening for continous motion

I have a cutscene where the camera continuesly tweens between positions and it’s meant to look like continous motion but it clearly slows down and stops before starting the next move, any advice?

Supporting Video:


As you can see, you can see and feel when it starts tweening between different invisible objects on the path:

Some of this is obviously down to fine tuning the speed so it increases/slows down at a good pace, that said you can clearly see the camera slowing down to a stand still before tweening again.

What have i tried to fix it so far:

  • Tuning TweenInfo speed.
  • Swapping out Animation.Completed:Wait() for wait(tonumber(array[1])-0.5)
  • Checking calculation overhead for each tween.
  • Changing wait time to change to next tween earlier.

My system uses a module for camera and :Play() from a require to call it. All the cutscene camera animation information is included in here:
image

Enclosed is my code aside the Camera shake code. I can also provide the testing file if need be
--| Cutscene Core
--| By: jeditcdisback
--| March 2th, 2020
--| Handles Camera
local Workspace = game:GetService("Workspace")
local TweenService = game:GetService("TweenService")
local CurrentCamera = Workspace.CurrentCamera
local FolderCutscenes = Workspace:WaitForChild("Cutscenes")
local Camera = {}
local t = 0
local Stop = false
local Permitted = false
local threshold = 5

local ToBooleans = 
{
    ["true"] = true;
    ["false"] = false
}

function PerlinNoise(Strength)
	t = t + Strength
	local x = math.noise(t)
	return x
end


function Camera.new(Pref)
	local self = setmetatable({}, Camera)
	self.Cutscene = Pref.SpecifiedCutscene or nil
	self.RequestType = Pref.Type
	self.CameraType = CurrentCamera.CameraType
	Completed = false
	local Cutscene = FolderCutscenes:FindFirstChild(self.Cutscene) or nil
	
	function self.Shake(Type,ReqCam,Strength,Speed,StartTime,EndTime)
	end
	
	--| Changes Camera position and Type
	--| General Use: Cutscenes, Single Shots.
	function self.Play()
		CurrentCamera.CameraType = Enum.CameraType.Scriptable
		for _,v in pairs(Cutscene:GetChildren()) do
			
			--| Setup Start
			local array = string.split(v.TweenInfo.Value)
			local TI = TweenInfo.new(tonumber(array[1]), Enum.EasingStyle[array[2]], Enum.EasingDirection[array[3]],tonumber(array[4]),ToBooleans[array[5]], tonumber(array[6]))
			local To = {CFrame = v.CFrame}
			Animation = TweenService:Create(CurrentCamera,TI,To)
			--| Setup End
			
			--| Shake while Tween
			if v:FindFirstChild("CameraShake") then
				local CameraShakeInfo = string.split(v.CameraShake.Value) 
				wait(0.5)
				Stop = false
				Permitted = false
				self.Shake("Camera",v,tonumber(CameraShakeInfo[1]),tonumber(CameraShakeInfo[2]),tonumber(CameraShakeInfo[3]),tonumber(CameraShakeInfo[4]))
				wait(CameraShakeInfo[3])
				Permitted = true
				wait(CameraShakeInfo[4])
				Permitted = true
			--|No Shake
			else
				print(v.Name," Started")
				Animation:Play()
				wait(tonumber(array[1])-0.5)
				print(v.Name," Ended")
			end
		end
		Completed = true
		CurrentCamera.CameraType = self.CameraType
	end

	--| Hides Parts within the specified cutscene folder 
	function self.HideCam()
		print(Cutscene)
		for _,v in pairs(Cutscene:GetChildren()) do
			v.Transparency = 1
			for _, instance in pairs(v:GetDescendants()) do
    			if instance:IsA("Texture") then
    				instance:Destroy()
    			end
			end
		end
	end

	--| Takes requests and calls the module type
	if self.RequestType then
		print("[Camera Module]: CALLED "..self.RequestType)
		self[self.RequestType]()
	end
	return Completed
end
return Camera

I appreciate all advice given and this may just me being dumb, I admit, I am not great at TweenInfo as there is a lack of detail as to what each option does. TweenInfo value object only includes the following:
image
The highlighted is the only value which changes.

Small update, some were getting confused by my wording of the issue so EriChan1235 said it alot better:

Instead of using Quad and Out, try using Sine and InOut, everything else is fine.

1 Like

Thank you for your reply, that’s a definite improvement but it’s still noticable, especially as the speed change is now quite jolting. below is a recording of the new system:


Would you mind explaining the different options as the API references are quite lacking in detail and behaviour of TweenInfo.

Heres an Easing Style documentation.

I don’t know if this will work, but try using Circular easing style instead.

I will do and thank you for the link; I’ll try that and linear. Just waiting on @erichan1235’s reply.
He’s been typing for…what…20mins now? xD.

The only issue I could see is the fact that you have inconsistent distances between your positions, which means if the value is not adjusted based on the distance, it’d always take 1 second to get to point a to b.

Let’s say you have position A, position B, and position C.

Position A is 1,
Position B is 5,
Position C is 7.

Now we tween the object from position A to position B.
This takes 1 second, and it’s the speed we want.
Position A to position B, however, is a much longer distance than position B to position C.

Now we tween the object from position B to position C, with the same 1 second length.
The difference here is the fact that now the object is moving much slower to position C, as the distance is much less than position A to position B.
You’re telling the tween to take 1 second to go to position C from position B, which would take 1 second.

What would be the fix for this?
To incorporate the time speed distance formula.

To add it to tweens would go something like this:

Let’s define all the variables firstly, however, what we want to focus on is the Speed variable.

Speed is how quickly the object will move, and it’s important for the Time Speed Distance Formula.
Let’s say it’s 20 studs per second.

local TweenService = game:GetService("TweenService")
local CurrentCamera = workspace.CurrentCamera

local Object = workspace.CutscenePart
local Speed = 20

now where we create the Tween, we start by calculating how long it takes from getting to point A to point B using the Speed variable we defined earlier. This is gonna be the time parameter of TweenInfo.new

local CurrentPosition = CurrentCamera.CFrame.Position
local GoalPosition = Object.Position
local Distance = (GoalPosition - CurrentPosition).Magnitude
local Time =  Distance / Speed

Now let’s create the actual tween which is pretty simple actually.
The EasingStyle’s and EasingDirection’s are pretty irrelevant for this example, so I will just use Enum.EasingStyle.Linear for the EasingStyle. I will use Enum.EasingDirection.InOut for the EasingDirection.
If you want to learn more about EasingStyles and EasingDirections, click this.

local Tween_Info = TweenInfo.new(Time, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut)
local Tween_Goal = {CFrame = Object.CFrame}
local Tween = TweenService:Create(CurrentCamera, Tween_Info, Tween_Goal)

Once that’s done, we should have a script that looks something like this:

local TweenService = game:GetService("TweenService")
local CurrentCamera = workspace.CurrentCamera

local Object = workspace.CutscenePart
local Speed = 20

local CurrentPosition = CurrentCamera.CFrame.Position
local GoalPosition = Object.Position
local Distance = (GoalPosition - CurrentPosition).Magnitude
local Time =  Distance / Speed

local Tween_Info = TweenInfo.new(Time, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut)
local Tween_Goal = {CFrame = Object.CFrame}
local Tween = TweenService:Create(CurrentCamera, Tween_Info, Tween_Goal)

This script isn’t completely finished, however it has all the necessary stuff.
Now the only thing there is left to do is to play the tween itself, listen to when it’s completed and then clean things up.

Once those are in place, the script should look something like this

wait(2)

local TweenService = game:GetService("TweenService")
local CurrentCamera = workspace.CurrentCamera

local Object = workspace.CutscenePart
local Speed = 20

local CurrentPosition = CurrentCamera.CFrame.Position
local GoalPosition = Object.Position
local Distance = (GoalPosition - CurrentPosition).Magnitude
local Time =  Distance / Speed

local Tween_Info = TweenInfo.new(Time, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut)
local Tween_Goal = {CFrame = Object.CFrame}
local Tween = TweenService:Create(CurrentCamera, Tween_Info, Tween_Goal)

Tween:Play()

Tween.Completed:Wait()
Tween:Destroy()

This script should take into account the distance between positions in order to make the part always travel the same speed to a position.
wait(2) is there to let the player load in for a few seconds, so that they can see the Cutscene happening.

Incorporate the above example into your script, and your problem should be fixed.

2 Likes

Thank you for your detailed reply, i’ll look over it and get back to you. Merci.

1 Like

I don’t like linear for any camera animation(except if you’re just going in one direction) because switching directions would have this, “edgy” feeling. Like a squarish feeling instead of a circular one.

1 Like

Original poster stated he wants a continuous motion, which means he doesn’t want it to slow down or go faster.

Apologies for any confusion. The speed does increase over time. The issue I’m having is with it buffering/slowing down around the different object tweens:


What I mean by continous motion is a consistent look and feel. even if it is actually speeding up. At the moment it’s jolty between the different objects. I hope that makes sense?

As I understand it, in your post the object stays at a constant speed no matter the distance. This should fix the issue? (I haven’t had the time to check it yet). But it removes that pick up in speed you see later in the clip.

1 Like

Ooh, so you want a smoother camera path, as it’s noticeable when the direction changes?

1 Like

Exactly, I apologies for any confusion on my end; You spent a great deal of time on that first reply. Would it be helpful if I link the place file to the testing area?

I don’t mean to be a pain but did you have any kind of follow up as im kind of just:

Over here. :slight_smile:

It’s not necessary to link to the place. I’m just slightly busy with another topic.
I have no experience regarding the topic you’ll probably have to dive into, which means I can’t really help you without doing a lot of research.

You’d probably wanna research something called Bezier curves and then try to implement that to your script.
Here’s a plugin for Bezier curves,
Here’s a Wikipedia article about Bezier curves.

Hmm, I’ll look into it but feel like that’ll lead me to a dead end in all honesty. Not sure how i’d implement that well. Especially as my camera system is built to work for multiple situations, an all in one solution for Cutscenes, Camerashake etc in just over 100 lines. Thank you for all your help, Don’t let me disturb you
but if anyone else has any suggestions. I’d love to hear them.

This is how far i’ve got so far: (a big thank you to @EriChan1235 and @VegetationBush)

Although this post was originally made 3 months ago, this is an issue I am also having and would love to find a solution.

1 Like

I believe you should look into bezier curves, as I’ve previously mentioned in this topic.
Here’s a developer hub article about them: https://developer.roblox.com/en-us/articles/Bezier-curves

Why they are useful here is because you want to smooth out the camera path when it comes to the corners, so it doesn’t instantly just snap and change direction. With bezier curves, you are able to generate a path that would be a bit smoother in the corners.

1 Like

Can verify, this solved the problem for me!

1 Like

There’s a module for making cutscenes with bézier curves now!

Made by @Vaschex, here’s the link: https://devforum.roblox.com/t/bezier-curve-cutscenes/718571

3 Likes