Smooth ball movement

Hello, I am trying to achieve smooth ball movement similar to SBB(Super Blocky Ball)
I tried using linearvelocity but I faced an issue where when it hits a wall it goes insanely fast due to how linearvelocity works

I thought of casting rays every frame to lower the velocity whenever it hits a wall but that sounds a bit expensive and may need some if statements to make it work perfectly

I am wondering if there is an easier way of doing it

Thanks for any help! I appreciate it!

4 Likes

Did you try using other physics movers like VectorForce.
Just make sure you look at all the properties though. Changing one Property from true to false may make huge differences in the way other properties work.

Also I just did a quick search of posts using the term “moving ball” and got quite a few hits. Have a look at those posts as well.

1 Like

I never actually searched moving ball I always searched ball movement maybe that’s why I never find something useful

Vectorforce not sure how it really works one time I did try it on a part the speed kept increasing did I do something wrong i am not sure I just set attachment and velocity I will look it up to see more about it

Sorry for the late reply btw

1 Like

I tried VectorForce but unsure on how it works documentation doesn’t tell much
VectorForce doesn’t move it although I set the force and vectorforce doesn’t have any properties that affects it or cause that to happen

I tried searching too but didn’t find anything helpful

It’s all in the VectorForce Properties. If you research each Property there’s a basic mention of what they do. Even setting one incorrectly will make it not do what you want since it can affect 2 of the other properties.
VectorForce Article
VectorForce API

yea i know there are properties but each one of them won’t change much how it works
I went through them and they didn’t do much

Do you know other ways to make the ball movement?

Edit: for some reason the ball doesn’t move at all even though I set attachment made it relative to the world and increased the velocity to a high number

I am still looking for any help.

a simple way to get smooth SBB-style rolling without LinearVelocity explosions: drive the ball with AngularVelocity (torque-like) and cap the linear speed. High-ish friction + low elasticity keeps wall hits sane.

here is a tut to do it

  • Make a Ball part (Shape = Ball, Anchored = false).
  • Inside it, add an Attachment and an AngularVelocity constraint.
  • Drop this LocalScript in StarterPlayerScripts (it finds/creates the pieces, rolls relative to camera, and clamps speed).
-- LocalScript (StarterPlayerScripts)
local Players = game:GetService("Players")
local UIS = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local cam = workspace.CurrentCamera

local ball = workspace:WaitForChild("Ball")
local att = ball:FindFirstChildOfClass("Attachment") or Instance.new("Attachment", ball)
local av = ball:FindFirstChildOfClass("AngularVelocity") or Instance.new("AngularVelocity", ball)
av.Attachment0 = att
av.RelativeTo = Enum.ActuatorRelativeTo.World
av.MaxTorque = 20000
av.ReactionTorqueEnabled = true

-- grippy, non-bouncy
pcall(function()
	ball.CustomPhysicalProperties = PhysicalProperties.new(1, 0.8, 0, 0, 1)
end)

local move = Vector3.zero
local function setKey(k, down)
	local delta = down and 1 or -1
	if k == Enum.KeyCode.W then move += Vector3.new(0,0,-1)*delta end
	if k == Enum.KeyCode.S then move += Vector3.new(0,0, 1)*delta end
	if k == Enum.KeyCode.A then move += Vector3.new(-1,0,0)*delta end
	if k == Enum.KeyCode.D then move += Vector3.new( 1,0,0)*delta end
end

UIS.InputBegan:Connect(function(i,g) if not g then setKey(i.KeyCode, true) end end)
UIS.InputEnded:Connect(function(i,g) setKey(i.KeyCode, false) end)

local radius = ball.Size.X*0.5
local targetSpeed = 40   -- how fast it tries to roll (studs/s)
local maxSpeed = 60      -- hard cap

RunService.RenderStepped:Connect(function()
	-- movement dir relative to camera on XZ
	local dir = Vector3.new(move.X,0,move.Z)
	if dir.Magnitude > 0 then
		dir = cam.CFrame:VectorToWorldSpace(dir)
		dir = Vector3.new(dir.X,0,dir.Z).Unit
		-- roll: ω = (up × dir) * (speed / r)
		av.AngularVelocity = Vector3.new(0,1,0):Cross(dir) * (targetSpeed / math.max(0.01, radius))
	else
		av.AngularVelocity = Vector3.zero
	end

	-- clamp crazy speeds after wall hits
	local v = ball.AssemblyLinearVelocity
	local s = v.Magnitude
	if s > maxSpeed then
		ball.AssemblyLinearVelocity = v.Unit * maxSpeed
	end
end)

I know it’s a little copy and paste’y. but if you are a new dev and don’t understand some of it just try breaking it up and learning what parts do. You can give it to chat gpt or ask me questions about it. it is client so other players will not see it but you can use remote events to connect the client to server

1 Like

since I like to script and learn instead of copy and pasting cuz I feel lazy so lazy when I do that so i have a few questions

Does any mover constraint apply velocity through assemblylinearvelocity so whenever I want to cap it I just edit it?

-- roll: ω = (up × dir) * (speed / r)
av.AngularVelocity = Vector3.new(0,1,0):Cross(dir) * (targetSpeed / math.max(0.01, radius))

Explain please what is behind this math.

btw I understand what cross does but I don’t know which order should I do to get exactly what I need

I’ll break it down more if you need to

  • Vector3.new(0,1,0)
    The world up vector (pointing straight up along Y). Think of it as the surface normal for flat ground.
  • dir
    The movement direction you want on the ground (usually a unit vector, i.e., length = 1). In practice you compute it from input and camera and then normalize:
dir = dir.Magnitude > 0 and dir.Unit or Vector3.ze
  • (targetSpeed / math.max(0.01, radius))
    The spin rate you need. For rolling without slipping:ω=vr\omega = \frac{v}{r}ω=rv​where v = targetSpeed (studs/sec) and r = radius (studs). The result ω is in rad/sec.
    • math.max(0.01, radius) returns the larger of 0.01 and radius. This guards against dividing by 0 (or an extremely tiny radius) which would blow up the number. 0.01 is just a small epsilon to keep things stable.

Putting it together

  1. axis = up:Cross(dir) → find the axis to spin around so the ball rolls in dir.
  2. spin = targetSpeed / radius → how fast to spin (ω = v/r).
  3. av.AngularVelocity = axis * spin → tell the constraint to spin around that axis at that rate.

Quick glossary

  • Tangent: on a circle/sphere, the tangent direction is the direction along the surface, perpendicular to the radius at the contact point. Rolling without slipping means the surface point’s tangential speed equals the ball’s ground speed (v = r * ω).
  • Cross product: a 3D operation giving a vector perpendicular to two inputs; used for axes and normals.
  • math.max(a, b): returns the larger of a and b.

Clear version with names

local up = Vector3.new(0, 1, 0)           -- world up
local axis = up:Cross(dir)                -- rotation axis (perpendicular to up and dir)
local spin = targetSpeed / math.max(0.01, radius)  -- ω = v / r, safe divide
av.AngularVelocity = axis * spin

(If the ball is on slopes, use the surface normal instead of up for axis.)

I still don’t quite understand it that’s a bit complex math for me is there a way to avoid all that math or maybe even use linearvelocity and cap the speed when hitting a wall

btw thanks for scripting for me but I don’t need scripts all I need is a basic idea that I can apply I know enough about scripting to do it and again thanks for scripting

there are ways vector force is the right option if you want simplicity, but In the SBB game you said I would bet they use angular velocity so differing from it I’m not 100% sure it will be “smooth”. Like blocks might work but you could just make the block collision off and weld it to a ball but its corners will go into the ground.

Here are 2 options

Note:

It’s ok not to understand 100% of the whole code Like I worked on a fps game before and using cframes I found it very complicated I low key just used AI for it so as long as you get the gist of the code then you should be fine and guessing you have not finished school like me you will understand it eventually. also I’m going to spill the beans I am using AI for this response I do copy and paste but I will try to understand it to the full potential

Option A (no angular math): push the ball with VectorForce

Let friction make it roll. You just apply a force in the move direction and clamp speed.

-- LocalScript (StarterPlayerScripts)
local Players = game:GetService("Players")
local UIS = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local cam = workspace.CurrentCamera
local ball = workspace:WaitForChild("Ball")

-- grippy, not bouncy (so it rolls instead of sliding)
pcall(function()
	ball.CustomPhysicalProperties = PhysicalProperties.new(1, 0.8, 0, 0, 1)
end)

local att = ball:FindFirstChildOfClass("Attachment") or Instance.new("Attachment", ball)
local vf = ball:FindFirstChildOfClass("VectorForce") or Instance.new("VectorForce", ball)
vf.Attachment0 = att
vf.RelativeTo = Enum.ActuatorRelativeTo.World

local move = Vector3.zero
local function setKey(k, down)
	local d = down and 1 or -1
	if k == Enum.KeyCode.W then move += Vector3.new(0,0,-1)*d end
	if k == Enum.KeyCode.S then move += Vector3.new(0,0, 1)*d end
	if k == Enum.KeyCode.A then move += Vector3.new(-1,0,0)*d end
	if k == Enum.KeyCode.D then move += Vector3.new( 1,0,0)*d end
end
UIS.InputBegan:Connect(function(i,g) if not g then setKey(i.KeyCode, true) end end)
UIS.InputEnded:Connect(function(i,g) setKey(i.KeyCode, false) end)

local ACCEL = 80          -- how hard you push (studs/s^2)
local MAX_SPEED = 60

RunService.RenderStepped:Connect(function()
	local dir = Vector3.new(move.X,0,move.Z)
	if dir.Magnitude > 0 then
		dir = cam.CFrame:VectorToWorldSpace(dir)
		dir = Vector3.new(dir.X,0,dir.Z).Unit
		-- F = m * a, use AssemblyMass so it feels consistent
		vf.Force = dir * (ball.AssemblyMass * ACCEL)
	else
		vf.Force = Vector3.zero
	end

	-- simple speed cap
	local v = ball.AssemblyLinearVelocity
	if v.Magnitude > MAX_SPEED then
		ball.AssemblyLinearVelocity = v.Unit * MAX_SPEED
	end
end)

Pros: super simple; no cross product; works great if friction is high.
Cons: on very slippery surfaces it may slide before it “catches”.


Option B (keep AngularVelocity, hide the math)

Make a tiny helper so you never look at the formula again.

-- one helper
local function rollTowards(av, dir, targetSpeed, radius)
	if dir.Magnitude < 1e-3 then
		av.AngularVelocity = Vector3.zero
		return
	end
	dir = dir.Unit
	-- internally still uses cross, but you don't deal with it elsewhere
	local axis = Vector3.yAxis:Cross(dir)            -- which way to spin
	local spin = targetSpeed / math.max(0.01, radius) -- how fast to spin (ω = v/r)
	av.AngularVelocity = axis * spin
end

Use it like:

local radius = ball.Size.X * 0.5
local targetSpeed = 40

-- each frame:
local dir = Vector3.new(move.X,0,move.Z)
if dir.Magnitude > 0 then
	dir = cam.CFrame:VectorToWorldSpace(dir)
	dir = Vector3.new(dir.X,0,dir.Z)
	rollTowards(av, dir, targetSpeed, radius)
else
	av.AngularVelocity = Vector3.zero
end

Pick whichever style you like: VectorForce keeps it math-free; the helper keeps AngularVelocity but hides the details.

1 Like

Why not cap vectorforce.Force instead of assemblylinearvelocity?

1 Like

sorry for the late reply I went to sleep you can definitely go with assemblylinear velocity its Just that the ball will move a bit like roblox rigs and I don’t think it will rotate the ball because it will just make the ball look in only one direction which isn’t what you want so the three best options are

  1. AngularVelocity
  2. VectorForce or linear velocity
1 Like