- Home /
How do I properly limit velocity while using AddForce?
I have a simple PlayerMovement script for a 2D platformer and I am attempting to limit left/right velocity to the float maxSpeed:
using UnityEngine;
public class PlayerMovement : MonoBehaviour {
public float speed;
public float maxSpeed;
private Rigidbody2D rb;
void Start() {
rb = this.gameObject.GetComponent<Rigidbody2D>();
}
private void FixedUpdate() {
float x_movement = Input.GetAxis("Horizontal");
if(rb.velocity.magnitude < maxSpeed) {
Vector2 movement = new Vector2(x_movement, 0);
rb.AddForce(speed * movement);
}
}
void Update() {
if (rb.velocity.magnitude > 0)
Debug.Log(rb.velocity.magnitude);
}
}
"rb.velocity.magnitude < maxSpeed" in FixedUpdate() is the suggested method to limit velocity as per several tutorials and answers I have read here. However, this does not work. Via the Debug.Log statement in Update(), I can see that the value of rb.velocity.magnitude often exceeds maxSpeed. I assume this is due to FixedUpdate() not being called as often as frames are being rendered, meaning that my speed can exceed maxSpeed on any frames that FixedUpdate() is not being run.
I tried several alterations of this code, but what finally allowed me to limit the velocity to maxSpeed and maintain that speed while continuing to move were these changes in Update() and FixedUpdate():
private void FixedUpdate() {
float x_movement = Input.GetAxis("Horizontal");
if(rb.velocity.magnitude <= maxSpeed) {
Vector2 movement = new Vector2(x_movement, 0);
rb.AddForce(speed * movement);
}
}
void Update() {
rb.velocity = new Vector2 (Mathf.Clamp(rb.velocity.x, 0 - maxSpeed, maxSpeed), 0);
if (rb.velocity.magnitude > 0)
Debug.Log(rb.velocity.magnitude);
}
This does exactly what I want it to do as I can observe with Debug.Log, but it does not feel right to me. I am now essentially limiting the velocity in two places, in addition to using both AddForce and directly setting rb.velocity. Changing "rb.velocity.magnitude <= maxSpeed" to "rb.velocity.magnitude < maxSpeed" results in fluctuations in rb.velocity.magnitude once the maximum speed is reached (although not exceeding maxSpeed).
So, is my solution perfectly acceptable and I'm just being weird about it? Or is there a better solution?
Your solution is perfectly acceptable imo, unless you have picky players or something.
Answer by Captain_Pineapple · Oct 20, 2021 at 11:31 AM
well yes ofc you can often exceed maxSpeed like this as your current approach is to check if speed <= maxSpeed
and if that is the case you add some value deltaSpeed
. You do not check if the result is larger than maxSpeed thus it naturally can get larger.
So how to solve that? Through physics..
you change in velocity should be
deltaSpeed = rb.mass* movement * Time.fixedDeltaTime;
so basically calculate that deltaSpeed and check if the new speed after adding this would be larger than maxSpeed. If so reduce the force you add by a factor of (maxSpeed - currentSpeed)/deltaSpeed
This sould then make sure that you only ever reach maxSpeed
at most.
Okay, are you basically saying I shouldn't be using AddForce at all? Or could I implement this solution with AddForce?
You can use AddForce but the max speed will be a little faster than what you might expect.
Using rb.velocity.normalized.
you can totally implement it as described. i do not see how i say that you should not use addforce? i just said that your current check is flawed as you already noticed yourself and pointed out why it is flawed.
I guess I'm not not quite sure where it should go if it's not replacing AddForce? Also, won't the speed still fluctuate instead of remaining constant once I reach maxSpeed, as it did with one of my changes?
Answer by darksider2000 · Oct 22, 2021 at 06:08 PM
Your method will work as intended if you move the Clamp from Update() to FixedUpdate():
private void FixedUpdate() {
float x_movement = Input.GetAxis("Horizontal");
if(rb.velocity.magnitude <= maxSpeed) {
Vector2 movement = new Vector2(x_movement, 0);
rb.AddForce(speed * movement);
}
// Move it here
rb.velocity = new Vector2 (Mathf.Clamp(rb.velocity.x, 0 - maxSpeed, maxSpeed), 0);
}
void Update() {
if (rb.velocity.magnitude > 0)
Debug.Log(rb.velocity.magnitude);
}
This should hopefully fix your jittering problem.
Your initial method didn't work because you didn't have the Clamp. Meaning you could enter the If statement (v < max), add a force that pushes velocity above max speed (v > max), and it would stay above max speed. Next frame you simply wouldn't enter the If statement, but the velocity would still be (v > max) and you didn't have any code to correct it in that scenario.
Adding a Clamp. An easy and simple fix, so why did it not feel right? FixedUpdate() is called a fixed number of times per second. While Update() is called as many times per second as your in game FPS. This means operations you do in FixedUpdate() are most of the time not in sync with Update(). So if you, again, add a force to push (v > max) inside FixedUpdate(), you'll have to wait for the next Update() to be called before the Clamp corrects it to (v = max).
Let me know if this does or doesn't work out!
I wish I could say that this worked, but unfortunately it did not. What happens with this code is that the speed fluctuates between two values - one value above what is set as the max speed and one below it. What I'd like to see is for the player to reach the max speed and stay at that speed as long as they are moving in the same direction, which only happens when I place this code in the Update() function.
Thank you for your response though, let me know if maybe I did something wrong or need to add something to this!