Bezier curve slowing down depending fps

I’ve been trying to find a fix for this for some time, sadly i got none.

In case of curiosity, this also happens to every projectile.

Low fps example: https://gyazo.com/76525f018fdc4fcd6598370b5a150b83
High fps example: https://gyazo.com/929ad091d4ef18c58206a49a017bc839

the code…

local replicatedStorage = game:GetService('ReplicatedStorage')
local tweenService = game:GetService('TweenService')
local events = replicatedStorage:WaitForChild('Events')
local assets = replicatedStorage:WaitForChild('Assets')
local modules = replicatedStorage:WaitForChild('Modules')

local cratterModule = require(modules.CratterModule)

local function Lerp(a, b, c)
	return a + (b - a) * c
end

local function CalculateQuadBezier(Iteration, StartPosition, MiddlePosition, EndPosition)
	local FirstLerp = Lerp(StartPosition, MiddlePosition, Iteration)
	local SecondLerp = Lerp(MiddlePosition, EndPosition, Iteration)

	local FinalLerp = Lerp(FirstLerp, SecondLerp, Iteration)
	return FinalLerp
end

return function(unit:Model, enemy:Model, originalCFrame:CFrame)
	local primaryPart = unit.PrimaryPart
	local E_primaryPart = enemy.PrimaryPart
	
	local lookAt = tweenService:Create(primaryPart, TweenInfo.new(0.2, Enum.EasingStyle.Back, Enum.EasingDirection.Out), {CFrame = CFrame.lookAt(primaryPart.Position, Vector3.new(E_primaryPart.Position.X, primaryPart.Position.Y, E_primaryPart.Position.Z))})
	lookAt:Play()
	
	local middle = (primaryPart.Position:Lerp(E_primaryPart.Position, 0.5)) + Vector3.new(0, 10, 0)
	
	task.wait(1.5)

	unit.Torso.Trail.Enabled = true
	for t = 0, 1, 0.025 do
		primaryPart.CFrame = CFrame.new(CalculateQuadBezier(t, originalCFrame.Position, middle, E_primaryPart.Position)) * CFrame.Angles(0, math.rad(180), 0) 
		task.wait(0.01/60)
	end

	cratterModule.Create(3, E_primaryPart.Position, 1)
	primaryPart:WaitForChild('SFX').Fall:Play()
	unit.Torso.Trail.Enabled = false

	task.spawn(function()
		enemy.PrimaryPart.RootAttachment.DMGTaken.Enabled = true task.delay(0.2, function() enemy.PrimaryPart.RootAttachment.DMGTaken.Enabled = false end)
	end)
	
	task.wait(0.6)

	local lookAt2 = tweenService:Create(primaryPart, TweenInfo.new(0.2, Enum.EasingStyle.Back, Enum.EasingDirection.Out), {CFrame = CFrame.lookAt(primaryPart.Position, Vector3.new(originalCFrame.Position.X, primaryPart.Position.Y, originalCFrame.Position.Z))})
	lookAt2:Play()

	local animation2 = unit.Humanoid.Animator:LoadAnimation(unit.Return)
	animation2:Play()

	task.wait(1.05)

	unit.Torso.Trail.Enabled = true
	for t = 0, 1, 0.025 do
		primaryPart.CFrame = CFrame.new(CalculateQuadBezier(t, E_primaryPart.Position, middle, originalCFrame.Position))
		task.wait(0.01/60)
	end

	cratterModule.Create(3, originalCFrame.Position, 1)
	primaryPart:WaitForChild('SFX').Fall:Play()
	unit.Torso.Trail.Enabled = false

	task.wait(0.5)

	local lookAt3 = tweenService:Create(primaryPart, TweenInfo.new(0.2, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut), {CFrame = originalCFrame})
	lookAt3:Play()
end
1 Like

the fix is dont use renderstepped

1 Like

i’m not using renderstepped tho…

you should, hartbeat depends on fps not renderstepped

but since you arent using any idk what the problem is, so i think the problem is elsewhere in a script.

Step through the leap using deltaTime to correct for lag/fps difference. You have task.wait(0.01/60) which is a very small number and often isnt exact by a lot.

step through the leap using deltaTime? wdym

This is probably the issue. The task.wait function minimum wait time is dependant on the FPS of the client when it runs locally, with the equation being 1/fps. 0.01/60 is much smaller than the minimum limit for both the 60 fps clients and the ones with unlocked fps(for example 120). This means that if a client has 120 fps instead of 60, the task library will wait for half the time, making the projectile faster.

The only way to ensure same behavior for all clients is to either run task.wait on the server or use a wait time that is higher than the minimum FPS. So if we assume the minimum fps to be 30, your wait time should be 1/30 = 0.0333... so all clients see the projectile moving at the same speed. If this makes the projectile move slowly, you can make it take bigger steps to reach its destination.

so if i understood right, if i use task.wait(1/60) it will wait 0.06 seconds on all clients? if i want to make it so it moves on 0.01 seconds i’ll have to use task.wait(1/100)? wouldn’t it be a problem?

If a client is running on 30 fps it will be ignored and the function will wait for the less available time for that client, which is 1/30, but if they have 60 fps or more, it will wait for 1/60 seconds.

this will wait for 1/60 seconds unless the client has 120 FPS

If you don’t want to slow down the projectile due to this, as mentioned above, you can take more steps per interval. So for example instead of doing move, wait, move, wait you can do move, move, wait, move, move, wait, move, move etc.

Could you provide me an example? sorry if i’m asking too much. I’m trying to learn more about this so i can have more experience when fixing stuff like these on the future…

And on the script i was doing, i was using a for i = loop, so i don’t know how i would do a “move, move, wait, move, move, wait, move, move” thing.

For your case you can simply increase the step of t. For example increasing it from 0.025 to 0.05 should make the projectile move twice as fast for the same task.wait delay. Although you should try to have small task.wait delays and steps, else the animation might appear choppy. A good delay is 1/30 which will make the projectile move 30 times per second.

Since you passed such a small number to wait, the accuracy of the time waited will be tied to framerate. Task.wait() depends on heartbeat which runs every frame. Most devices can’t wait an exact 0.01/60 seconds (very small number, 60 fps is 1/60. 0.01/60 is 100x faster).

DeltaTime is the change in time or elapsed time. So you want to correct the position based on how much time has actually passed.

something like

local maxTime = 1
local totalElapsedTime = 0 -- t
repeat 
    bezier( math.min(totalElapsedTime, maxTime) )
    totalElapsedTime += task.wait() -- task.wait returns elapsed time
until totalElapsedTime >= maxTime
1 Like

this actually fixed my problem! tysm dude!!!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.