Archived

This topic is now archived and is closed to further replies.

Finding the angle between two sprites

This topic is 5601 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

hello there. I want my enemy to chase down the player by finding the angle between the two and setting its vector to that angle. Here's what I am doing:
  
float opp = ( p_playerY - enemyY );
float adj = ( p_playerX - enemyX  );

float angle = atan( ( opp / adj ) ) * 180.0f / 3.14f;
   
This should give me the angle right? well, the enemy doesn't move toward the player like he should. Can someone help or give another solution to this problem? Thanks. [edited by - randomDecay on August 15, 2002 2:06:46 PM] [edited by - randomDecay on August 15, 2002 2:07:12 PM]

Share this post


Link to post
Share on other sites
Off the top of my head I''d say it''s something to with operator precedence. Try using this


  
float opp = ( p_playerY - enemyY );
float adj = ( p_playerX - enemyX );

float angle = (atan( ( opp / adj ) ) * 180.0f) / 3.14f;

Share this post


Link to post
Share on other sites
atan2( y, x ) yields the angle.
Alternativelly, you can use do this:

dir_x = ai_x - player_x
dir_y = ai_y - player_y;

dir_x & dir_y are now the direction vectors the ai should travel along to reach the player.

ie ai_x + dir_x equals player_x

To move the ai one step towards the player over time ( dt ), then do something like

ai_x += dir_x * dt;
ai_y += dir_y * dt;


These operations would all be easier if you use a vector library.

Share this post


Link to post
Share on other sites
maxsteel -- your second solution will work thanks, but I have one question for you about it...

let''s say enemy XY = (96, 96) player XY = ( 416, 288 )

dirx = 96 - 416 = -320
diry = 96 - 288 = -192

you said enemyX + dirX = playerX which is not true :
96 + -320 = -224

On a computer screen the enemy should be going SOUTH EAST with the coordinates above. but if you work out your solution, ie --

enemyX += ( dirX * dt ) (let''s say dt = 1/2 )

enemyX = 96 + ( -320 * .5 ) = -64 ?? Shouldn''t the enemy be going EAST in the x direction?

Thanks and please tell me if I understand what I am doing

Share this post


Link to post
Share on other sites
sweet, it works perfectly! One thign I am concerned about though: all my movement for enemies is INDEPENDENT of the frame rate. Is this method dependent on the framerate? I can''t seem to get that atan2 method working, so I guess I am going to have to use this.

Thanks maxsteel and everyone else who helped!

Share this post


Link to post
Share on other sites
if you divide dir_x and dir_y by sqrt(dir_x^2 + dir_y^2), you will get the same values for the x and y offset that you would have gotten if your atan2 method was used, and then used sin and cos on the angle to generate an x and y offset, if that answers your question.

As for being frame rate dependent, that depends on if you base movement on the system clock (frame rate dependent), or plan to set a strict amount of movement each frame (not frame rate dependent).

if you want your movement to be independent of frame rate, you will always multiply your x and y offset values by the same value for movement.

Make sense?


[edited by - Waverider on August 15, 2002 3:43:57 PM]

Share this post


Link to post
Share on other sites
so if I always use the same dt, it will be the same offset even on two different computers with different framerates?

But the way I see it is with a higher framerate, the PursuePlayer() function will be called more more often than a lower framerate. So the enemy will move faster on some machines with higher framerates.

[edited by - randomDecay on August 15, 2002 3:49:20 PM]

Share this post


Link to post
Share on other sites
Any game that moves a set amount each frame will move faster on a higher end machine if the frame rate itself isn't controlled somehow. (EVERYTHING will move faster)

If you want to, say, limit the frame rate to 24, then you could keep the object positions from updating until 1/24 second in time passes, then allow the movement routine to execute on all the objects.

Otherwise, you can update the object positions every frame based on how much time has actually elapsed since the last frame, and the objects will move the same speed no matter what machine they are running on.

To answer your question, if you use the SAME dt every frame, a faster machine will run the movement routine more often, causing everything to go faster. So if you base your dt on how much time elapses instead of setting it the same value every frame, you will get the same movement speed on every machine, even thought the frame rate will be higher on the faster machines (IF you don't want to control the frame rate).

[edited by - Waverider on August 15, 2002 4:13:02 PM]

Share this post


Link to post
Share on other sites
OK thanks waverider -- here''s what I came up with (looks very good to me, what do you think?)


  
DWORD time = CGameTime::Elapsed();

float dirX = (p_playerX - GetXPos());
float dirY = (p_playerY - GetYPos());

float dt = ( static_cast<float>(time) - static_cast<float>(GetLastTimeMoved()) ) / 1000.0f;

SetXPos( GetXPos() + ( dirX * dt ) );
SetYPos( GetYPos() + ( dirY * dt ) );

SetLastTimeMoved( time );

Share this post


Link to post
Share on other sites
Just have a function that gets the time elapsed between each frame in milliseconds. You shouldn't limit your framerates just to sync movement, it'll never work unless you set a really low limit.

Let's say you want to move the vehicle at a rate of 10 units per second. You have the DeltaY and DeltaX, the difference in x and y between the two points. Basically, you have a 2D vector. Normalize this vector to get a unit vector. Now you have directional vector of length one. Since you want to move the vehicle 10 units per second, you want to make sure that per 1000 ms, it moves 10 units. So per N ms, it moves N*10/1000 = N*1/100 of a unit. Let's say 30 ms has elapsed since the last frame. You would multiply the x and y offsets by 30*.01 = .3. Since the x and y offsets form a unit vector(vector length one), you will effectively move the vehicle exactly .3 units over the course of 30 ms. If the next time elapsed is 60 ms, you would make up for the lost time and move .6 and in the course of one second, you will have moved exactly 10 units. Sound good?

Btw, with the angles, if you wanted to figure out which direction the vehicle were facing(i.e. for sprite rendering) you would still need angles. To get the angle from A to B you have to make DeltaX = Bx - Ax, and DeltaY = By - Ay(Not Ax - Bx or Ay - By). Otherwise, the angle that comes out will be opposite of what you want(This is the also an answer to the other question of why the directional offsets of enemyX and Y came out wrong at first).Use atan2(x,y)//not y,x, to get the angle in radians. To convert to degrees, multiply this by 57.295f(approx.). Now, the resulting angle CAN be negative! So you'll have to do this:
if(ResultAngle < 0)ResultAngle += 360;
This should get you the precise angle to turn to.
If the turn is delayed, meaning, its not an instantaneous turn, you should make it so that the vehicle takes the shortest time to turn, and not pull a near 360 to head in a certain direction.

-=~''^''~=-.,_Wicked_,.-=~''^''~=-

[edited by - Wicked Ewok on August 15, 2002 4:48:43 PM]

Share this post


Link to post
Share on other sites
The problem I see with that code is, assuming GetXPos() and GetYPos() return the position of the enemy, then the overall offsets from player could be large or small, affecting the movment rate. Which I assume is not what you want.

Directly after you calculate dirX and dirY, do this:

float denom = sqrt(dirX * dirX + dirY * dirY );
dirX = dirX / denom;
dirY = dirY / denom;

That way dirX and dirY represent a direction vector (unit length 1) instead of being the actual offset from the enemy to the player. And you will get a consistent movement rate.

Watch out for the enemy being right on top of the player though, that would make denom = 0 and you''ll get a division error!

Share this post


Link to post
Share on other sites
Here's what I have now --


    
DWORD time = CGameTime::Elapsed();

float dirX = (p_playerX - GetXPos());
float dirY = (p_playerY - GetYPos());

float denom = sqrt( (dirX * dirX) + (dirY * dirY) );
dirX = dirX / denom;
dirY = dirY / denom;


float dt = ( static_cast<float>(time) - static_cast<float>(GetLastTimeMoved()) ) / 10.0f;

SetXPos( GetXPos() + ( dirX * dt ) );
SetYPos( GetYPos() + ( dirY * dt ) );

SetLastTimeMoved( time );


If dirX and dirY are 1 why even include them? I changed 1000.0f to 10.0f because he was moving ultra slow until I changed it.

So, finally, this code will make the enemy move the exact same rate on totally different machines and the enemy won't be moving at different speeds on the same machines like my code did earlier.

Am I right?

EDIT -- OK I left out the divide by zero error check, but other than that, that's what I have.

[edited by - randomDecay on August 15, 2002 5:01:06 PM]

Share this post


Link to post
Share on other sites
Should work. Will you need the actual angle later ro render the sprites facing the right directions? Because if you''re going to get the angle of the sprites, there''s a faster way to move, using that angle, than to have to seperately calculate the directional vector with a sqrt formula. Just this:
Xoffset = SIN[ int(angle) ]*Speed*Time_Elapsed;
Yoffset = COS[ int(angle) ]*Speed*Time_Elapsed;
where SIN[] and COS[] are prebuilt arrays of 360 size of sin and cos values. Since both spew out values between -1 and 1, you will find that they work very well with what you need to do. So if you already need to find the angle for sprite rendering. Just use that method.

By the way, DirX and DirY being a unit vector doesn''t mean that their values are one. It means that the hypotenuse formed by X and Y was the sides is equal to 1. DirX and DirY will be < 1, but they will still point somewhere.

-=~''''^''''~=-.,_Wicked_,.-=~''''^''''~=-

Share this post


Link to post
Share on other sites
dirX and dirY aren't 1. dirX^2 + dirY^2 is 1. The VECTOR is length 1.

The only other possible problem I see is that dt must be reliably set to the amount of elapsed time between frames (I'm not sure if your calculation for dt accomplishes that), and maybe the 1000.0f could be changed to a different constant, depending on how fast you would like everything to move.

If CGameTime::Elapsed() returns the amount of time passed since last frame, you could just set dt to CGameTime::Elapsed() / 1000.0f, and do away with the SetLastTimeMoved() call.


[edited by - Waverider on August 15, 2002 5:23:11 PM]

Share this post


Link to post
Share on other sites
Kind of redundant now, but atan2 does work, but you have to negate the result:

angle = -(atan2(var,var))*(180/PI)

I had the exact same problem when not using the negated atan2 and it drove me nuts while everyone threw a multitude of different and usually much more long winded methods. AFAIK the -(atan2()) method is one of the fastest angle determination methods available.

Share this post


Link to post
Share on other sites