[Tutorial] "Perfect" Following
Author 
Message 
The_Bean

Posted: Tue Mar 25, 2008 9:12 pm Post subject: [Tutorial] "Perfect" Following 


After watching classmates make countless game where you are being chased by something and have to shoot at it, and my recent Geometry Wars game, I have decided to show you how to make those enemies follow perfectly.
Warning: Math Ahead
It is advised to have Grade 11 academic math before attempting to understand this, but if you think you can do it, go ahead.
Part: 1  What to get away from!
We all know how to make a ball bounce off the wall. Now people often apply the same idea in trying to get an enemy to follow you or your mouse around the screen. This is really primitive, but in tile based games works just fine. But we're not here to talk about those.
I find alot of people using the simple if my x position is greater then my enemies then my enemies += their speed same thing with the y and in the opposite direction. But doing this makes your enemy only able to travel: N NE E SE S SW W NW.
This is a simple program of that:
Turing: 
View.Set ("graphics:max;max,title:Prefect Following,offscreenonly,nobuttonbar")
var xm, ym, bm : int % Mouse variables
var eTaleLength : int := 5 % length of the enemys tale
var eX, eY : array 1 .. eTaleLength of int % enemy variables
var eSpeed : int := 5 % enemy speed
var eAngle : real %enemy angle of travel
eX (eTaleLength ) := Rand.Int (eTaleLength, maxx  eTaleLength ) % making the enemy appear randomly
eY (eTaleLength ) := Rand.Int (eTaleLength, maxy  eTaleLength )
for i : 1 .. eTaleLength  1 % preseting the the tale to keep from getting errors
eX (i ) := eX (eTaleLength ) % makes all start at the same spot
eY (i ) := eY (eTaleLength ) %because hes not moving yet :)
end for
proc youLose
put "He got you"
end youLose
loop
cls
Mouse.Where (xm, ym, bm )
for i : 1 .. eTaleLength  1 % making the tale move by making it = to the one infront of it
eX (i ) := eX (i + 1)
eY (i ) := eY (i + 1)
end for
if xm > eX (eTaleLength ) then
eX (eTaleLength ) + = eSpeed
elsif xm < eX (eTaleLength ) then
eX (eTaleLength )  = eSpeed
end if
if ym > eY (eTaleLength ) then
eY (eTaleLength ) + = eSpeed
elsif ym < eY (eTaleLength ) then
eY (eTaleLength )  = eSpeed
end if
for i : 1 .. eTaleLength % draws the tale and enemy
Draw.FillOval (eX (i ), eY (i ), i, i, 7)
end for
View.Update
delay (25)
exit when hasch % prematurely exits when keyboard is hit (nice for testing)
if Math.Distance (xm, ym, eX (eTaleLength ), eY (eTaleLength )) < eTaleLength then % checks to see if he ketches you (ads a game flare to it)
youLose %(Jordan YOU LOSE)
exit
end if
end loop

Now that you know what you use to be doing and what were getting away from lets get to "Perfect" Following
Part: 2  The Math
In grade 11 math you go deeper into trig and how to use it. That is exactly what we are going to do here.
(I'm going to * as the degree symbol to make it easier and shorter for me to write)
Firstly if you noticed before the N NE E... are at 0*, 45*, 90* ... so when trying to get something to follow you perfectly it needs to travel in more degrees than just those.
We need to find the degree from your enemy to you
Since were working on the x,y plane we know that tan Angle =y/x gives you the degree, but the problem is that the origin is in the bottom left and we need the origin to be over the enemy so he will (0,0) and your guy or mouse will be (x,y) so to do this we subtract the enemies x and y from your x and y so your coordinates compared to your enemy are
(mouseXenemyX,mouseYenemyY).
so now we can use tan=y/x
now to put this in turing we need the angle on one side an everything on the other.
Angle := tan inverse y/x
and in turing
Angle := arctand (y/x)
now putting this in a function
Turing: 
function setAngle (x, y : real) : real
result arctand(y/x )
end setAngle
enemyAngle := setAngle (mouseXenemyX,mouseYenemyY )

But we can't use this just yet!
First there are a couple other things that we need to take care of.
When x=0 meaning that you and the enemy are on the same x coordinate
you will have y/0 and we know that you can't divide by 0 so when need to take catch that in the function.
Turing: 
function setAngle (x, y : real) : real
if x= 0 and y> 0 then
result 90
elsif x= 0 and y< 0 then
result  90
else
result arctand(y/x )
end if
end setAngle
enemyAngle := setAngle (mouseXenemyX,mouseYenemyY )

But we still aren't done!
For some reason when y=0 turing can't decide what to do and sometimes he travels away form you and sometimes hes goes towards you. So I put the case where y=0 in also.
Turing: 
function setAngle (x, y : real) : real
if x= 0 and y> 0 then
result 90
elsif x= 0 and y< 0 then
result  90
if x> 0 and y= 0 then
result 0
elsif x< 0 and y= 0 then
result 180
else
result arctand(y/x )
end if
end setAngle
enemyAngle := setAngle (mouseXenemyX,mouseYenemyY )

And once again still not done. Doesn't this seam alot easier in class. The power of the Brain WOW!
We also have to consider that tan only gives us something between 90 and 90 so we need to see whta quadrent it is in and then make the necessary change to accomadate.
Turing: 
function setAngle (x, y : real) : real % the function that gets the angle from the enemy to your mouse
if x = 0 and y = 0 then
result 0
elsif x = 0 and y > 0 then
result 90
elsif x = 0 and y < 0 then
result 270
elsif y = 0 and x > 0 then
result 0
elsif y = 0 and x < 0 then
result 180
elsif x > 0 and y > 0 then
result arctand (y / x )
elsif x < 0 and y > 0 then
result 180 + arctand (y / x )
elsif x > 0 and y < 0 then
result 360 + arctand (y / x )
elsif x < 0 and y < 0 then
result 180 + arctand (y / x )
else
result 0
end if
end setAngle
enemyAngle := setAngle (mouseXenemyX,mouseYenemyY )

YAY
Your function for getting the angle to which you are from your enemy is now done. Now to use that angle.
If you remember form class.
You derived from that nice little triangle that to get your (x,y) from a given angle and radius you used:
x= r * cos Angle
y= r * sin Angle
Now we will use r (radius, the hypotinues of the triangle) as the speed of the enemy (how fast he travels around the screen in pixels) and the Angle is what we did before. the x and y you get are the amount that the enemy travels in that given direction.
So you add that to their current (x,y) and get their new (x,y) r distance away at the Angle.
Now to put those in turing your x and y need to be integers remember that.
enemyX+= round(cosd(Angle)*enemySpeed)
enemyY+= round(sind(Angle)*enemySpeed)
Now put that into a nice little program add a cool tail a simple game aspect to it and you get something like this:
Turing: 
View.Set ("graphics:max;max,title:Prefect Following,offscreenonly,nobuttonbar")
var xm, ym, bm : int % Mouse variables
var eTaleLenght : int := 5 % length of the enemys tale
var eX, eY : array 1 .. eTaleLenght of int % enemy variables
var eSpeed : int := 5 % enemy speed
var eAngle : real %enemy angle of travel
eX (eTaleLenght ) := Rand.Int (eTaleLenght, maxx  eTaleLenght ) % making the enemy appear randomly
eY (eTaleLenght ) := Rand.Int (eTaleLenght, maxy  eTaleLenght )
for i : 1 .. eTaleLenght  1 % preseting the the tale to keep from getting errors
eX (i ) := eX (eTaleLenght ) % makes all start at the same spot
eY (i ) := eY (eTaleLenght ) %because hes not moving yet :)
end for
function setAngle (x, y : real) : real % the function that gets the angle from the enemy to your mouse
if x = 0 and y = 0 then
result 0
elsif x = 0 and y > 0 then
result 90
elsif x = 0 and y < 0 then
result 270
elsif y = 0 and x > 0 then
result 0
elsif y = 0 and x < 0 then
result 180
elsif x > 0 and y > 0 then
result arctand (y / x )
elsif x < 0 and y > 0 then
result 180 + arctand (y / x )
elsif x > 0 and y < 0 then
result 360 + arctand (y / x )
elsif x < 0 and y < 0 then
result 180 + arctand (y / x )
else
result 0
end if
end setAngle
proc youLose
put "He got you"
end youLose
loop
cls
Mouse.Where (xm, ym, bm )
for i : 1 .. eTaleLenght  1 % making the tale move by making it = to the one infront of it
eX (i ) := eX (i + 1)
eY (i ) := eY (i + 1)
end for
eAngle := setAngle (xm  eX (eTaleLenght ), ym  eY (eTaleLenght )) % gets the angle from enemy to mouse
eX (eTaleLenght ) + = round (cosd (eAngle ) * eSpeed ) % uses trig x=cos(angle)*radius  this gives the difference in the direction
eY (eTaleLenght ) + = round (sind (eAngle ) * eSpeed ) % uses trig y=sin(angle)*radius  to move towards the mouse
for i : 1 .. eTaleLenght % draws the tale and enemy
Draw.FillOval (eX (i ), eY (i ), i, i, 31i* 3)
end for
View.Update
delay (25)
exit when hasch % prematurely exits when keyboard is hit (nice for testing)
if Math.Distance (xm, ym, eX (eTaleLenght ), eY (eTaleLenght )) < eTaleLenght then % checks to see if he ketches you (ads a game flare to it)
youLose %(Jordan YOU LOSE)
exit
end if
end loop

Now fix up those old basic games you use to make, and know that your enmys no longer look stupid.






Sponsor Sponsor



Mackie

Posted: Tue Mar 25, 2008 10:09 pm Post subject: RE:[Tutorial] "Perfect" Following 


This is an excellent tutorial! It's very infomative, and you communicate your ideas very well.
+20 bits.






zylum

Posted: Wed Mar 26, 2008 1:03 am Post subject: Re: [Tutorial] "Perfect" Following 


The information presented is very useful, good job! I would however change the title of the tutorial. It describes the thought process of how to derive a function that gives the polar angle between two points, which has many applications, not only enemy following.
FYI, there is a simpler method for enemy following using similar triangles. Maybe you can make a second part describing it
Also, just to clean things up a bit and make it a 'proper' tutorial, read the tutorial about how to make good tutorials ie. using headings and color and such to make it more visually appealing.
+ Karma






swami

Posted: Wed Mar 26, 2008 4:51 pm Post subject: Re: [Tutorial] "Perfect" Following 


:O omg.... Amazing!!!! Very well put, it made me think i could fly. :S if thats a good thing






LaZ3R

Posted: Wed Mar 26, 2008 5:28 pm Post subject: RE:[Tutorial] "Perfect" Following 


Very nice and very well coded.
The procedure for youLose is kind of pointless in THIS case since it consists of nothing but a single line of output, but if more stuff was added it would make sense






Nick

Posted: Wed Mar 26, 2008 6:33 pm Post subject: RE:[Tutorial] "Perfect" Following 


youLose would be better as a function too






swami

Posted: Mon Mar 31, 2008 5:09 pm Post subject: Re: [Tutorial] "Perfect" Following 


lmao 'youLose' ... awesome Beaner xD http://www.losethegame.com/ < inside joke...






riveryu

Posted: Sat May 17, 2008 6:14 pm Post subject: Re: [Tutorial] "Perfect" Following 


Just for people who want to learn/review some Trigs...
Dave' Short Trig Course  good stuff I learned from when i was gr 10 (right now)
University of Guelph Trig Review  good jam packed summary/review...contains more than you need for this though
"If you google everything, then you'll get everything."






Sponsor Sponsor



richcash

Posted: Sat May 17, 2008 6:31 pm Post subject: Re: [Tutorial] "Perfect" Following 


Trig Without Tears. That's a good approach to trigonometry too. It does not encourage "memorizing symbols in order".






Saad

Posted: Sat May 17, 2008 7:32 pm Post subject: Re: [Tutorial] "Perfect" Following 


The tutorial is good, and great job on it. Finding the angle is a valid way to do it but why do more work then needed.
I shall be going over a method that doesn't directly involve finding the angles but works on similar triangles (its still based on trig but you have no need to know it).
The Theory
From the following picture
We can see that both triangles are similar. Knowing that we can derive the following equations
For those that don't understand the following
More simply put, is the same as the projected length of the direction and is the distance from the player and object
Now knowing this we can rearrange and find that
And
The Code
And so lets use these equations in some code
Turing:  View.Set ("offscreenonly")
type Position :
record
x, y : real
end record
const ENEMY_MOVEMENT_SPEED := 1 %% This is the same as the projection length (Same as Direction)
var enemy : Position := init (200, 200)
loop
var mouseX, mouseY, mouseButtonState : int
Mouse.Where (mouseX, mouseY, mouseButtonState )
var distance := Math.Distance (mouseX, mouseY, enemy.x, enemy.y )
var deltaX := mouseX  enemy.x
var deltaY := mouseY  enemy.y
%% Here we plug into the equations
var xDir := (deltaX * ENEMY_MOVEMENT_SPEED ) / distance
var yDir := (deltaY * ENEMY_MOVEMENT_SPEED ) / distance
enemy.x + = xDir
enemy.y + = yDir
Draw.FillOval (round (enemy.x ), round (enemy.y ), 5, 5, black)
View.Update ()
Time.Delay (10)
cls ()
end loop

Description: 

Filesize: 
23.13 KB 
Viewed: 
355 Time(s) 







r691175002

Posted: Sat May 17, 2008 8:47 pm Post subject: Re: [Tutorial] "Perfect" Following 


More specifically, what Saad has described is normalizing the displacement vector between you and the target then scalar multiplying it by the speed.






richcash

Posted: Sat May 17, 2008 10:55 pm Post subject: Re: [Tutorial] "Perfect" Following 


Good job, Saad. And good job The_Bean, you're tutorial is still useful as it shows how to find the polar angle.
If you look at the The_Bean's method, you'll notice he uses cos(angle) and sin(angle), which we all know is x/radius and y/radius respectively, which leads us to the exact same equations as in Saad's method, so finding the polar angle is extraneous for the purpose of "following". This problem can be thought of in terms of trigonometry, but if you reduce properly you'll lead to doing the same thing as in similar triangles method (which can also be thought of in terms of vectors, as r691175002 pointed out).






chipanpriest

Posted: Tue Dec 06, 2011 7:45 pm Post subject: Re: [Tutorial] "Perfect" Following 


Hey could somebody post a code where there's no tail? :p the tail's messing me up






Zren

Posted: Tue Dec 06, 2011 9:06 pm Post subject: RE:[Tutorial] "Perfect" Following 


There was probably a better (or more recent) tutorial I should of linked you to. Oh well. The function setAngle is properly known as atan2 in other languages. Here's an example using radians.
Turing: 
fcn atan2 (x, y : real) : real
if x = 0 and y = 0 then
result 0
elsif x = 0 and y > 0 then
result Math.PI / 2
elsif x = 0 and y < 0 then
result Math.PI + Math.PI / 2
elsif y = 0 and x > 0 then
result 0
elsif y = 0 and x < 0 then
result Math.PI
elsif x > 0 and y > 0 then
result arctan (y / x )
elsif x < 0 and y > 0 then
result Math.PI + arctan (y / x )
elsif x > 0 and y < 0 then
result Math.PI * 2 + arctan (y / x )
elsif x < 0 and y < 0 then
result Math.PI + arctan (y / x )
else
result 0
end if
end atan2
type Point :
record
x, y : real
end record
var m :
record
x, y, b : int
end record
var p : Point := init (100, 100)
var speed := 1. 5
View.Set ("offscreenonly")
loop
% Input
Mouse.Where (m.x, m.y, m.b )
% Calculate where we will go
var d : Point
if Math.Distance (p.x, p.y, m.x, m.y ) < speed then
% Target will be reached this frame
d.x := m.x
d.y := m.y
else
% Step towards target
var angle := atan2 (m.x  p.x, m.y  p.y )
d.x := p.x + speed * cos (angle )
d.y := p.y + speed * sin (angle )
end if
% Collision detection
% Move to point if no collision
p := d
% Render Frame
cls
Draw.FillOval (round (p.x ), round (p.y ), 3, 3, black)
View.Update ()
end loop








