2
\$\begingroup\$

I can manage to achieve this but the code seems to be much longer than it should be. Velocity is given an initial value, either positive or negative and then is gradually reduced until it is 0,0.

Vector2 velocity = new Vector2(4,-3);
private float decSpeed = .2f;

public void update(float delta) {
velocity.x = setVel(velocity.x);
velocity.y = setVel(velocity.y);
}


private float setVel(float vel) {
    if (vel > 0) {
        if (vel - decSpeed <= 0)
            vel = 0;
        else
            vel -= decSpeed;
    } else if (vel < 0)
        if (vel + decSpeed >= 0)
            vel = 0;
        else
            vel += decSpeed;
    return vel;
}

I simply want to decrease the vector value for both x and y by a fixed amount, but of course you have to take into account that the vector can be either positive or negative.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Does the decrease have to be a fixed amount? Many physics engines implement this with a multiplier slightly less than 1 instead. That has the effect of bleeding more speed off fast-moving objects, then lightening up on the brakes for a gradual stop for more slow-moving objects - an exponential ease-out curve. It's a very simple approximation of how fast-moving objects face more air resistance (neglecting aerodynamic differences). \$\endgroup\$
    – DMGregory
    Commented Jul 13, 2020 at 15:12
  • \$\begingroup\$ @DMGregory♦ I guess it doesn't necessarily have to be fixed, would that make it easier to implement than what I have here? \$\endgroup\$
    – Hasen
    Commented Jul 13, 2020 at 16:19

2 Answers 2

3
\$\begingroup\$

If you don't need the deceleration to be the same fixed amount each frame, you can instead apply a "drag multiplier". This is used in many games as a crude approximation of air resistance, where the faster you go, the more air you have to push through per unit of time, increasing the friction at high speeds.

public float velocityMultiplier = 0.95f;

public void update() {
    velocity.x *= velocityMultiplier;
    velocity.y *= velocityMultiplier;
}

This reduces the magnitude of your velocity vector while preserving its direction.

If you want to adjust the amount of braking to match your timestep, you need to compute an updated multiplier:

(I've also included a clamp-to-zero if you fall below a minimum speed constant)

public void update(float delta) {

    float multiplier = Math.pow(velocityMultiplier, delta * 30.0f);

    if (velocity.x * velocity.x + velocity.y * velocity.y < MIN_SPEED * MIN_SPEED)
        multiplier = 0f;

    velocity.x *= multiplier;
    velocity.y *= multiplier;
}

This will make your drag occur at the same rate as it would at a fixed 30 FPS framerate with your current velocityMuliplier setting, but adjusted to handle shorter or longer frames too.

(The * 30.0f above just sets the "reference framerate" we're using to measure our velocityMultiplier. So with the values above, we lose 5% of our velocity per 30th of a second)

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Wouldn't it never arrive at 0,0 though? \$\endgroup\$
    – Hasen
    Commented Jul 13, 2020 at 17:30
  • \$\begingroup\$ In infinite-precision real numbers, no, it will never reach zero. In floating point, it will eventually get so small that adding it to your position no longer changes your position, which is close enough to zero for our purposes. ;) Many game physics engines will also implement a minimum velocity, where once the magnitude drops below this value, it's clamped to zero to damp small vibrations and let objects go to sleep for efficiency. \$\endgroup\$
    – DMGregory
    Commented Jul 13, 2020 at 17:34
  • \$\begingroup\$ OK so you'd need to add something like if (Math.abs(velocity.x) < .2f) etc checks in there? \$\endgroup\$
    – Hasen
    Commented Jul 13, 2020 at 17:35
  • \$\begingroup\$ No, more like if( velocity.x * velocity.x + velocity.y * velocity.y < MIN_VELOCITY * MIN_VELOCITY) \$\endgroup\$
    – DMGregory
    Commented Jul 13, 2020 at 17:37
  • \$\begingroup\$ Ok that's interesting - what's the benefit of doing it that way - squared is faster? Do you want to add that into your answer? Cos the question stipulates arriving at 0,0. Also, for a fixed decrease value I take it my original way was a good way to do it? \$\endgroup\$
    – Hasen
    Commented Jul 13, 2020 at 17:39
0
\$\begingroup\$

If you want deceleration to be linear, you can subtract a fixed amount from the length of the vector, or set the vector to zero if it below the step size. So you might have something like:

float len = velocity.length();
if (len < difference) {
    velocity.x = 0;
    velocity.y = 0;
} else {
    float scale = difference / len;
    velocity.x -= velocity.x * scale;
    velocity.y -= velocity.y * scale;
}

This is slower than DMGregory's answer because of the square root hidden in the length function, so if you don't about keeping the deceleration constant his might be a better fit.

You might choose to use the Fast Inverse Square Root to speed things up, getting something like

float sqlen = velocity.x * velocity.x + velocity.y * velocity.y;
if (sqlen < difference * difference) {
    velocity.x = 0;
    velocity.y = 0;
} else {
    float scale = fastInvSqrt(sqlen) * difference;
    velocity.x -= velocity.x * scale;
    velocity.y -= velocity.y * scale;
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Comments are not for extended discussion; this conversation has been moved to chat. \$\endgroup\$
    – DMGregory
    Commented Jul 26, 2020 at 19:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .