- Home /
A simple solution for constant rigidbody movement without changing and clamping velocity directly
Hello, I'm studying Unity game development about 5 years, but I had about 2-3 atempt for all these time of writing player controller and they were very simple, so yesterday i started to write own player movement system. From just a beginning I ran into a problem what to use to achieve the result I want. In Unity there are two most popular ways to write player controller: CharacterController and Rigidbody. I realised that the first don't fit to me because CC does not interact with other rigidbodies by default and is not very "physically realistic". So I choosed the rigidbody. Those people, who worked with RB( rigidbody ) may know about very cool method as AddForce and similar. But the main reason because of whith this method doesn't fit for movement( in some cases ) is that it will always accelerate a rigidbody, no "constant" movement speed, so someone can think that we need to limit the speed and will write something like this:
void FixedUpdate()
{
var targetVelocity = someMoveDirection.normalized * speed;
Vector3 velocity = rig.velocity;
Vector3 velocityChange = (targetVelocity - velocity);
velocityChange.x = Mathf.Clamp(velocityChange.x, -maxVelocityChange, maxVelocityChange);
velocityChange.z = Mathf.Clamp(velocityChange.z, -maxVelocityChange, maxVelocityChange);
velocityChange.y = 0;
rig.AddForce(velocityChange, ForceMode.VelocityChange);
}
But such a code will always limit your rigidbody's velocity to maxVelocityChange variable which is completely wrong way! Try to throw an massive rigididbody with enough speed into player and you will understand what I'm talking about. The result will be, for example, that player with mass 1 and speed 0 will stop some RB with mass 1000 and speed 500..
So I decided to return to AddForce method and look again what is wrong and how it can be resolved. I wanted to limit only the speed of my horizontal movement but not the velocity of RB in total. And one brilliant but at the same time simple thought came to my mind. How about adding as much force as it needed to keep the magnitude of RB's velocity "constant"? But how to reach that? The solution is very simple!
var targetVelocity = someMoveDirection.normalized * speed;
Vector3 horizontalVelocity = rig.velocity;
horizontalVelocity.y = 0.0f;
myRB.AddForce(targetVelocity * (1 - (horizontalVelocity.magnitude / maxMovementSpeed)));
As the result I just not add force to my RB if the speed is max. I hope it will help many people! :D
P.S. I am always open to any critique. if I wrote something wrong, you always can write bellow what is incorrect.
Yes, you did something wrong ^^. You included the answer in your question. Please post an actual answer to your question and remove the answer from the question ;)
Note that your solution is not complete / has a nasty bug. If the player gets pushed / accelerated into one direction faster than your max$$anonymous$$ovementSpeed, then this term
(1 - (horizontalVelocity.magnitude / max$$anonymous$$ovementSpeed))
will get negative. Now if the player presses the button in the opposite movement direction he can accelerate infinitely since your targetvelocity (which is not a velocity but a force) is applied in the current movement direction. So as a result you will get even faster. This makes the term above even more negative. The larger it gets the more force you can apply. It would be somewhat similar to the accelerated back hop glitch in the Source engine, though different.
Also you could replace horizontalVelocity.magnitude / max$$anonymous$$ovementSpeed
with
horizontalVelocity.sqr$$anonymous$$agnitude / (max$$anonymous$$ovementSpeed*max$$anonymous$$ovementSpeed)
That way you get rid of the square root and you get a slightly better result as the applied force will not be reduced linearly but quadratic. So your code does apply 50% force when you are at half the velocity. Using the squared magnitude you will apply 75% of your force. So it stays longer closer to 1 and only drops to 0 when you get close to your max velocity.
In addition you could just multiply the (1 - sqr$$anonymous$$ag/maxSpeed) term by an arbitrary factor greater than 1 to introduce a "hold" phase. Since we now use $$anonymous$$athf.Clamp01 we will always get a value between 0 and 1. However since the term inside will be greater than 1 even we have not yet reached the max velocity we get a resulting factor of 1 for a longer time. So for example if you multiply by a factor of 2, at half the velocity you would still apply the full force.
Well this is all just basic math and actually a crude version of a PID controller with only a linear (proportional) part. andeeeee wrote a simple PID controller class almost 10 years ago if someone is interested.
[2]: https://docs.unity3d.com/ScriptReference/$$anonymous$$athf.Clamp01.html
What is the different between the rigidbody myRB and rig?
Answer by Reid_Taylor · Feb 08, 2020 at 01:53 AM
Also if you don't wanna go through the trouble you can use rigidbody.velocity = transform.TransformDirection(Vector3.(left, right, back or forward) * speed).
Answer by PandrPi · Feb 18, 2020 at 12:07 PM
@Bunny83 Yes, sorry, I just forgot to write clamp the value. And yes, this post is not a question, it is something like discussion for reaching simple movement :)
Also I found out that if do like I wrote here the result will be that player will move like on ice in some cases.
So now I use rigidbody.$$anonymous$$ove method in FixedUpdate to move the player.
Your answer
Follow this Question
Related Questions
Movement script breaks colliders 1 Answer
Character controller jittering up and down 2 Answers
Player is Speeding up in collisions 0 Answers
Rigid body child Player movement 0 Answers
Collisions causing Rigidbody to go Haywire and move randomly. 1 Answer