Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programming a smooth change of thrust from current velocity vector to a target vector

TL;dr: "I am not sure how to calculate a smooth transition of thrust between one vector and another."

I am programming a simple game where an enemy chases after the player in an open space (no walls). I was calculating the enemy's x & y velocities independently, accelerating them if they were taking them in the direction of the player and quickly slowing them if they were going the wrong way (e.g. EnemyVelocity.x > 0 & player.x < enemy.x, then EnemyVelocity.x - 2.)

While the gameplay is decently fun trying to dodge the enemy, it is my desire to have the enemy behave using proper physics. What I am currently doing is have the enemy set their thrust (think a spaceship) based on the angle between them and the player, and have their thrust accelerate up to a max speed (calculating side c of the EnemyVelocity triangle). Once that happens, I'm not sure the best way to have the thrust adjust itself. If I leave no max speed, the enemy accelerates nicely but will easily overshoot the player, and then take a long time to get enough momentum heading back in the player's direction.

What I'd like to happen is have the enemy constantly adjust their velocity on the way to the player, targeting wherever they are at (I don't want them to predict where you will be). Then, when they miss the player, I'd like for the same thrust & acceleration formulas to readjust their velocity and send them back at the player.

I'm thinking this will involve two vectors: one where the enemy is currently traveling, and one where the enemy wants to travel (the vector that will take them straight to the player). I am not sure how to calculate a smooth transition of thrust between one vector and another.

Any tips, formulas or questions will be much appreciated! Thank you Stack Overflow.

like image 486
user128526 Avatar asked Sep 02 '09 01:09

user128526


2 Answers

It all comes back to Newton's equations:

F = m * a
s = s_o + v * t + a * t^2 / 2
v = v_o + a * t

In this case F is the force (thrust), a is the acceleration, and m is the mass of the ship. s is the current location, s_o is the original location, v is the velocity, and t is the current time.

Of course this is along a straight line, so if you want to convert to two or three dimensions you'll have to do some math. F, s, v, and a are all vectors, meaning that their direction is equally important. Technically t is also a vector, but since time generally only goes one direction, we don't have to worry about that.

2d:
F^2 = F_x^2 + F_y^2 (use Pythagorean theorem to split force into components)
F_x = m * a_x
F_y = m * a_y
s_x = s_o_x + v_x * t + a_x * t^2 / 2
s_y = s_o_y + v_y * t + a_y * t^2 / 2
v_x = v_o_x + a_x * t
v_y = v_o_y + a_y * t

3d:
F^2 = F_x^2 + F_y^2 + F_z^2 (surprisingly, this works)
F_x = m * a_x
F_y = m * a_y
F_z = m * a_z
s_x = s_o_x + v_x * t + a_x * t^2 / 2
s_y = s_o_y + v_y * t + a_y * t^2 / 2
s_z = s_o_z + v_z * t + a_z * t^2 / 2
v_x = v_o_x + a_x * t
v_y = v_o_y + a_y * t
v_z = v_o_z + a_z * t

Now to adjust your velocity to the direction of the player, you've got a fixed total force (F) in order to change your current velocity toward the player. In physics things don't happen instantaneously, but your goal should be to minimize the time in which the change happens ('t').

This gives you an equation in terms of your current location ((s_o_x,s_o_y) or (s_o_x,s_o_y,s_o_z)) and your opponent's current location or your target location ((s_x,s_y) or (s_x,s_y,s_z)), for your target velocity (ignores acceleration).

v_x = (s_x - s_o_x) / t
v_y = (s_y - s_o_y) / t

v_x = (s_x - s_o_x) / t
v_y = (s_y - s_o_y) / t
v_z = (s_z - z_o_y) / t

We can substitute this for our other equation:

(s_x - s_o_x) / t = v_o_x + a_x * t
(s_y - s_o_y) / t = v_o_y + a_y * t

(s_x - s_o_x) / t = v_o_x + a_x * t
(s_y - s_o_y) / t = v_o_y + a_y * t
(s_z - z_o_y) / t = v_o_z + a_z * t

We then solve for the acceleration (this is related to the force, which is what we are trying to calculate).

(s_x - s_o_x) / t^2 - v_o_x / t = a_x
(s_y - s_o_y) / t^2 - v_o_y / t = a_y

(s_x - s_o_x) / t^2 - v_o_x / t = a_x
(s_y - s_o_y) / t^2 - v_o_y / t = a_y
(s_z - z_o_y) / t^2 - v_o_z / t = a_z

Plug this into the force equation:

F_x = m * (s_x - s_o_x) / t^2 - m * v_o_x / t
F_y = m * (s_y - s_o_y) / t^2 - m * v_o_y / t

F_x = m * (s_x - s_o_x) / t^2 - m * v_o_x / t
F_y = m * (s_y - s_o_y) / t^2 - m * v_o_y / t
F_z = m * (s_z - z_o_y) / t^2 - m * v_o_z / t

Now solve for t:

t = (-m * v_o_x +/- sqrt(m^2 * v_o_x^2 - 4 * F_x * m * (s_x - s_o_x))) / 2 / F_x
t = (-m * v_o_y +/- sqrt(m^2 * v_o_y^2 - 4 * F_y * m * (s_y - s_o_y))) / 2 / F_y

t = (-m * v_o_x +/- sqrt(m^2 * v_o_x^2 - 4 * F_x * m * (s_x - s_o_x))) / 2 / F_x
t = (-m * v_o_y +/- sqrt(m^2 * v_o_y^2 - 4 * F_y * m * (s_y - s_o_y))) / 2 / F_y
t = (-m * v_o_z +/- sqrt(m^2 * v_o_z^2 - 4 * F_z * m * (s_z - s_o_z))) / 2 / F_z

The times should converge, so the times will be equal! This gives us a system of equations for each coordinate (plane and sphere). Note that there are multiple possible values, but some involve imaginary numbers so you'll have to eliminate those solutions:

(-m * v_o_x +/- sqrt(m^2 * v_o_x^2 - 4 * F_x * m * (s_x - s_o_x))) / 2 / F_x
= (-m * v_o_y +/- sqrt(m^2 * v_o_y^2 - 4 * F_y * m * (s_y - s_o_y))) / 2 / F_y
F^2 = F_x^2 + F_y^2

(-m * v_o_x +/- sqrt(m^2 * v_o_x^2 - 4 * F_x * m * (s_x - s_o_x))) / 2 / F_x
= (-m * v_o_y +/- sqrt(m^2 * v_o_y^2 - 4 * F_y * m * (s_y - s_o_y))) / 2 / F_y
= (-m * v_o_z +/- sqrt(m^2 * v_o_z^2 - 4 * F_z * m * (s_z - s_o_z))) / 2 / F_z
F^2 = F_x^2 + F_y^2 + F_z^2

Solve for the (F_x,F_y) or (F_x,F_y,F_z) coordinates and you've got the force you need.

Let me know if you have any questions or if you find mistakes in my math.

like image 128
Imagist Avatar answered Nov 02 '22 17:11

Imagist


You may get the effect you want by ensuring a smooth change in velocity, rather than thrust. That way, if the enemy overshoots the player, it can immediately reverse its acceleration, which will slow it down and eventually reverse its direction of travel.

You can accomplish this by changing the velocity during each iteration, by a small amount that's based on the distance from the enemy to the player:

while (game_in_progress)
{
    // Distance from enemy to player.  The larger the
    // distance, the greater the acceleration will be.
    delta.x = player.x - enemy.x
    delta.y = player.y - enemy.y

    // Accelerate by changing velocity based on distance,
    // where 'scale' is sufficiently small. (Limit v to
    // some maximum if you choose; likely to be unnecessary.)
    v.x += delta.x * scale
    v.y += delta.y * scale

    // Update the enemy's position.
    enemy.x += v.x
    enemy.y += v.y
}

By calculating the x and y values independently, you can save yourself the headache of dealing with vectors, angles, and simultaneous equiations.

Similarly, by recognizing that acceleration (thrust) is simply a change in velocity, which in turn is a change in position, you can create a discrete-time simulation using only simple algebra instead of calculus.

Have fun!

like image 23
Adam Liss Avatar answered Nov 02 '22 19:11

Adam Liss