Hi there, Im trying to make a tennis project for fun and im trying to figure out how can i control how fast and high a tennis ball goes in my game.
This is how it works in the game:
The player has manual control over where the ball goes, via the use of the target, the normal behiavior is that after he swings the racket and it hits the ball, the ball will fly or launch over the net and hit the ground on that target, then bounce. Similarly in a real tennis match.
So no matter what, when the ball touches the ground, it hits that target. No matter how high or fast it goes.
Here’s an example:
So I cooked up this code:
local RS = game:GetService("ReplicatedStorage")
local BMLEvent = RS.BallMachineLaunch
local SendBallEvent = RS.MoveBall
local CurrentBall = RS.ImportantAssets.TennisBall
local CBValue = RS.CurrentBallValue.Value
local upwardSpeed = 45 -- This will now represent the peak height of the bounce
local forwardSpeed = 2 -- This acts as a timescale; how fast the ball completes its bounce (tweaked for time)
local bounceFactor = 0.95 * 1.25
local TrajectoryInturrupted = false
local CurrentClone = nil
-- Function to calculate the time it takes for the ball to reach the target
local function calculateFlightTime(targetPosition, startPosition)
local horizontalDistance = (Vector3.new(targetPosition.X, 0, targetPosition.Z) - Vector3.new(startPosition.X, 0, startPosition.Z)).Magnitude
return horizontalDistance / forwardSpeed -- Time to reach the target controlled by forwardSpeed
end
-- Function to calculate the initial velocities (vx, vz, vy)
local function calculateInitialVelocity(startPosition, targetPosition, peakHeight, gravity, time)
-- Get the horizontal direction to the target
local direction = (targetPosition - startPosition).Unit
-- Calculate the horizontal velocity components (X and Z)
local vx = direction.X * (targetPosition - startPosition).Magnitude / time
local vz = direction.Z * (targetPosition - startPosition).Magnitude / time
-- Calculate the required vertical velocity (vy) based on the peak height
local vy = (2 * peakHeight) / time
return Vector3.new(vx, vy, vz)
end
local function SimulateBall(Part: BasePart, bball: BasePart, targetPosition: Vector3)
local rs = game:GetService("RunService").Heartbeat
local g = Vector3.new(0, -game.Workspace.Gravity, 0) -- gravity vector
local x0 = Part.Position -- initial position of the ball
-- Calculate the time to reach the target based on forwardSpeed
local timeToTarget = calculateFlightTime(targetPosition, x0)
-- Calculate the initial velocity (horizontal and vertical components)
local v0 = calculateInitialVelocity(x0, targetPosition, upwardSpeed, g, timeToTarget)
local nt = 0 -- time counter
-- Clone the current ball and set its properties
local c = CurrentBall:Clone()
c.Parent = workspace
c.CanCollide = false
c.Anchored = true
c.Transparency = 0
CurrentClone = c
TrajectoryInturrupted = false
CBValue = CurrentClone
local BounceSFX = c:WaitForChild("BounceSFX")
-- Main loop for ball physics simulation
while true do
if not TrajectoryInturrupted then
-- Calculate the new position based on time, velocity, and gravity
-- The vertical position is updated by the parabolic motion formula
local newPos = 0.5 * g * nt * nt + v0 * nt + x0
c.CFrame = CFrame.new(newPos)
-- Check if the ball has hit the ground (bounce)
if newPos.Y <= 0.5 then
-- Bounce by reversing and reducing the vertical speed
v0 = Vector3.new(v0.X, -v0.Y * bounceFactor, v0.Z)
nt = 0 -- reset time counter for the bounce
x0 = Vector3.new(newPos.X, 0.5, newPos.Z)
BounceSFX:Play()
end
nt = nt + rs:Wait()
-- Break loop if the bounce height becomes too small (end simulation)
if math.abs(v0.Y) < 0.1 then
break
end
else
c:Destroy()
end
end
end
-- Function to interrupt and reset the ball simulation
local function InterruptAndReset(Part, targetPosition)
TrajectoryInturrupted = true
if CurrentClone then
CurrentClone:Destroy()
CurrentClone = nil
end
-- Restart the simulation with the new target position
SimulateBall(Part, CurrentBall, targetPosition)
end
BMLEvent.Event:Connect(function(Part, targetPosition)
if CurrentBall ~= nil then
InterruptAndReset(Part, targetPosition)
end
end)
SendBallEvent.Event:Connect(function(hrp, targetPosition)
if CurrentBall ~= nil then
InterruptAndReset(hrp, targetPosition)
end
end)
In this code, the Ball gets cloned, the projectile path is calculated and teh ball follows it, when it gets hit or after some time, the clone will get destroyed and a new ball is created with a new projectile path.
ForwardSpeed Dictates how fast the ball launches and bounces (think of it like Tween Timescale).
UpwardSpeed dictates the height (or originally, the maximum height) the ball attains during its bounce.
My issue with this is that, Is that if i tweak the forward speed or the upward speed a little bit, it overshoots the target position and misses it completely, or it will drastically slow down and (undershoot?) hit the ground too early.
Pls help.