Throwing Spear Mechanism

Im trying to make a spear throw, and i dont know the best way to do this, i’ve tried using bezier curves and this has worked the best so far, but its a bit strange and i’ve never really used bezier curves. i’ve searched for some answers and nothing really sticks out all too much, i dont know if body velocities would work for my case, because i want the spear to end up where your mouse.

this is what i have so far but the spear timing to reach its end position is a bit innacurate and looks strange, also this is just for testing and doesnt have a spear model, i followed a tutorial by someone for the bezier curves. If you have any ideas or different approaches, please let me know!

local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local players = game:GetService("Players")

local Remotes = ReplicatedStorage:WaitForChild("Remotes")
local DamageRemote = Remotes:WaitForChild("Damage")
local SpearThrow = Remotes:WaitForChild("Weapons"):WaitForChild("Spear"):WaitForChild("SpearThrow")

local function lerp(t, a, b)
	return a + (b - a) * t
end

local function quad(t, p0, p1, p2)
	local l1 = lerp(t, p0, p1)
	local l2 = lerp(t, p1, p2)
	local quad = lerp(t, l1, l2)
	return quad
end

SpearThrow.OnServerEvent:Connect(function(player, startPos, endPos)
	local p1 = Instance.new("Part", workspace.ProjectilesHitboxes)
	p1.Size = Vector3.one
	p1.Color = Color3.new(0.219608, 0.466667, 1)
	p1.Position = startPos
	p1.Anchored = true
	p1.CanCollide = false
	p1.CanQuery = false
	p1.CanTouch = false
	
	local MaxDistance = 75
	
	local direction = (endPos - startPos)
	local horizontalDir = Vector3.new(direction.X, 0, direction.Z)
	local distance = horizontalDir.Magnitude
	
	if distance > MaxDistance then
		horizontalDir = horizontalDir.Unit * MaxDistance
	end
	
	local origin = startPos + horizontalDir
	
	local finalEndPos
	
	if direction.Magnitude < MaxDistance then
		finalEndPos = endPos
	else
		local Raycast = workspace:Raycast(startPos + horizontalDir, Vector3.new(0, -100, 0))
		if Raycast then
			finalEndPos = Raycast.Position
		end
	end
	
	local p2 = Instance.new("Part", workspace.ProjectilesHitboxes)
	p2.Size = Vector3.one
	p2.Color = Color3.new(1, 0.294118, 0.305882)
	p2.Position = finalEndPos
	p2.Anchored = true
	p2.CanCollide = false
	p2.CanQuery = false
	p2.CanTouch = false
	
	local p1p2MidPoint = 0.5 * (startPos + finalEndPos)
	local distance = (startPos - finalEndPos).Magnitude
	local arcHeight = distance / 4
	local p3FinalPosition = p1p2MidPoint + Vector3.new(0, arcHeight, 0)

	local p3 = Instance.new("Part", workspace.ProjectilesHitboxes)
	p3.Size = Vector3.one
	p3.Color = Color3.new(0.411765, 1, 0.25098)
	p3.Position = p3FinalPosition
	p3.Anchored = true
	p3.CanCollide = false
	p3.CanQuery = false
	p3.CanTouch = false

	local p4 = Instance.new("Part", workspace.ProjectilesHitboxes)
	p4.Size = Vector3.one
	p4.Color = Color3.new(1, 1, 1)
	p4.Position = p1.Position
	p4.Anchored = true
	p4.CanCollide = false
	p4.CanQuery = false
	p4.CanTouch = false
	
	local MaxDuration = 1
	
	local distance = (endPos - startPos).Magnitude
	local duration = math.clamp((distance / MaxDistance) * MaxDuration, 0, MaxDuration)
	
	local elapsed = 0
	
	local connection
	connection = RunService.Heartbeat:Connect(function(dt)
		elapsed += dt
		local t = elapsed / duration

		local position = quad(t, p1.Position, p3.Position, p2.Position)
		p4.Position = position

		if t >= 1 then
			connection:Disconnect()
		end
	end)
end)

3 Likes

For throwing a spear, shouldn’t you just use a parabola instead of a bezier curve to feel more natural?

u can use parabolas for roblox? how would i tween the spear tho? could you maybe send a video or a forum post on this?

Maybe this could help if you need an arcing projectile?

thats pretty good, but its mostly physics based, i dont know if that would work for a spear, because i want it to like rotate as well to make it stick to the ground


these images is an example of what i would want it to look like, if you need more explenation lmk!

I added a piece of code that keeps the projectile in the direction of the throw. Just add it in the heartbeat, after position is defined.

		local direction = position - startPos
		if direction.Magnitude > 0.001 then
			p4.CFrame = CFrame.lookAt(position, position + direction)
		else
			p4.Position = position
		end
		startPos = position

Also,

What did you mean by this?

Here, I made this test experience to show you how to make this using only Roblox physics and no curve following (the spear is a physics object the entire time):

Spear.rbxl (62.7 KB)

My code generates the perfect yeet velocity vector required to land the spear on the target using a parabolic arc: CalculateHeading(start, end, arcHeight)

If you don’t like how the spear isn’t substepped rotationally, you’d have tween between their CFrames on the client side, replicating them manually.


-- function that calculates the initial velocity vector required to reach the target using a parabola (with a given arc height)

-- I just set the spears PrimaryPart.AssemblyLinearVelocity to the returned value
local function CalculateHeading(start: Vector3, target: Vector3, arcHeight: number): Vector3
	local gravity = workspace.Gravity

	local displacementXZ = (target - start) * Vector3.new(1,0,1)

	local peakY = math.max(start.Y, target.Y) + arcHeight

	-- Time to rise to peak, and fall to target
	local invGrav = 2 / gravity
	local tUp = math.sqrt((peakY - start.Y) * invGrav)
	local tDown = math.sqrt((peakY - target.Y) * invGrav)
	local totalTime = tUp + tDown

	-- Horizontal velocity
	local vxz = displacementXZ / totalTime

	-- Initial vertical velocity
	local vy = gravity * tUp

	return vxz + Vector3.yAxis * vy
end
1 Like