How To Script A Ball Movement System

I want to make a player-controlled ball which can move left and right. It will have a continuous forward velocity and all movement will be handled on the server. I also want to compensate for client-to-server latency and make sure that the system is exploit-proof.

Currently, I have a system that works, except the left/right movement is really slow and doesn’t feel smooth and responsive. Here’s a video of how the system is currently:

Current System Video

Here’s a link to the test place in case you want to see for yourself how annoyingly slow it is.

I want to make the system work so that when I press the A/D keys on my keyboard, the ball will move quicker and will accelerate much faster left/right. I also want to make sure that there is little to no lag.

Here is my server and client code. It’s very basic and uses BodyMovers, which are deprecated.

Client Code (StarterPlayerScripts)
local camera = game.Workspace.CurrentCamera
local uis = game:GetService("UserInputService")
local marble = game.Workspace:WaitForChild("Marble")

local movementEvent = game.ReplicatedStorage:WaitForChild("Movement")

uis.InputBegan:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.A then
		movementEvent:FireServer(marble, "Left", true)
	elseif input.KeyCode == Enum.KeyCode.D then
		movementEvent:FireServer(marble, "Right", true)
	end
end)

uis.InputEnded:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.A then
		movementEvent:FireServer(marble, "Left", false)
	elseif input.KeyCode == Enum.KeyCode.D then
		movementEvent:FireServer(marble, "Right", false)
	end
end)
Server Code (ServerScriptService)
local forceVelocity = 500
local directionalVelocity = 1000

local bv = Instance.new("BodyForce")
bv.Name = "ForwardForce"
bv.Force = Vector3.new(forceVelocity,0,0)
bv.Parent = workspace:WaitForChild("Marble")

local movement = game.ReplicatedStorage:WaitForChild("Movement")

movement.OnServerEvent:Connect(function(player, marble, movementType, callType)
	if callType == true then
		-- if movement began
		if movementType == "Right" then
			bv.Force += Vector3.new(0,0,directionalVelocity)
		else
			bv.Force += Vector3.new(0,0,-directionalVelocity)
		end
	else
		-- movement ending
		bv.Force = Vector3.new(forceVelocity,0,0)
	end
end)

All help is greatly appreciated! Thanks in advance! If you need clarification, feel free to ask.

1 Like

You really should have the physics done on the client. Communication with the server can be incredibly slow. If someone has somewhat high latency of 0.25 seconds that means it could take half a second after their input for anything to happen. General rule of thumb is that if a player controls it, they should have network ownership so that controls feel good.

To prevent cheaters just track the balls movement on the server and if it’s moving too fast or through a wall or something just teleport them back to where they used to be and reset their speed.

1 Like

Thank you for your response.

That’s a great solution for the client-to-server latency and exploiting issues. Unfortunately, my main problem still exists, the extremely slow ball movement. I have looked at different BodyMovers and cannot find one that works properly for my intended purpose. I encourage you to take a look at the test place to see for yourself how slow it is.

Personally I set the assemblyLinearVelocity instead of using a body mover when I built a little marble game a while back. The body mover should work though. I unfortunately can’t test (I’m on mobile). Is it problematic to just increase the force?

1 Like

I have tried increasing the force, it becomes way too fast. I can’t find a middle ground. If only there was an acceleration parameter for BodyForces… sigh.

What’s this about AssemblyLinearVelocity? Should I use that instead? Is it more responsive?

Ahh you are using a bodyForce. You should be using a bodyVelocity. You could try assemblyLinearVelocity but I guess looking at the documentation it recommends using this instead VectorForce

I think the body movers are deprecated. So I would try something else personally. Looking into VectorForce seems like the best approach.

1 Like

I actually did use a BodyVelocity previously but that resulted in extremely jerky movement that felt really bad. I did look into VectorForce but it seems that they use Attachments for relative movement, which isn’t what I need. Maybe if I used the BodyVelocity from the client…?

The way you have your code written you are handling the velocity yourself. The problem is body force I believe deals with acceleration. So you would need to keep force constant instead of adding to it every time since acceleration adds to velocity.

nvm on this point. I misread a part of the code. Glad you got it working though. The likely issue was that your acceleration wasn’t high enough, but you weren’t clamping the maxVelocity so it would go way faster than it should.

1 Like

I am now just using a LocalScript and using BodyVelocity instead of BodyForce.

LocalScript Code
local camera = game.Workspace.CurrentCamera
local marblesFolder = game.Workspace:WaitForChild("Marbles")
local uis = game:GetService("UserInputService")
local marble = game.Workspace:WaitForChild("Marble")

function cameraFollow(marble)
	local offset = Vector3.new(-7, 5, 0)
	local camForwardOffset = Vector3.new(2,1.5,0)

	game:GetService("RunService").RenderStepped:Connect(function()
		camera.CFrame = CFrame.new(marble.Position + offset, marble.Position + camForwardOffset)	
	end)	
end


cameraFollow(marble)

marble:SetNetworkOwner(game.Players.LocalPlayer)

local bv = Instance.new("BodyVelocity")
bv.MaxForce = Vector3.new(math.huge, 0, math.huge)
bv.Velocity = Vector3.new(10,0,0)
bv.Name = "BallVelocity"
bv.Parent = marble

uis.InputBegan:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.A then
		bv.Velocity += Vector3.new(0,0,10)
	elseif input.KeyCode == Enum.KeyCode.D then
		bv.Velocity += Vector3.new(0,0,-10)
	end
end)

uis.InputEnded:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.A or input.KeyCode == Enum.KeyCode.D then
		bv.Velocity = Vector3.new(10,0,0)
	end
end)

Unfortunately, it doesn’t work, and I’m not sure why.

EDIT: IT WORKS! LETS GOOOOO

I solved this by setting the NetworkOwner of the marble to the client from a serverscript. Thank you so much for your help @tlr22! Have a nice day!

1 Like