# Automatically triggering standard spaceship controls to stop its motion

I have been working on a 2D top-down space strategy/shooting game. Right now it is only in the prototyping stage (I have gotten basic movement) but now I am trying to write a function that will stop the ship based on it's velocity. This is being written in Lua, using the Love2D engine.

My code is as follows (note- object.dx is the x-velocity, object.dy is the y-velocity, object.acc is the acceleration, and object.r is the rotation in radians):

function stopMoving(object, dt)
local targetr = math.atan2(object.dy, object.dx)
if targetr == object.r + math.pi then
local currentspeed = math.sqrt(object.dx*object.dx+object.dy*object.dy)
if currentspeed ~= 0 then
object.dx = object.dx + object.acc*dt*math.cos(object.r)
object.dy = object.dy + object.acc*dt*math.sin(object.r)
end
else
if (targetr - object.r) >= math.pi then
object.r = object.r - object.turnspeed*dt
else
object.r = object.r + object.turnspeed*dt
end
end
end


It is implemented in the update function as:

if love.keyboard.isDown("backspace") then
stopMoving(player, dt)
end


The problem is that when I am holding down backspace, it spins the player clockwise (though I am trying to have it go the direction that would be the most efficient at getting to the angle it would have to be) and then it never starts to accelerate the player in the direction opposite to it's velocity.

What should I change in this code to get that to work?

EDIT :

I'm not trying to just stop the player in place, I'm trying to get it to use it's normal commands to neutralize it's existing velocity.

I also changed math.atan to math.atan2, apparently it's better. I noticed no difference when running it, though.

• Why not just player.dx=0 player.dy=0? – Anko Dec 31 '12 at 9:03
• Because I'm not trying to tell it to just stop in place, I want it to use the ship's normal abilities in movement to cancel out it's motion. I'm going to edit the question to reflect this. – PixelArtDragon Dec 31 '12 at 15:46
• What are the "normal abilities" of the ship? – Anko Jan 3 '13 at 1:16
• Turning left and right, accelerating and decelerating (accelerates in the opposite direction). Decelerating is half as powerful as accelerating. – PixelArtDragon Jan 3 '13 at 2:35
• Ok. Next, I don't understand your rotation comparisons: In the first (targetr == object.r + math.pi), why are you adding pi? In the second ((targetr - object.r) >= 2*math.pi), why are you comparing the value to a full circle (2*pi) instead of 0? Consider commenting your code. It's a good habit to get into, because it makes your intention clearer! – Anko Jan 3 '13 at 5:06

## 3 Answers

I would strenuously urge you to give the player's ship retro-rockets to be used during braking.

You really need to do that, because the alternative is for the computer to take over and automatically turn the ship to face away from its direction of travel whenever it decides that the player should stop. That'll be intensely annoying to the player, having their ship start turning every time they take their fingers off the controls.

But with that said, the code to do so (say, for AI-controlled ships) would basically consist of a two-stage process; first the ship would try to turn to face away from its direction of travel. And once it's facing far enough away from its direction-of-travel, it thrusts to counteract its velocity, thrusting harder the more directly away from its direction of travel it might be. Commented pseudocode (which assumes that angles are expressed in radians) follows:

float pi = 3.141593;
float two_pi = pi * 2.0;
float half_pi = pi / 2.0;

void do_stop()
{
// These are the values which we'll calculate.  We can then
// use these values as if they were controller inputs.

outval float steer, thrust;

float angle_of_travel = atan2f(object.dy,object.dx);
float angle_of_desired_braking = atan2f(-object.dy,-object.dx);
float angle_of_facing = object.r;

// Now calculate how many radians between the direction we're
// currently facing and the direction we want to face for braking.

float delta_angle = angle_of_desired_braking - angle_of_facing;

// now normalise delta_angle back into [-pi .. pi], since the
// above subtraction could have resulted in a value anywhere
// in [-two_pi .. two_pi].

delta_angle = fmod(delta_angle, two_pi) - pi;

// use a multiplication factor to control steering strength.

const float c_steering_sensitivity = 0.5;
steer = clamp( delta_angle * c_steering_sensitivity, -1.0, 1.0 );

// Note that if you have rotational inertia, you'll need to
// further modify the steering value here, to try to slow down
// the rotation rate as you approach the desired orientation.
// The sample code for this question doesn't imply the presence
// of rotational inertia, so I'm leaving that out of this code.

// Now if we're reasonably close to pointing the right
// direction, let's start thrusting.  c_thrust_threshold
// states how close to correctly-aimed we need to be before
// we'll start thrusting.

const float c_thrust_threshold = half_pi;

if ( fabs(delta_angle) <  c_thrust_threshold )
{
// modulate how hard we thrust based on how close we
// are to pointing in the correct direction.

thrust = 1.0 - fabs(delta_angle / c_thrust_threshold);
}
else
{
// don't thrust if we aren't pointing close enough
// to the desired direction for slowing down.

thrust = 0.0;
}
}


If I could get it, the matter is that when you press the button, the speed of the object starts decreasing until it stops. You wantt to create a "break distance" (a distance between the X,Y place of pressing the button and the X,Y place of the place where the object stops) to create some kind of realistic breaking.

Don't really get the stuff doing with degrees, though.

So here's my suggestion for solving this problem: simply create a function (let's call it stopMoving as you did too) that runs an iteration that closes dx and dy to 0, and call it love.update.

Something like for i = object.dx, 0, -1 do in case of dx>0 and same code without -1 in case of dx<0. And same for dy.

If you want to be precise, try to iterate with the greatest common divisor of x and y. -1/+1 is also a common divisor for any whole x and y, but that may result a very small speed decreasing.

All above are just ideas, but may be worth a try. Good luck!

The problem you're running into is the fact that when your player has stopped, there's no angle to work with so the game can't know which way to move backwards.

You either need to add one more variable that keeps track of the angle your dx/dy variables are on or two more variables to store dx/dy, leaving them alone when your real dx/dy variables don't change (and they can be safely left when their magnitude alone changes), but updating them when their direction changes.
(I think it would suffice to keep your "backup" dx/dy variables updated everywhere else save for your stopMoving() function)

• This isn't the issue. If the player had stopped, it would have been caught by the "if currentspeed ~= 0". That means it wouldn't continue after the player has stopped. My issue is that it never even stops turning, and that no matter what the angle is, it always goes in the same direction. – PixelArtDragon Jan 1 '13 at 3:40
• Hmm. Once I have a better handle on your problem, I'll replace my answer with something better suited. Are you sure you're calculating the angle object.r correctly? – Raceimaztion Jan 1 '13 at 4:55
• Object.r is fine, sonce I've been able to move the player around using normal controls just fine. It's not being calculated, it's being called. – PixelArtDragon Jan 1 '13 at 14:18
• Why are you performing this exacting comparison? `targetr == object.r + 2*math.pi' If floating-point values are compared, more often than not they won't be equal. – Raceimaztion Jan 1 '13 at 21:50
• So you'd suggest something to test if they are within something like 1% of each other? – PixelArtDragon Jan 2 '13 at 3:57