1
\$\begingroup\$

I'm creating a 3D twin-stick tank game in Godot with C#. I have created the tank movement and rotation, separate of its turret, and it seems to be working properly.

The issue: I want the body of the tank to rotate in the direction which is closest to the tank's current rotation, and more specifically, even when that is backwards relative to the body of the tank itself. Currently, the "animation" is flipping around a full 180 degrees (or Pi radians), when suddenly moving the tank from left to right, or from up to down.

The following snippet is from my Rotator Node3D class:

public override void _PhysicsProcess(double delta)
{
    // TODO: Implement driving backwards, because tanks shouldn't suddenly rotate all the way around.

    var angle = _input.AngleTo(Vector2.Up);
    Rotation = Rotation with { Y = Mathf.LerpAngle(Rotation.Y, angle, _rotationSpeed * (float)delta) };
}

The input vector is a Vector2D that is based on the left-stick, or WASD movement.

For completeness, but not very relevant, here's the movement (from my Mover Node3D class) logic itself:

public override void _PhysicsProcess(double delta)
{
    Position += new Vector3(_input.X, 0, _input.Y) * _movementSpeed * (float)delta;
}

This GIF showcases the issue, I don't want the tank to suddenly turn around, but simply drive backwards. Do you have any pointers (or even an implementation) to help me figure this out? tank issue

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Someone on the Godot Discord helped me out, and told me to store the previous input vector and compare it to the current one. Then, if they are pointing in opposite directions (dot product < 0), invert the resulting vector.

Here is the resulting implementation:

private Vector2 _input;
private Vector2 _previousInput;
private bool _driveBackwards;

public override void _PhysicsProcess(double delta)
{
    if (_input.Dot(_previousInput) < 0)
    {
        _driveBackwards = !_driveBackwards;
    }

    var angle = _input.AngleTo(_driveBackwards ? Vector2.Down : Vector2.Up);
    Rotation = Rotation with { Y = Mathf.LerpAngle(Rotation.Y, angle, _rotationSpeed * (float)delta) };

    _previousInput = _input;
}

As a bonus, I made the rotation linear. Also, I limited the final rotation to tau (the full unit circle), because it kept increasing when rotating in one direction:

private static double LerpLimitedLinearAngle(double from, double to, double weight)
{
    const double destinationWeight = 1d;
    var destinationAngle = Mathf.LerpAngle(from, to, destinationWeight);
    var linearAngle = Mathf.Clamp(destinationAngle, from - weight, from + weight);
    var limitedAngle = linearAngle % Mathf.Tau;
    return limitedAngle;
}

Which is called with:

var newRotation = LerpLimitedLinearAngle(Rotation.Y, angle, _rotationSpeed * delta);

Rotation = Rotation with { Y = (float)newRotation };

Now it looks like this: tank movement fix

\$\endgroup\$

You must log in to answer this question.

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