How to make a ball come to a smooth stop?

Hello developers!

I’m developing a system that allow users to shoot a (golf)ball.I currently create a module script with a function in it to shoot a ball. The problem is that the ball will be shot in a nice curve (like in the real world) but then it won’t come to a real stop. I need to change the velocity, but that is not smooth enough. Also the time the ball will stop needs to be randomly picked between 0.5 - 1.5 seconds (after the ball stops bouncing).
The question: how can I improve the following script to match the criteria I mentioned above?

The script:

function swingModule.Shoot(ball, plr, character, strength, strengthMultiplier)
	-- Calculate the used strength
	local usedStrength = (swingModule.DefaultStrength + strength) * strengthMultiplier
	
	-- Get the start and to positions for the ball
	local startPosition = ball.Position
	local toPosition, ok = GetPositionFromCharacterToPoint(character, usedStrength)
	if not ok then
		return false
	end
	
	-- Calculate the force to shoot the ball with
	local direction = toPosition - startPosition
	local duration = math.log(1.001 + direction.Magnitude * 0.01)
	local force = direction / duration + Vector3.new(0, game.Workspace.Gravity * duration * 0.5, 0)
	
	-- Get the player's plot part where to count the distance from
	local plot = PlotManager.returnPlot(workspace.Plots, plr)
	local shootToPart = plot.ScriptParts.ShootToPart
	
	-- Shoot the ball
	SendBallStartPositionEvent:FireClient(plr, shootToPart.Position)
	PlaySoundEvent:FireClient(plr, "Hit")
	ball:ApplyImpulse(force * ball.AssemblyMass)
	ball:SetNetworkOwner(nil)
	
	-- Stop the ball
	task.wait(duration)
	ball.Velocity = Vector3.new(0, ball.Velocity.Y, 0)
	
	-- Calculate the distance the ball went
	local distance = (ball.Position - shootToPart.Position).Magnitude
	local roundedDistance = math.round(distance)
	
	-- Check if it is the player's personal best score
	local personalBestStore = Datastore2("PersonalBest", plr)
	local newPersonalBest = false
	if personalBestStore:Get(0) < roundedDistance then
		newPersonalBest = true
		personalBestStore:Set(roundedDistance)
	end
	
	-- Calculate the rewards
	local cashReward = math.round(roundedDistance * swingModule.CashPerStud)
	local gemReward = math.round(roundedDistance * swingModule.GemPerStud)
	AddRewardEvent:Fire(plr, cashReward, gemReward, roundedDistance, newPersonalBest)
	
	-- Unanchor the HumanoidRootPart of the player, so it can move again
	local humRP = character:FindFirstChild("HumanoidRootPart")
	if humRP then
		humRP.Anchored = false
	end
	
	-- Destroy the ball
	ball:Destroy()
end

The GetPositionFromCharacterToPoint() function:

local function GetPositionFromCharacterToPoint(character, spawnDistance)
	local humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
	if not humanoidRootPart then
		return Vector3.new(0, 0, 0), false
	end
	
	local forwardVector = humanoidRootPart.CFrame.LookVector
	local spawnPosition = humanoidRootPart.Position + (forwardVector * spawnDistance)
	
	local raycastResult = workspace:Raycast(spawnPosition, Vector3.new(0, -spawnDistance, 0))
	if raycastResult then
		spawnPosition = raycastResult.Position
	end
	
	return spawnPosition, true
end

Thanks in advance!

2 Likes

I’m not very good with physics, but I believe you can use a BodyPosition object and set it’s position to where you want the ball to end up.
Just try to play around with it’s value or use TweenService (to tween the BodyPosition’s parameters) if you have to in order to get desirable results.

3 Likes

Thanks for your response! But BodyPosition is deprecated to use, is there any other option?

1 Like

Use body gryro

local BodyGryo = Instance.new("BodyGryo")
BodyGryo.Parent = -- The ball
BodyGryo.MaxTorque = Vector3.new(25,25,25)
BodyGryo.D = 0.5
2 Likes

AlignPosition is also an option, but there’s no way to have them ignore an axis, which can result in the ball floating on the Y-axis. I personally would use BodyPositions until roblox makes the MaxForce value for AlignPositions a Vector3.

3 Likes

Thanks for your response!
Could you provide me with an example of how I can implement this? Thanks in advance!

1 Like

All you’d have to do is use Instance.new to create a BodyPosition, and then change it’s max force after enough time passes.
Something like this:

local BodyPosition = Instance.new("BodyPosition")
BodyPosition.MaxForce = Vector3.zero -- Prevents the BodyPosition from acting on the ball
BodyPosition.Parent = ball

-- After the time has passed:
BodyPosition.Position = toPosition -- The BodyPosition's goal
BodyPosition.MaxForce = Vector3.new(1000, 0, 1000) -- These values are just an example; you might want to experiment with the properties of the bodyposition to get the best results
1 Like