Hi guys, I’m creating a soccer game inspired by Super League Soccer. I love that game and would like to recreate the ball control and kicking mechanics!
I have two questions:
-
The first is about the best way to control the ball when the player has it so that it has the correct rotation, based on the direction the player is moving. I’m doing this with a 6D motor inside the ball, but I’m not sure if it’s the right way.
-
And the second is about the kicking, I want to have predictability about the ball’s trajectory (on the client). I even tried to create a Bézier curve that shows the trajectory before the kick, but when I try to move it around the server with a heartbeat it behaves very strangely
I’m not looking for ready-made scripts, I really want to understand how these games create such fluid experiences within Roblox. I want to learn the best practices for this type of real-time update for all players.
Another test I did was using Body Velocity, but that way I can’t predict what the ball’s trajectory will be. Here is a video of the current state of gameplay using the Bézier curve to move the ball.
This code creates the trajectory using Beam and fires the event to the server with: initial, control and final positions.
function onShoot(actionName, inputState)
if actionName ~= InputActionsEnum.OFFENSIVE.SHOOT then return end
if inputState == Enum.UserInputState.Begin then
holdDuration = 0
height = 0
createBeam()
heartbeatConnection = RunService.Heartbeat:Connect(function(delta)
holdDuration = holdDuration + delta
controlPoint = (ballHolder.Position + shotAim.Position) / 2
if holdDuration >= MAX_HOLD_DURATION.Value then return end
local direction = humanoidRootPart.CFrame.LookVector
shotAim.Position = shotAim.Position + direction * delta * SHOOT_POWER.Value
controlPoint = (ballHolder.Position + shotAim.Position) / 2
height = math.clamp((controlPoint - ballHolder.Position).Magnitude, 0, 10)
if not beam then return end
beam.CurveSize0 = height
beam.CurveSize1 = -height
end)
elseif inputState == Enum.UserInputState.End then
if not heartbeatConnection then return end
heartbeatConnection:Disconnect()
doKick(true)
end
end
On the server, I process this event and move the ball
function onKicked(
playerWhoKicked,
startPosition,
controlPosition,
endPosition,
percentage,
isKickToGoal
)
if ball == nil then return end
player = nil
humanoidRootPart = nil
if isKickToGoal then
MatchStatsServiceInstance:addShootToPlayer(playerWhoKicked)
end
local points = createCurve(startPosition, controlPosition, endPosition)
ballDisabled = true
ball.Motor.Part1 = nil
ball:SetNetworkOwner(nil)
local bodyPosition = Instance.new("BodyPosition")
bodyPosition.Position = startPosition
bodyPosition.MaxForce = Vector3.new(math.huge, math.huge, math.huge)
bodyPosition.P = 50000
bodyPosition.Parent = ball
local currentStep = 0
local heartbeatConnection
heartbeatConnection = RunService.Heartbeat:Connect(function(delta)
if currentStep < #points and ball then
local nextPoint = points[currentStep + 1]
local direction = (nextPoint - ball.Position).Unit
local distanceToNextPoint = (nextPoint - ball.Position).Magnitude
local movement = direction * 500 * delta
bodyPosition.Position = ball.Position + movement
if distanceToNextPoint > 0 then
local lookAtPosition = ball.Position + direction
ball.CFrame = CFrame.lookAt(ball.Position, lookAtPosition)
end
if distanceToNextPoint < 1 then
currentStep = currentStep + 1
end
else
heartbeatConnection:Disconnect()
bodyPosition:Destroy()
ballDisabled = false
end
end)
local args = {
eventType = BallEventTypesEnum.LOST
}
BallEvent:FireClient(playerWhoKicked, args)
end