How to make an object fly from point A to point B in an arch shape like a projectile

I have a toaster, and I want it to shoot a loaf of bread onto a random but scripted area. The problem is, I want the bread to fly in an arch shape, something like when a ball is thrown into the sky and falls back down. I’ve searched up and found similar questions, but I never understood any of the solutions and how to modify them to suit my needs because my small goldfish brain is unable to understand the complicated math behind it. can someone help pls :slight_smile:

look into trigonometry and sine waves. That’s the main way developers make these curves.

I’ve tried looking into them, but I have no idea what they mean or how I’m supposed to use them. sorry my math is bad ;-;

Trigonometry is widely used to calculate curves, its simple, with a combination of a sine wave and Linear Interpolation could be possible, firstly a sine wave is the most basic one out of all of them, the main types are:

  • Sine
  • Cosine
  • Tangent

They all have different curves, and have a hyperbolic variation as well, but that’s not necessary for the animation.

Heres an example:

local Tracer = script.Parent
local EndPosition = workspace.EndPoint.Position

local function CalculateArcPosition(startPos, endPos, t)
	-- Create an arc by modifying the Y coordinate using a sine wave
	local midPoint = (startPos + endPos) / 2
	local height = math.sin(t * math.pi) * (endPos - startPos).Magnitude * 0.5

	return Vector3.new(
		startPos.X + (endPos.X - startPos.X) * t,
		midPoint.Y + height,
		startPos.Z + (endPos.Z - startPos.Z) * t
	)
end

local function UpdateTracerPath()
	local startPos = Tracer.Position
	local progress = 0

	while progress < 1 do
		progress += 0.025
		local newPos = CalculateArcPosition(startPos, EndPosition, progress)
		Tracer.Position = Tracer.Position:Lerp(newPos, progress)
		task.wait(0.01)
	end
end

UpdateTracerPath()

Here we use a simple sine wave to calculate the height of the arch. the t is the hypoteneuse. Then just wrap it into a loop with a progress variable, and voila, you’ve got yourself an arch animation.

Its important to understand the code, look into it and find out more about math and trigonometry, it will help you in the long run.

How does this get the height? I don’t understand what’s happening in this line.

The math.sin creates a sine wave with the given hypotenuse, aka t, basically t is the frame of the animation, then multiply it by math.pi, which makes it more arched and smooth, and multiply that by the distance between the 2 parts, so the highest point of the sine will be in the middle part of the animation, and multiply all of that by 0.5 to make it less exaggerated.

Tweak with the settings and you’ll understand what they do.

looks like i’m gonna have to dive into sine waves even more :confused:

After running the code, I realised that the part moves at a constant speed. How would I tweak the code such that the part slows down when nearing the max height and start speeding up again once it starts to fall?

something like this:

local Tracer = script.Parent
local EndPosition = workspace.EndPoint.Position

local function CalculateArcPosition(startPos, endPos, t)
	local midPoint = (startPos + endPos) / 2
	local height = math.sin(t * math.pi) * (endPos - startPos).Magnitude * 0.5
	return Vector3.new(
		startPos.X + (endPos.X - startPos.X) * t,
		midPoint.Y + height,
		startPos.Z + (endPos.Z - startPos.Z) * t
	)
end

local function UpdateTracerPath()
	local startPos = Tracer.Position
	local progress = 0
	while progress < 1 do
		progress += 0.02

		local waitTime = math.abs(math.sin(progress * math.pi)) * 0.015

		local newPos = CalculateArcPosition(startPos, EndPosition, progress)
		Tracer.Position = newPos
		task.wait(waitTime)
	end
end

task.wait(2)
UpdateTracerPath()

What if I wanted to alter the progress instead of the wait time? I heard that using wait() is generally not good, especially if the wait time is short and frequently repeated. I want to use Run Service’s heartbeat’s delta time to determine the progress, alongside the position of the object whether it is nearing or going away from the max height. How can I do so? (sorry if i sound very demanding or am demanding too much, im not very good at scripting and math :confused: )

wait() by itself is not reliable, at least at todays standard, the thing is wait is really important in any program, but using wait() as it is(globally) is unreliable and unpredictable, that’s why there’s task.wait() which should be used instead as its more predictable.

I also tested on how to make a local version, I succeeded but at sometimes overshoots a bit at higher speeds.

a local version is really simple to implement, heres an example:

local Tracer = workspace.Tracer
local EndPosition = workspace.EndPoint.Position

local RunService = game:GetService("RunService")

local function CalculateArcPosition(startPos, endPos, t)
	local midPoint = (startPos + endPos) / 2
	local height = math.sin(t * math.pi) * (endPos - startPos).Magnitude * 0.5
	return Vector3.new(
		startPos.X + (endPos.X - startPos.X) * t,
		midPoint.Y + height,
		startPos.Z + (endPos.Z - startPos.Z) * t
	)
end

local function UpdateTracerPath()
	local startPos = Tracer.Position
	local progress = 0
	local connection
	connection = RunService.Heartbeat:Connect(function(delta)
	
		progress += delta

		local newPos = CalculateArcPosition(startPos, EndPosition, progress)
		Tracer.Position = newPos
		
		if progress >= 1 then
			connection:Disconnect()
			return
		end
	end)
end

task.wait(2)
UpdateTracerPath()

You can multiply the delta and the progress together to achieve a slower result.

Something like this:

progress += delta * 0.05

Its fine, don’t worry about it, keep learning and you’ll progress with time.

the max height is half of the progress, so if linear interpolation goes from 0 to 1, then the max height is 0.5 of the progress, use it as you wish.

So the closer the progress is to 0.5, the smaller the number I multply the deltatime by? Also if the progress being at 0.5 is the max height does that mean the start and end point have to be along the same Y vector?

Edit: To slow down the progress when the progress is closer to 0.5, I tried multiplying the deltatime by math.abs(0.5 - progress). However, once the progress reaches 0.5, math.abs(0.5 - progress) would be 0 and the progress would not increase because multiplying deltatime by 0 is still 0 and adding 0 to the progress does nothing. I solved this by changing math.abs(0.5 - progress) to 0.1 if it is less than 0.1, but that feels a bit forceful and not smooth. Is there a better way to do this?