Weird bounce effect when tweening part along a curved path

  1. I want to make a smooth movment when the part is tweening along a path

  2. Here is a video of the issue.

  3. I have tried to look on the devfourm for a sulution but didnt see anything. I once tried using an EasingStyle but that didnt do much, only made the tween worse.

Any help is appreciated!


Can you share how you’re tweening it along the path?



local function CreateTween(Part:Part,End:Part)
	local distance = (Part.CFrame.Position - End.CFrame.Position).Magnitude
	local Time = distance / speed
	local tweenInfo =,Enum.EasingStyle.Linear,Enum.EasingDirection.InOut)
	local tween = ts:Create(Part,tweenInfo,{CFrame = End.CFrame})
	return tween

Im am basicly using this for every part in the curve using tween.Complet.Wait() like this;

			for i = 1,count do
				local tween = CreateTween(sling,part:FindFirstChild(tostring(i)))
                            --sling var is the moving part, part var is the folder with the path

I see, so you’re essentially tweening the dragon between the dragon itself’s position and the centre of each part? This snapping you’re seeing is because when tweening, the interpolations are linear, so really your path is being broken up into multiple straight segments. You could try splitting your path up into smaller segments to give the illusion of a smoother curve.

If you want a proper curve, it might be worth looking into creating a quadratic bezier curve to interpolate the dragon along, where the control point would be roughly the intersection between the two parts, ie:


Or you could create a spline with the entire trajectory.

Let me know whether splitting your trajectory up into smaller segments gives you a satisfactory result, if not I’ll write up a more in depth post for how to move it along a curve


I really want to use splines to make it so the curve is more customizable, but I do not know how to use splines.

I’m too busy to give you code to fix it, but I can tell you how to fix it.

This is happening because the tween is ending, then starting again. Tweens take a few seconds to register since every game and program runs off CPU ticks. You can fix it by fixing the timing.

task.wait(TweenTime - .03)

Adding this changed nothing, honestly made the problem worse.

Hey I didn’t forget about this, I apologize for the super late response, I got busy the last few days and was feeling under the weather so wasn’t able to write anything up at that time.

Anyway, depending on what your requirements are, there are multiple different ways to approach this.

Roblox has a few instances relating to curves, which is notably used for the curve animation editor, however you can use it to create a spline that actually passes through the control points. I give a very basic example here, however suphi has a very in depth tutorial on how to use them here

If you don’t want to use float curves, there are many resources here on the forum that can give you what you’re looking for, for example here,

Another one is to create a bezier spline with each point, however the path will not pass through each of the vertices (ie the spline will be contained within the convex hull from which the vertices are composed), and because it uses factorialization, it can get expensive with many vertices:

local function binomialCoefficient(n: number, k: number) -- what the binomial coefficient is used for in the context of bezier curves is to determine the influence of each vertex to the final curve
	-- "binomial coefficient = n! / (k! (n-k)!)"
	-- ! denotes factorialization, which is the product of all positive integers up to that number, inclusively

	local result = 1
	for i = 1, k do
		result *= n - (k - i) -- 1 * 2 * 3 ...  k * (n - k + 1) * ... n
		result /= i -- divide by 1, then 2, then 3 and so on up to `k` (k!)

	return result

local function bernsteinPolynomial(n: number, k: number, t: number) -- this is the polynomial that determines the influence of each vertex to the final curve at a given `t`
	-- n = vertex count
	-- k = index of current vertex
	-- t = 0 to 1, the position on the curve
	-- B_k,n (u) = C(n, k) * u ^ k * (1 - u) ^ (n - k)
	-- from the article, "C(n, k) is a binomial coefficient"
	return binomialCoefficient(n, k) * t ^ k * (1 - t) ^ (n - k)

local function bezierSpline(vertices: { Vector3 }, t: number)
	local vertexCount = #vertices - 1

	local pointOnCurve = 

	for i, vertex in vertices do -- sum up all vertices influence on the final curve at `t`
		pointOnCurve += vertex * bernsteinPolynomial(vertexCount, i - 1, t)

	return pointOnCurve

Derived from:

To use it you would just hook up whatever event you’d like to use (ie heartbeat or use a loop) and add it to the elapsed time, then divide it by the total time to obtain a number between 0 and 1 which can be passed to the bezierSpline function.

local splineVertices = workspace:WaitForChild('SplineVertices')
local splineVisuals = workspace:WaitForChild('SplineVisuals')

local vertices: { Vector3 } = {}

local function updateCurve()

	for i = 1, 100 do -- roughly the path the object will follow
		-- `i / 25` is the position on the curve, from 0 to 1
		local pointOnCurve = bezierSpline(vertices, i / 100)

		local newPart ='Part')
		newPart.Anchored = true
		newPart.CanCollide = false
		newPart.Material = Enum.Material.SmoothPlastic
		newPart.Size =, 0.2, 0.2)
		newPart.Color =, 1, 1)
		newPart.Parent = splineVisuals

		local prevToThis = bezierSpline(vertices, (i + 1) / 100) - pointOnCurve -- dir = goal - origin
		local tangent = prevToThis.Unit
		local magnitude = prevToThis.Magnitude

		newPart.CFrame = CFrame.lookAt(pointOnCurve, pointOnCurve + tangent, Vector3.yAxis)
		newPart.Size =, 0.2, magnitude)

		newPart.Parent = splineVisuals

local curvePart ='Part')
curvePart.Anchored = true
curvePart.CanCollide = false
curvePart.Material = Enum.Material.SmoothPlastic
curvePart.Size =, 0.5, 0.5)
curvePart.Color =
curvePart.Parent = workspace

	local elapsed = 0
	local traverseTime = 10

	while true do
		local pointOnCurve = bezierSpline(vertices, elapsed / traverseTime)
		local tangent = (bezierSpline(vertices, (elapsed + 0.1) / traverseTime) - pointOnCurve).Unit
		-- `+ 0.1` is to get a point slightly ahead of the current one so we can get the orientation

		curvePart.CFrame = CFrame.lookAt(pointOnCurve, pointOnCurve + tangent, Vector3.yAxis)

		elapsed += task.wait()

		if elapsed > traverseTime then
			elapsed = 0

for _, vertex in splineVertices:GetChildren() do
	vertices[tonumber(vertex.Name) :: number] = vertex.Position
		vertices[tonumber(vertex.Name) :: number] = vertex.Position


splineTester.rbxl (52.3 KB)

There is a more refined version of this available here which allows you to move an object at a consistent speed regardless of vertex distance/density and is probably more optimized than mine so I think that it may also be worth checking out.