Ok, so I think there are 2 common ways to control the angle and corresponding ways to stop overshoot when the angle reaches the desired value. The first is a more realistic acceleration controller that will have a nice ease-in-out effect, and the second is a quick solution with constant velocity that generally only works in software.
1. PID (Proportional, Integral, Derivative) controller. Real physical control systems use these kinds of controllers because you can’t just magically set a value. Instead, to turn a ship you affect angular acceleration by applying a force – usually a higher force the more you have to turn, so it doesn’t take too long. Thus your applied force is proportional to the difference in angle. To avoid overshoot, you can subtract a value that scales with your angular velocity, or the derivative of your motion, to counteract the inertia. Now in real systems there’s always friction which will cause a steady state error as you get really close to your target but can’t overcome the last bit of friction. So real controllers also add a value that scales with the integral – or the accumulation of error. Since you probably aren’t dealing with friction when turning your ships, you could use a PD controller and it would look quite realistic. Here is a sample:
double w = 0.075;
double kp = w * w;
double kd = 2 * w;
double angularAcc = kp * (radians - currentRadians) - kd * (currentRadians - lastRadians);
angularVel += angularAcc;
lastRadians = currentRadians;
currentRadians += angularVel;
// Draw sprite with angle "currentRadians"
Using kp = w^2
and kd = 2w
is called “critical damping” and is generally the fastest way to get to the target without overshooting. I got w = 0.075
from testing, and it results in taking about 1.5 seconds to complete a turn (assuming 60 fps). Increasing kp
will make it go faster, but overshoot, and increasing kd
will add damping and reduce overshoot. Here’s a website where you can see a live example.
2. The other option is much simpler, but may look less realistic. The idea is that you can just compare your current angle and your previous angle to your desired angle every frame to see if you turned too far. After turning the ship as you are now, check if your new angle passed the desired angle: (prev < desired && desired < curr) || (prev > desired && desired > curr)
. If so then just set the angle to be the desired angle.
One last thing you should consider is that since angles can wrap around, you should compare abs(target - current)
, abs((target + 2 * Math.PI) - current)
, and abs((target - 2 * Math.PI) - current)
to see which target angle is the closest, and use that as your desired angle. For example if you’re going from 5 degrees to 350 degrees, you should instead target -10 degrees because turning -15 is much faster than turning +345.