Limit the velocity of a Networked Physics object.

Hello,
As the the topic states I am trying to limit the velocity of a Networked Physics component. I am using a NetworkPhysicsComponent’s AsyncPhsyicsTickComponent method to apply forces based on the player’s input, ie:

void UVesselMovementComponent::AsyncPhysicsTickComponent (float DeltaTime, float SimTime)
{
   Super::AsyncPhysicsTickComponent(DeltaTime, SimTime);
...

    // Apply inputs    
    if (VesselInputs.bAreBoostersActive)
	{
		FVector boosterForce = HullMesh->GetForwardVector() * BoosterForce * BodyInstance->GetBodyMass();
		HullMesh->AddForce(boosterForce);
	}

... 
    // Limit velocity ~ this seems to wipe out any previous applied forces
    if(HullMesh->GetPhysicsLinearVelocity().Length() > 500)
	{
		FVector limitedVelocity = UKismetMathLibrary::ClampVectorSize(HullMesh->GetPhysicsLinearVelocity(), 0, 500);
		HullMesh->SetPhysicsLinearVelocity(limitedVelocity);
	}
}

However, when my speed limit is exceeded, hard setting the velocity seems to scrap any applied forces (including ones that would push the object in the other direction) which results in the object getting locked in to the max velocity vector. I almost need a post async tick function to run after the new forces have been applied.

Does anyone know how to limit the velocity of a networked physics object using the async physics tick? I believe it needs this in order for correct resimulation.

I was able to get this to work blueprints outside of networking via applying the forces in on input actions, and limiting the velocity in the base blueprint Tick, but that isn’t networked and I have been re-writing components to get multiplayer working.

Also, the guide I was following applied forces to the core component’s RigidBodyHandle via getting it from GetPhysicsThread(), like this:

if (const auto Handle = BodyInstance->ActorHandle)
	{
		RigidBodyHandle = Handle->GetPhysicsThreadAPI();
	}

Where RigidBodyHandle is: Chaos::FRigidBodyHandle_Internal*
And BodyInstance is: FBodyInstance*
However I’m just directly applying the forces to the component that the RigidBodyHandle wraps.
I’m was unable to find a difference in results from testing. Does anyone know the differences there?

Im using Unity 5.5

Thanks for your time.

Updating because I figured it out after talking to ChatGPT, dusting off the ol’ physics text books, and reading some other blogs.

Firstly, some Unreal book keeping:

  • Make sure your physics settings are set correctly in Project Settings > Physics > Framerate:

    I found these settings to be smooth, I’ll need to tinker more with them when I understand them better.
  • In AsyncPhysicsTickComponent be sure to apply forces to the RigidBodyHandle (on the physics thread) and not the actual RigidBody the handle is representing on the main thread.

Onto solving the problem: Limiting speed by adjusting applied force, with the basic force formula: Force = Mass * Acceleration
Effectively, we want acceleration to approach 0 as we approach our max speed, and have acceleration respect our arbitrary MaxSpeed.

We can determine the amount of acceleration we need by:
Acceleration = DeltaVelocity/DeltaTime
DeltaVelocity = (InputVelocity - CurrentVelocity) / DeltaTime
InputVelocity = InputDirection * MaxSpeed

Unfortunately, CurrentVelocity isn’t that simple ~ it needs to be the Current Velocity’s contribution to the target InputVelocity. For example, imagine our InputVelocity is(4,0) and our CurrentVelocity is(1,3), current Velocity is already contributing 1unit of velocity, so we would only need an addition 3 units of speed.
We need to project CurrentVelocity onto our desired InputVelocity which gives us a magnitude of CurrentVelocity already done by InputVelocity. Lets call this our ContributingSpeed.
From our example, projecting CurrentVelocity onto InputVelocity, our ContributingSpeed is:

= Dot(CurrentVelocity(1,3), InputVelocity(4,0)) / Magnitude(InputVelocity(4,0)
= ((1*4) + (3*0) ) / Sqrt(4^2+0^2)
= (4+0)/sqrt(16)
= 4/4
= 1

This means that our current velocity is contributing 1 unit of speedtowards our InputVelocity target speed already. Some notes:

  • the result is NOT a vector, its a magnitude.
  • if the result is NEGATIVE, our current velocity is in an opposing direction to our InputForce
  • if the result is LARGER than our InputVector magnitude, we are already traveling faster in that direction than our input wants us to go.

Next we want to consider the previous notes and determine the % of InputVector still needed to hit our target acceleration, we can call this % our ContributionFactor, which should be between 0 and 1 (0 no more velocity is needed, 1 need full possible velocity):

if  (ContributingSpeed < 0 )
  // current velocity is opposing our input so we want to apply our full possible acceleration.  
  ContributionFactor = 1 
else
  // Want to see how velocity is still needed, but limit it in the event we are already traveling faster than desired.
  ContributionFactor = 1 - Clamp(ContributingSpeed/Magnitude(InputVector), 0, 1)

Now, we can use ContributionFactor on our InputVelocity to determine our needed Acceleration, and clamp it by our thruster’s max force as needed:

Acceleration = InputVelocity * ContributionFactor / DeltaTime
LimitedAcceleration = Acceleration.ClampMagnitude(ThrusterForce*Mass);
RigidBody.AddForce(LimitedAcceleration * Mass)

The C++ Unreal code is:

void UGameMovementComponent::AsyncPhysicsTickComponent (float deltaTime, float simTime)
{
	Super::AsyncPhysicsTickComponent(deltaTime, simTime);

	// Setup
	if (const auto Handle = BodyInstance->ActorHandle)
	{
		RigidBodyHandle = Handle->GetPhysicsThreadAPI();
		if (!RigidBodyHandle) return;
	}
	const FVector inputDirection = FVector::UpVector; // This would come from the player's input struct
	const float mass = 1000;
	const float maxSpeed = 750;
	const float thrusterForce = 3000;

	// Acceleration Based.
	const FVector InputVelocity = inputDirection * maxSpeed;

	// Determine the current velocity contributions
	const float currentVelocityContribution = RigidBodyHandle->V().Dot(InputVelocity)/InputVelocity.Length(); // Note, RigidBodyHandle->V() is the current velocity of the rigid body state, thread bound and stuff.
	const float velocityContributionFactor = currentVelocityContribution < 0.0 ? 1.0 : 1.0 - FMath::Clamp(currentVelocityContribution/InputVelocity.Length(), 0.0, 1.0);

	// Determine the target 
	const FVector targetAcceleration = inputDirection * velocityContributionFactor / deltaTime;
	const FVector limitedAcceleration = targetAcceleration.GetClampedToMaxSize(thrusterForce * mass);
	
	const FVector appliedForce = limitedAcceleration * mass * deltaTime;
	RigidBodyHandle->AddForce(appliedForce, true);
}

Here’s a video of it in action. Thrusters visually activate when force/input is being applied. Note the continued drift/velocity:

Some final notes:

  1. This applies force in the direction of the input, it does not redirect the object to fly towards the input. Meaning, imagine you start moving directly forward, then apply input directly right. In this implementation you would be traveling up AND right. If you want to use input to redirect current velocity to match the input, you need to replace the “contributing velocity” stuff with current velocity in acceleration, ie: Acceleration = (InputVelocity - CurrentVelocity) / DeltaTime. This would effectively give you an opposing/corrective vector to apply force on.
  2. This implementation allows you to exceed max velocity, just like from 1. You can thrust straight, then thrust right, and you would continue your straight-ward velocity. This is by design ~ I’m trying to do an arcady feel to space flight where you can have strong boosters that wont take you up 1000’s of km/h. Also, it allows for a faster ship to ‘push’ a slower ship which should help keep the sandbox feeling, I hope.

Hope this helps someone in the future.

1 Like