Home > Net >  Unity Physics - Calculate the deceleration needed to stop at a specified distance
Unity Physics - Calculate the deceleration needed to stop at a specified distance

Time:05-24

I have been trying to find a solution to this problem for a while now, but I feel like I lack the understanding of how physics work to come to the right solution. Hopefully someone here can help me!

The Problem

Like the title states - what I want to accomplish is to decelerate a moving object to a complete stop and reach a specific distance.

Context

I am specifically trying to implement this to be used in a player controller, where once input is no longer provided I take the moving object's current velocity and slow it to a stop x units from its position at the point of release.

Current Approach

Currently I understand that I know (a) the initial velocity (b) the target velocity (c) the distance traveled over the change in velocity and (d) the target distance to reach.

I have been able to get this working at a specific speed of 5 units / second using this script:

public class DecelToStop : MonoBehaviour

{ Rigidbody2D rb;

public float speed = 5f; // velocity of object while input is pressed
public float stoppingDistance = 3f; // distance object should stop at on input released
public float stopTimeMultiplier = 3.5f; // multiplier applied to time step to reach desired stopping distance


bool inputIsReleased = false;
float decelerationNeeded = 0;

public float GetDeceleration(float initalVelocity, float targetVelocity)
{
    
    float travelTime = stoppingDistance / initalVelocity; // total time needed to reach a stop
    float velocityChange = targetVelocity - initalVelocity;// total change in velocity
    float decelTimeMultiplier = Mathf.Sqrt(stoppingDistance * stopTimeMultiplier); // how much to multiply the travel time by
    float deceleration = initalVelocity / (travelTime * decelTimeMultiplier); //amount of deceleration to apply each fixed update

    return deceleration;
}

private void FixedUpdate()
{
    // get deceleration needed on input release
    if (!inputIsReleased)
    {
        decelerationNeeded = GetDeceleration(speed, 0);
        inputIsReleased = true;
    }

    // apply total force needed by applying the inital speed and the deceleration neeed 
    if (rb.velocity.x != 0)
    {
        rb.AddForce(new Vector2(speed, 0)); 
        rb.AddForce(new Vector2(decelerationNeeded, 0));
    }
}

}

The problem with my current approach is once I change the speed variable the stopTimeMultipler becomes exactly what I am trying to avoid - which is a bunch of guess work to find the exact value needed for everything to work properly.

I'm sure there are multiple flaws in this approach - like I said I don't have a great understanding of physics calculations - so if you have a solution to this if you could explain it like you were talking to a 5 year old that would be great! The solution doesn't need to hit the exact stopping distance - there can be some variation as long as it is relatively close (within 0.2 units) and can scale with varying speeds and stopping distances.

CodePudding user response:

Ok, after spending some more time on this I have been able to find a scalable solution.

I have completely reworked my approach - and instead switched to accelerating and decelerating my rigidbody using the methods shown in these two videos: https://www.youtube.com/watch?v=uVKHllD-JZk https://www.youtube.com/watch?v=YskC8h3xFVQ&ab_channel=BoardToBitsGames (I recommend watching these to fully understand how acceleration is implemented)

These videos allowed me to accelerate and decelerate an object to a target speed within a specified amount of time.

From here I was able write a method that converted a provided distance value into the time variable needed to find the correct amount of acceleration to apply each update.

I found the formula to do this here: https://physics.stackexchange.com/questions/18974/what-do-i-need-to-do-to-find-the-stopping-time-of-a-decelerating-car

But for the c# implementation look to the ConvertDistanceToVelocityStep() method in the code below.

All my testing so far shows that this approach allows for only a max speed and desired stopping distance to be provided to slow a moving object to a complete stop at a specified distance once input is no longer provided.

Here is the full script with notes - if you have any optimizations or suggested improvements feel free to leave them below.

public class Accelerator : MonoBehaviour

{ Rigidbody2D m_Body2D;

// - Speed
public float maxSpeed = 6f;

// - Distance
public float stoppingDistance = 1f;
public float accelDistance = 1f;

// - Time
float timeZeroToMax = 2.5f;
float timeMaxToZero = 6f;
float accelRatePerSec;
float decelRatePerSec;
float xVel;

public bool inputPressed = false;
public bool allowInputs = true;
Vector2 lastHeldDirection;

// - get any needed references
private void Awake()
{
    m_Body2D = GetComponent<Rigidbody2D>();
}

// - convert distance values into an acceleration to apply each update
void ConvertDistanceToVelocityStep()
{
    //acceleration
    timeZeroToMax = (2 * accelDistance) / (maxSpeed - 0);
    accelRatePerSec = maxSpeed / timeZeroToMax;
    //deceleration
    timeMaxToZero = (2 * stoppingDistance) / (0   maxSpeed);
    decelRatePerSec = -maxSpeed / timeMaxToZero;
}

private void Start()
{
    ConvertDistanceToVelocityStep();
    xVel = 0;
}

private void Update()
{
    // if inputs are allowed - check when horizontal buttons are pressed
    if (allowInputs)
    {
        if (Input.GetButtonDown("Horizontal"))
            inputPressed = true;
        else if (Input.GetButtonUp("Horizontal"))
            inputPressed = false;
    }
    else inputPressed = false;
}

private void FixedUpdate()
{
    // if a valid input is provided
    if (inputPressed && allowInputs)
    {
        // get direction
        InputDirection();
        // get acceleration
        Accelerate(accelRatePerSec);
        // apply acceleration in desired direction
        m_Body2D.velocity = new Vector2(lastHeldDirection.x * xVel, m_Body2D.velocity.y);
    }
    // if input no longer pressed
    else
    {
        // while still moving
        if (Mathf.Abs(m_Body2D.velocity.x) > 0.01f)
        {
            // get deceleration
            Accelerate(decelRatePerSec);
            // apply deceleration in last held direction
            m_Body2D.velocity = new Vector2(lastHeldDirection.x * xVel, m_Body2D.velocity.y);
        }
        else
            // bring x velocity to zero
            m_Body2D.velocity = new Vector2(0, m_Body2D.velocity.y);
    }
}

// calculate x velocity to move rigidbody
void Accelerate(float accelRate)
{
    xVel  = accelRate * Time.deltaTime;
    xVel = Mathf.Clamp(xVel, 0, maxSpeed);
}

Vector2 InputDirection()
{
    // get both axis of input
    float hor = Input.GetAxis("Horizontal");
    float vert = Input.GetAxis("Vertical");
    // save to vector2
    Vector2 inputDir = new Vector2(hor, vert);
    // round last held direction to whole number
    if (Mathf.Abs(inputDir.x) > 0.25f)
    {
        if (inputDir.x > 0)
            lastHeldDirection.x = 1;
        else lastHeldDirection.x = -1;
    }
    //normalize diagonal inputs
    if (inputDir.magnitude > 1)
        inputDir.Normalize();
    // return input direction
    return inputDir;
}

}

  • Related