Cap speed of part when using VectorForce to move it

I need some assistance with the math on this one.

I am creating a ball that the player can control, and currently I am using a VectorForce to move the ball. So far it works, however I am attempting to tune it to make it feel more responsive, less sluggish.
I’d like to be able to create a variable to define a top speed for the ball, and to adjust the force relative to the ball’s current speed so that it doesn’t go faster than the top speed.

Any ideas on how to accomplish this?

my apologies if it’s simple math, my brain isn’t thinking straight atm

4 Likes

show us your current code so we can build on it

Here is the code that sets the force.

force.Force = char.Humanoid.MoveDirection * Vector3.new(25000,0,25000)

The force variable is the VectorForce. char is the character.

1 Like

By setting the force to some constant multiplied by the move direction, you’re effectively setting the acceleration to something with a constant magnitude. You want the velocity to have a constant magnitude, so it’s not quite right. Instead you should set the acceleration to something that scales with the difference between the target velocity and the current velocity.

E.g.:

local rollSpeed = 10
local P = 1
   
function unitOr0(vector)
	return vector.Magnitude > 0 and vector.Unit or Vector3.new()
end

function updateRollForce(dt)
	local targetVelocity = unitOr0(inputRollDir) * rollSpeed
	local differenceVelocity = targetVelocity - ball.Velocity
	rollForce.Force = differenceVelocity * ball:GetMass() * P
end

where inputRollDir returns a vector representing the direction the player wants to move (e.g. humanoid:GetMoveDirection and P is some number that makes the ball match the rollSpeed more or less “aggressively”. E.g. a P of 1 makes the ball get to a speed of 10 in like 5 seconds, but a P of 100 makes it reach it in just a few frames.

The unitOr0 function is just to deal with cases where inputRollDir is (0, 0, 0) so inputRollDir.Unit would be (nan, nan, nan) and break stuff. We need the unit vector of the movement vector because otherwise diagonal movement would be faster than movement in 1 direction (W + A = (-1, 0, -1)).

EDIT:

This system has some limitations and caveats:
  • Setting a high rollSpeed or P, or even just having the system respond to the ball moving at very high velocities and changing direction, can cause the force to be so large that the ball “spazzes out” and flings into low earth orbit. This can be prevented by putting some upper limit to the magnitude of the force e.g. by comparing the some maxForceMagnitude variable. This reduces responsiveness at very high speed differences but shouldn’t be a problem in most cases.
  • As the velocity gets closer and closer to the target velocity, the force gets smaller and smaller. This means the target rollSpeed might never be reached, especially for small values of P. Slightly increase the rollSpeed to compensate for this, and check the actual velocity like so: print(string.format("%.2f", ball.Velocity.Magnitude)). Alternatively, use a “PI controller” instead (this is just a “P” controller), but that’s a much more advanced topic and a bit harder to implement.
  • This doesn’t respect when the ball has speed from something other than the player trying to control it. E.g. if the ball is rolling downhill at 20 studs/s or a jump pad shoots the ball at 50 studs/s, the control script will try to stop it in a way that makes it seem pretty unnatural. Temporarily disable the force being applied for a few seconds, or implement a more complicated movement system that can deal with this properly (e.g. the “Source engine” movement physics).
6 Likes

I’m looking for a way to be able to define a variable as the max speed. Is there a way to modify your functions to accomplish this?

1 Like

You just set the rollSpeed variable to something at the top of your script :slight_smile:
E.g.

local rollSpeed = 10
local P = 1

This should work in theory

local maxVelocity = 100

Part:GetPropertyChangedSignal('Velocity'):Connect(function()
    local X,Y,Z
    
	if math.abs(Part.Velocity.X)>maxVelocity then
		X = (Part.Velocity.X/math.abs(Part.Velocity.X))*maxVelocity
	else
      	X = Part.Velocity.X
    end
    
    if math.abs(Part.Velocity.Y)>maxVelocity then
		Y = (Part.Velocity.Y/math.abs(Part.Velocity.Y))*maxVelocity
	else
      	Y = Part.Velocity.Y
    end
    
    if math.abs(Part.Velocity.Z)>maxVelocity then
		Z = (Part.Velocity.Z/math.abs(Part.Velocity.Z))*maxVelocity
	else
      	Z = Part.Velocity.Z
    end
    
    Part.Velocity = Vector3.new(X,Y,Z)
end)

I think

try it out

The issue is that your code doesn’t seem to provide much acceleration at all. How should I modify it to increase the acceleration?
It’s probably simple, my brain isn’t working atm

Nevermind, figured it out. Completely missed the P variable!

1 Like