I realise this is very similar to other 2D movement questions already posted but cant find a solution that fits the behaviour I'm looking for, so I'm posting in the hope that someone more knowledgeable than myself can give me a nudge in the right direction.

Here goes:

I have an object that I am moving round a canvas. This object always faces the cursor position and accelerates to a maximum speed when the mouse is held. now the issue I have is when implementing the movement of the object. I want the object to slowly loose velocity when no longer accelerating and to slowly change direction based on the acceleration and direction (imagine a boat going at top speed then hitting reverse instantly, I want it to slow to a stop before going in the other direction) so far I have logic along these lines in my code (simplified for ease of debugging, swapped in constant values instead of references):

velocityCalculation() {
    if (_thrust> 0) {
        _velocity.x += mouseMagnitude.x * (_thrust * 0.1);
        _velocity.y += -mouseMagnitude.y * (_thrust * 0.1);

    if (_velocity.x != 0)
        _velocity.x += -Math.sign(_velocity.x) * 2;

        //cap velocity
        if (_velocity.x > 100) _velocity.x = 100;
        else if (_velocity.x < -100) _velocity.x = -100;
        if (_velocity.x < 2 && _velocity.x > -2) _velocity.x = 0;
    if (_velocity.y != 0) {
        _velocity.y += -Math.sign(_velocity.y) * 2;

        //cap velocity
        if (_velocity.y > 100) _velocity.y = 100;
        else if (_velocity.y < -100) _velocity.y = -100;
        if (_velocity.y < 2 && _velocity.y > -2) _velocity.y = 0;

where mouseMagnitude is a normalised vector pointing towards the cursor, thrust is the current acceleration (probably named badly, I have no clue the correct terminology). Now if I place the cursor just off axis the object begins accelerating and moving normally however after reaching the correct direction of the mouse(shown as white arrow) it continues accelerating until its travelling diagonally (shown as red, objects velocity = 100x,100y) instead of in the direction of the mouse. I'm sure I'm missing something simple but how do I make the object continue travelling in the direction of the mouse instead of diagonally?

debug output of my code


1 Answer 1


Your issue is that you're clamping the x and y components of the velocity independently. So once the x value hits 100, it saturates. Meanwhile, if the mouse is still above the object, the y value keeps accelerating, until it also hits 100 and saturates.

What you've done is clamp the velocity vector to a square shape. That means there are only 8 stable states for the velocity when it's accelerating:

  • the 4 corners of the square, which are stable equilibria: once the velocity gets into one of theses 45-degree corners of (±100, ±100), as long as the acceleration vector is anywhere in a 90-degree span, the velocity will stay pinned in that corner.

  • the centers of the 4 edges of the square, which are unstable equilibria: if the acceleration has even a tiny magnitude in the perpendicular direction, the velocity will slide along the edge of the square to the corresponding corner.

What you really want to do is clamp the velocity to a circle:

let speed = Math.sqrt(_velocity.x * _velocity.x + _velocity.y * _velocity.y);

let reducedSpeed = Math.max(0, Math.min(100, speed - 2));

_velocity.x *= reducedSpeed / speed;
_velocity.y *= reducedSpeed / speed;

By applying our limits and deceleration to the magnitude of the vector, instead of to each component separately, we ensure the effect is radially-symmetric, and affects vectors in all directions equally. No more corners in our clamping function.

Also, bonus: less code, with more sensible behaviour near zero. 😁

  • \$\begingroup\$ I like that solution, it seems very clean and concise, is this a replacement for the entire function? (as I don't see the thrust or mouse position taken into account) or just the code inside of the second two conditional statements? I kept the code in conditional statements and clamped values near zero so I could come to a 'dead stop' and have no overhead of calculating velocity when at rest or not accelerating \$\endgroup\$
    – Gavin Hunt
    Commented May 4, 2022 at 12:52
  • \$\begingroup\$ It sounds to me like you've already reasoned the answer to that question for yourself. \$\endgroup\$
    – DMGregory
    Commented May 4, 2022 at 12:53
  • \$\begingroup\$ That's brilliant, it acts just as I was looking for it to with a lot less code. Cant say I've seen a cleaner solution to this than yours so far \$\endgroup\$
    – Gavin Hunt
    Commented May 4, 2022 at 13:05

You must log in to answer this question.

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