How to make a throw arc in roblox

  1. What do you want to achieve? Keep it simple and clear!
    I need to make a throw arc as such:

  2. What is the issue? Include screenshots / videos if possible!
    Im not sure how to do it besides raycasting but other then that i dont have much other direction

  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    if tried a couple dev fourum posts but they never seemed to get me what i wanted, i have a gravity of 200 (In a vector force) and i have a “Power” variable and a “Direction” Variable… Ima spend some time learning raycasting so thanks for any help!

Forgot to say this, i need it to follow the path of

tmateRoot.AssemblyLinearVelocity = (Direction + Vector3.new(0,1,0)).unit * Power

Also for now the rays are 1 block apart and when it hits the ground itl put a point there

Here is a great tutorial for this:

gif3

The TL;DR is you use physics equations to predict where the ball will go in the future. You can also do collisions for this using raycasting along the path determined by the physics.

1 Like

i had an idea that might work, what if i just throw an invisable clone of the ball thats being thrown and put a point every 1 stud?

That would work though it would be less efficient and you couldn’t calculate the full path very quickly (i.e. you’d need to wait for the physics updates).

good point (Characterlimmetttttt)

Using the code you provided, a simpler equation looks something like this:

local function predict(position, initialVelocity, dt)
    local gravity = workspace.Gravity
    local futurePosition = position + initialVelocity * dt - Vector3.new(0, 0.5, 0) * gravity * dt * dt
    return futurePosition
end

-- Example usage:
local velocity = (Direction + Vector3.new(0,1,0)).unit * Power
local position = ball.Position
for i = 1, 10 do
    -- Predict where the ball will be in i seconds based on the initial velocity and position
    local delta_time = i
    local position = predict(position, velocity, delta_time)
    -- Here you can place markers at `position` to show where the ball will be at 1 sec, 2 sec, etc
end

Edit: @ElipsGames I wrote something incorrect that I fixed in an edit. Please make sure to use the code that looks like - Vector3.new(0, 0.5, 0) * gravity * dt * dt not - Vector3.new(0, -1, 0) * gravity * dt * dt

1 Like

Sick thanks i was kinda pulling my hair out trina understand egomooses code, ill implement it

1 Like

How would i make this update every 1 stud with the beam so it calculates x stud away depending on how much power there is so there will be say power is 24 its 24 points

You would probably need to do raycasting for that.

For example, you could have a for loop like the one above but have a very small dt (to be more accurate) like 1/30, then raycast between the last prediction and the current prediction until there is a hit. The hit is then where the ball is predicted to land. (Then you can do the distance between the ball.Position and the raycast hit to calculate the distance the ball would go.)

How does your current system work? ide prolly have to go with raycasting then

You’d do something like this:

local function predict(position, initialVelocity, dt)
    local gravity = workspace.Gravity
    local futurePosition = position + initialVelocity * dt - Vector3.new(0, -1, 0) * gravity * dt * dt
    return futurePosition
end

local function predictLanding(position, initialVelocity, maxFlightTime, stepTime)
    local dt = stepTime
    local lastPosition = position
    while dt < maxFlightTime do
        local position = predict(position, initialVelocity, dt)
        -- TODO: Raycast from lastPosition to position to get `hit`
        if hit then
            -- Found a landing position
            return hit.Position
        end
        lastPosition = position
        dt += stepTime
    end
    return nil -- No hit
end

local velocity = (Direction + Vector3.new(0,1,0)).unit * Power
local position = ball.Position
local landing = predictLanding(position, velocity, 30, 1/10)
if landing then
    local distance = (landing - position).Magnitude
    print(distance)
end

Where does the “hit” Come in? Also the ball = tmateRoot

local RunService = game:GetService("RunService")

local DEFAULT_TEXTURE = "rbxgameasset://Images/guide"
local DEFAULT_TRANSPARENCY = NumberSequence.new {
	NumberSequenceKeypoint.new(0, 1),
	NumberSequenceKeypoint.new(0.01, 1),
	NumberSequenceKeypoint.new(0.15, 0.35),
	NumberSequenceKeypoint.new(0.4, 0.885),
	NumberSequenceKeypoint.new(0.75, 0.95),
	NumberSequenceKeypoint.new(1, 1)
}
local ROT_OFFSET = {
	[0] = CFrame.Angles(0, math.rad(90), 0),
	[1] = CFrame.Angles(0, math.rad(-90), 0)
}

local function solveParabola(x, a, b, c)
	return (a * x * x) + (b * x) + c
end

local function solveParabolaDeriv(x, a, b, c)
	return (2 * a * x) + b
end

local TrajectoryBeam = {}
TrajectoryBeam.__index = TrajectoryBeam

function TrajectoryBeam.new(originAttachment)
	local self = setmetatable({}, TrajectoryBeam)
	
	self.launchVelocity = 250
	
	self.enabled = false
	self.connections = {}
	
	self.originAttachment = originAttachment
	self.part = self.originAttachment.Parent
		
	self.att0 = Instance.new("Attachment")
	self.att0.Name = "Attachment0"
	self.att0.Parent = self.part
	
	self.att1 = Instance.new("Attachment")
	self.att1.Name = "Attachment1"
	self.att1.Parent = self.part
	
	self.beam = Instance.new("Beam")
	self.beam.Enabled = false
	self.beam.Texture = DEFAULT_TEXTURE
	self.beam.LightEmission = 1
	self.beam.Transparency = DEFAULT_TRANSPARENCY
	self.beam.TextureLength = 10
	self.beam.TextureSpeed = 0.25
	self.beam.Segments = 50
	self.beam.CurveSize0 = 0
	self.beam.CurveSize1 = 0
	self.beam.Width0 = 0
	self.beam.Width1 = 0
	self.beam.Attachment0 = self.att0
	self.beam.Attachment1 = self.att1
	self.beam.Parent = self.part
	
	return self
end

function TrajectoryBeam:setLaunchVelocity(launchVelocity)
	self.launchVelocity = launchVelocity
end

function TrajectoryBeam:setEnabled(enabled)
	if self.enabled == enabled then return end
	self.enabled = enabled
	
	self.beam.Enabled = enabled
		
	if self.enabled then
		table.insert(self.connections, RunService.RenderStepped:Connect(function(dt) self:_onRenderStep(dt) end))
	else
		for i, v in pairs(self.connections) do
			v:Disconnect()
		end
		self.connections = {}
	end
end

function TrajectoryBeam:_onRenderStep(dt)
	local x0 = 0
	local x1 = x0
	local a, b, c = 1, 0, 0
	
	local startVelocity = self.launchVelocity
	local startAngle = math.asin(self.originAttachment.WorldCFrame.lookVector.Y)
	
	local gravity = workspace.Gravity
	a = -gravity / (2 * startVelocity * startVelocity * (math.cos(startAngle) * math.cos(startAngle)))
	b = math.tan(startAngle)
	c = 0 
	
	x1 = math.clamp((startVelocity * startVelocity * math.sin(2 * startAngle)) / gravity, 0, 150)
	
	self:_setBeamParabola(x0, x1, a, b, c)
end

function TrajectoryBeam:_setControlPoint(idx, pos)
	local part = self.part
	local attachment = self["att" .. idx]
	local attachmentPos = attachment.WorldPosition
	local vecFromAttachment = pos - attachmentPos
	local curveSize = vecFromAttachment.Magnitude
	
	attachment.CFrame = part.CFrame:toObjectSpace(CFrame.new(attachmentPos, pos) * ROT_OFFSET[idx] * CFrame.Angles(math.rad(90), 0, 0))
	self.beam["CurveSize" .. idx] = curveSize
end

function TrajectoryBeam:_setBeamParabola(x0, x1, a, b, c)
	local startPos = self.originAttachment.WorldPosition
	local startCF = CFrame.new(startPos, (startPos + self.originAttachment.WorldCFrame.lookVector * Vector3.new(1, 0, 1)))
	local partCF = self.part.CFrame
	
	local midpointX = (x0 + x1) / 2
	local domainSize = (x1 - x0)
	
	local p0 = startCF:pointToWorldSpace(Vector3.new(0, solveParabola(x0, a, b, c), -x0))
	local p1 = startCF:pointToWorldSpace(Vector3.new(0, solveParabola(x1, a, b, c), -x1))
	
	local c = startCF:pointToWorldSpace(Vector3.new(0, solveParabola(x0, a, b, c) + (solveParabolaDeriv(x0, a, b, c) * (domainSize / 2)), -midpointX))
	local c0 = (2/3) * c + (1/3) * p0
	local c1 = (2/3) * c + (1/3) * p1
	
	self.att0.Position = partCF:pointToObjectSpace(p0)
	self.att1.Position = partCF:pointToObjectSpace(p1)
	self:_setControlPoint(0, c0)
	self:_setControlPoint(1, c1)
		
	self.beam.Width0 = math.clamp(domainSize / 10, 0.1, 5)
	self.beam.Width1 = self.beam.Width0
end

return TrajectoryBeam

Something I wrote a long time ago. Use it like so:

local TrajectoryBeam = require(game.ReplicatedStorage.TrajectoryBeam)

local beam = TrajectoryBeam.new(character.RightHand.RightGripAttachment)
beam:setLaunchVelocity(velocity)
beam:setEnabled(true)

I used it for the cannon trajectories in Bootleg Buccaneers. Works pretty well for that. You’ll need to replace the image if you want that.

1 Like

Hit is the raycast result’s instance. Where it says TODO you’d add a raycast and if the raycast found a result (ie the return value from the raycast function is non-nil), set a variable named hit to the result.Instance.

I’d maybe use a beam and make it connect to wherever the mouse is hitting? and then modify the curve based on the distance or the velocity used to throw it to that specific position

Im kinda busy rn but i got it working how i wanted it to work so ill show off my code in a bit but ty for all the help

1 Like

This is part of a MUCH larger script (Like 600 lines)

if Power then
				local StartPos = plrRoot.Position
				local Direction = plrRoot.CFrame.LookVector -- Fix these when i get extra info tmr @ElipsGames
				local ball = tmateRoot -- Your ball part
				local initialPosition = ball.Position

				local mass = ball:GetMass()

				local initialVelocity = (Direction + Vector3.new(0, 1, 0)).unit * Power
				local acceleration = Vector3.new(0, -200 / mass, 0)

				local predictionSteps = 3
				local timeStep = 0.1

				for _, v in pairs(workspace:GetChildren()) do
					if v:IsA("Part") and v.Name == "PredictionDot" then
						v:Destroy()
					end
				end

				for i = 0, predictionSteps do
					local t = i * timeStep
					local futurePosition = initialPosition
						+ initialVelocity * t
						+ 0.5 * acceleration * t^2

					local dot = Instance.new("Part")
					dot.Position = futurePosition
					dot.Size = Vector3.new(0.3, 0.3, 0.3)
					dot.Anchored = true
					dot.CanCollide = false
					dot.BrickColor = BrickColor.new("Bright yellow")
					dot.Material = Enum.Material.Neon
					dot.Name = "PredictionDot"
					dot.Parent = workspace
				end

			end