I’ve been trying for days to get realistic bounce behavior for my game but all I get is this:
and what i want to get to is this behavior:
Any suggestions or anything I might be doing wrong? My code currently looks like this:
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CameraQuery = ReplicatedStorage:WaitForChild("CameraQuery")
local ballSpeed = 0
local maxBallSpeed = 150
local currentTarget = nil
local deflectDirection = nil
local lerpAlpha = 0
local bouncePoints = nil
local bounceT = 0
local function bezierCurve(p0, p1, p2, t)
local mt = 1 - t
return mt * mt * p0 + 2 * mt * t * p1 + t * t * p2
end
local function generateBouncePoints(startPos, normal, targetPos)
local bounceForce = Vector3.new(
math.random(-25, 25),
math.random(10, 30),
math.random(-25, 25)
)
local rotatedNormal = normal * CFrame.Angles(
math.rad(math.random(-45, 45)),
math.rad(math.random(-45, 45)),
math.rad(math.random(-45, 45))
).LookVector
local midPoint = startPos + rotatedNormal * math.random(25, 40) + bounceForce
local randomTargetOffset = Vector3.new(
math.random(-10, 10),
math.random(-10, 10),
math.random(-10, 10)
)
return {
startPos,
midPoint,
targetPos + randomTargetOffset
}
end
local function getNextTarget(currentCharacter)
if not currentCharacter then
return workspace:FindFirstChild("NPC")
end
if currentCharacter.Name == "NPC" then
local player = Players:GetPlayers()[1]
return player and player.Character
else
return workspace:FindFirstChild("NPC")
end
end
local function findPlayerWithPlaying()
if not currentTarget or not currentTarget.Parent then
currentTarget = workspace:FindFirstChild("NPC")
end
return currentTarget
end
local function lerpVector(start, target, alpha)
return start + (target - start) * alpha
end
local function handleCollision(ball, hit)
if hit:IsA("BasePart") and hit.Name ~= "HumanoidRootPart" then
local raycastResult = workspace:Raycast(ball.Position, hit.Position - ball.Position)
if not raycastResult then return end
local targetCharacter = findPlayerWithPlaying()
if not targetCharacter then return end
local targetHRP = targetCharacter:FindFirstChild("HumanoidRootPart")
if not targetHRP then return end
bouncePoints = generateBouncePoints(ball.Position, raycastResult.Normal, targetHRP.Position)
bounceT = 0
end
end
local function checkDeflections()
if not currentTarget or not currentTarget.Parent then return end
local ball = workspace:FindFirstChild("Ball")
if not ball then return end
local humanoidRootPart = currentTarget:FindFirstChild("HumanoidRootPart")
if not humanoidRootPart then return end
local distance = (humanoidRootPart.Position - ball.Position).Magnitude
if distance <= 10 and not deflectDirection then
local deflect = currentTarget:FindFirstChild("Deflect")
if deflect and deflect.Value then
local nextTarget = getNextTarget(currentTarget)
if not nextTarget then return end
if currentTarget.Name == "NPC" then
local nextHRP = nextTarget:FindFirstChild("HumanoidRootPart")
if not nextHRP then return end
deflectDirection = (nextHRP.Position - ball.Position).Unit
else
local player = Players:GetPlayers()[1]
if not player then return end
local cameraCFrame = CameraQuery:InvokeClient(player)
if not cameraCFrame then return end
deflectDirection = cameraCFrame.LookVector
end
lerpAlpha = 0
currentTarget = nextTarget
end
end
end
local function updateBall()
local ball = workspace:FindFirstChild("Ball")
if not ball then return end
if not ball:GetAttribute("CollisionSetup") then
ball:SetAttribute("CollisionSetup", true)
ball.Touched:Connect(function(hit)
handleCollision(ball, hit)
end)
end
if bouncePoints then
bounceT = bounceT + 0.05
if bounceT >= 1 then
bouncePoints = nil
bounceT = 0
else
local newPosition = bezierCurve(
bouncePoints[1],
bouncePoints[2],
bouncePoints[3],
bounceT
)
ball.AssemblyLinearVelocity = (newPosition - ball.Position) * 8
return
end
end
local targetCharacter = findPlayerWithPlaying()
if not targetCharacter then return end
local humanoidRootPart = targetCharacter:FindFirstChild("HumanoidRootPart")
if not humanoidRootPart then return end
checkDeflections()
local targetPosition = humanoidRootPart.Position
local ballPosition = ball.Position
local finalDirection
if deflectDirection then
local toTarget = (targetPosition - ballPosition).Unit
local lerpedDirection = lerpVector(deflectDirection, toTarget, lerpAlpha)
finalDirection = lerpedDirection.Unit
lerpAlpha = math.min(1, lerpAlpha + 0.01)
if lerpAlpha >= 1 then
deflectDirection = nil
lerpAlpha = 0
end
else
finalDirection = (targetPosition - ballPosition).Unit
end
if ballSpeed < maxBallSpeed then
ballSpeed = ballSpeed + 0.2
end
ball.AssemblyLinearVelocity = finalDirection * ballSpeed
end
RunService.Heartbeat:Connect(updateBall)