- Home /
Why does writing to rigidbody.velocity after AddForce stop my rigidbody moving?
I've not spotted this issue documented, so hoping someone can help out.
My aim:
Add input force 'correctly' to my rigidbody (via AddForce)
Adjust the rigidbody velocity to stop it going too fast (e.g. by capping the max)
[as an aside, I know for #2, adjusting the rigidbody velocity directly isn't recommended, but I still see it mentioned in Unity Answers and forums and it is even in some Unity demo scripts, I tried to only use it for limiting max velocity]
I didn't even get as far as step #2 though - what I discovered was: After I use AddForce, then making any change to the rigidbody velocity, even just doing a get/set, seems to cancel out any movement made with AddForce.
Any idea what I'm doing wrong? Have I missed something obvious? I'll paste my code below, you can repro by adding a cube and assigning this script to it. See the line marked #THEPROBLEM for whereabouts the issue occurs.
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(Rigidbody))]
public class TestMover : MonoBehaviour {
private Transform cameraTransform;
void Start ()
{
cameraTransform = Camera.main.transform;
}
public float charMoveInputForceXZ = 25f;
void moveCharacter (Vector3 inputVector)
{
rigidbody.AddForce(inputVector * charMoveInputForceXZ * Time.deltaTime , ForceMode.VelocityChange);
// #THEPROBLEM
// The below should have no effect, yet...
// * If commented out, the rigidbody can be moved
// * If compiled in, the rigidbody cannot be moved
// * If compiled in and placed prior to the AddForce line, the rigidbody can be moved
Vector3 v;
v = rigidbody.velocity;
rigidbody.velocity = v;
}
void FixedUpdate ()
{
Vector3 cameraForward;
Vector3 moveInput = Vector3.zero;
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
if (cameraTransform != null)
{
// calculate camera relative direction to move:
cameraForward = Vector3.Scale (cameraTransform.forward, new Vector3(1,0,1)).normalized;
moveInput = v * cameraForward + h * cameraTransform.right;
}
if (moveInput.magnitude > 1)
{
moveInput.Normalize();
}
moveCharacter(moveInput);
}
}
Answer by Owen-Reynolds · Sep 30, 2014 at 04:37 PM
In your case, easiest way is probably to not use AddForce. Change line 19 to rigidbody.velocity+=inputVector...
AddForce is merely a front-end to a direct velocity change. The four versions do/don't divide by mass and multiply by deltaTime. Your first line "should" omit time.deltaTime
and instead use ForceMode.Acceleration
(which applies T.dT for you.)
The "never use velocity directly" advice is really just a warning to people starting out with programming and physics systems. Try to explain what Time.deltaTime
does to a non-mathy person, and you find yourself saying "know what -- forget I mentioned it. Use AddForce."
I use direct velocity changes for everything, in dozens of ways including a velocity limit, and never had a problem like that. But then I never use AddForce, so don't use them together.
For fun, I'd print velocity after each line (print each x,y,z by itself, otherwise it's cut off to the tenths place.) It's gotta be some crazy bug elsewhere, or a compiler glitch.
Ah, right, that makes a lot of sense, thanks!
I did wonder why the docs warned people off of changing velocity and then the samples proceeded to do just that! :-)
Hearing you say:
I use direct velocity changes for everything, in dozens of ways including a velocity limit
is exactly the kind of info I needed - I'll stick to modifying velocity from now on!
[Relating to "For fun..."... It's the writeback on line 31 that triggers the issue, I'd hazard a guess that the internals of the setter for rigidbody.velocity is doing something more than just setting it]
Answer by Kiwasi · Sep 30, 2014 at 07:39 PM
Its worth noting that this is not a bug. There is a reason the docs say not to set velocity every frame, and that is because it overrides the physics engine velocity calculations.
To explain further AddForce does not actually change the velocity of the RigidBody. AddForce queues a force to the physics engine to be applied during the next physics update step. The physics update happens after FixedUpdate. During the physics update all queued forces are applied, and the RigidBody is assigned the new velocity. Then the RigidBody is moved. Direct changes to velocity are applied after the forces are calculated. This is by design, otherwise direct velocity changes would be overridden by the physics engine.
In terms of your script that means v is reading the velocity before the force is applied. In your case 0. Then the physics update is applied, which applies your force. Then you set the velocity back to v, which is zero.
Note: I'm not 100% certain that velocity changes are applied after forces by PhysX. But this does seem to be the most logical cause to your problem. The other possibility is that your force simply isn't large enough. If I get bored enough I might run a few tests to confirm.
Answer by Chris the Horribly Named · Jan 21, 2015 at 08:57 AM
I was intrigued by BoredMormon's post, so I did a quick test in 4.6.1 to confirm/explore when AddForce indeed happens.
Compare version 1:
void FixedUpdate()
{
print("Enter FixedUpdate. rigidbody v: " + this.rigidbody.velocity);
this.rigidbody.velocity += Vector3.forward;
print("Exit FixedUpdate. rigidbody v: " + this.rigidbody.velocity);
}
to version 2:
void FixedUpdate()
{
print("Enter FixedUpdate. rigidbody v: " + this.rigidbody.velocity);
this.rigidbody.AddForce(Vector3.forward, ForceMode.VelocityChange);
print("Exit FixedUpdate. rigidbody v: " + this.rigidbody.velocity);
}
With Version 1 you get what you might expect, namely if you update rigidbody.velocity, the change is visible right away, just as with a "normal variable":
Enter FixedUpdate. rigidbody v: (0.0, 0.0, 0.0)
Exit FixedUpdate. rigidbody v: (0.0, 0.0, 1.0)
Enter FixedUpdate. rigidbody v: (0.0, 0.0, 1.0)
Exit FixedUpdate. rigidbody v: (0.0, 0.0, 2.0)
Enter FixedUpdate. rigidbody v: (0.0, 0.0, 2.0)
Exit FixedUpdate. rigidbody v: (0.0, 0.0, 3.0)
Enter FixedUpdate. rigidbody v: (0.0, 0.0, 3.0)
Exit FixedUpdate. rigidbody v: (0.0, 0.0, 4.0)
With Version 2, in contrast, we see don't see the rigidbody's velocity being increased until we get to the next FixedUpdate:
Enter FixedUpdate. rigidbody v: (0.0, 0.0, 0.0)
Exit FixedUpdate. rigidbody v: (0.0, 0.0, 0.0)
Enter FixedUpdate. rigidbody v: (0.0, 0.0, 1.0)
Exit FixedUpdate. rigidbody v: (0.0, 0.0, 1.0)
Enter FixedUpdate. rigidbody v: (0.0, 0.0, 2.0)
Exit FixedUpdate. rigidbody v: (0.0, 0.0, 2.0)
Enter FixedUpdate. rigidbody v: (0.0, 0.0, 3.0)
Exit FixedUpdate. rigidbody v: (0.0, 0.0, 3.0)
Enter FixedUpdate. rigidbody v: (0.0, 0.0, 4.0)
Exit FixedUpdate. rigidbody v: (0.0, 0.0, 4.0)
So AddForce is indeed being delayed until after FixedUpdate
Thanks! Yes, since my original post, I've kept with AddForce.
Just to add something I tried which helped: For cases when you need to access the 'latest' velocity during the FixedUpdate you can just setup something to cache it, i.e. you just write to your cache and it sums up what you are adding, so reading back gives you what you'd expect. Then at the end of the FixedUpdate you actually send the total cached velocity out via AddForce, and at the start of FixedUpdate you can reset your internal cache to be the rigidbody velocity again.
Answer by Ferb · Sep 30, 2014 at 05:30 PM
My guess would be that there's a bug in which the AddForce is updating the velocity, but the old velocity is still being stored in the physics engine for certain reasons, getting rigidbody.velocity is getting the old value before you changed it, and setting it is setting the value that had been updated.
As for why you shouldn't alter velocity, it's basically because it messes with physics. You could do it if you're really really careful, but all the checks you're going to have to make to avoid unintended consequences will be awkward.
Take this instance for example - using force to move a character, but changing velocity to cap the speed. The trouble is that this may also cap the speed not only when the player is trying to move, but when a fast object is pushing the player. And if the players velocity is capped in that instance, you may have an irresistible force pushing an immovable (or immovable past a certain speed) object, which will cause big problems, such as the two objects starting to overlap. A better solution to cap the player movement speed would be to just check the current movement speed first, and reduce the amount of force applied accordingly. Here is the code I use in my 2D-platformer so that the user can't make the player run faster than 3 units per second. (The commands will be slightly different for 3D-rigidbodies.)
tempvel = rigidbody2D.velocity;
tempvel.x = 3f * hInput;
rigidbody2D.AddForce(0.1f * (tempvel - rigidbody2D.velocity),ForceMode2D.Impulse);
(The multiplication by 0.1 is so that the character slowly accelerates rather than immediately running at the requested speed.) All movement here is by added force rather than setting the velocity, but the speed the user can move the player is still capped, but the character can move at greater speeds if moved by other forces in the level.
Your example doesn't necessarily have a max speed of three. AddForce will gladly move the player faster and faster and faster, like a rocket on a spaceship. If you think about it, it Adds Force, which increases the speed.
The only reason velocity is capped is because of friction (rigidbody drag, and friction$$anonymous$$aterials.) Eventually the Added force and friction exactly cancel, and you get a max speed. But if you tweak drag, the max changes. Or if you want to change the max to 4 ... the math isn't obvious. Or if you want to increase accel, but not max speed.
Normally, the exact speed won't matter, as long as it looks good, and you can do it by eye. If you need an exact max speed, adding if(rigidbody.velocity.magnitude lessThan 3)
before the push mostly works.