IntroductionThis is a tutorial about making a character move the right distance when moving diagonally, and not moving too fast.
ie When x is increased by a value, and y is increased by a value, the total distance moved is faster than i would be if the user just moved along x or along y.
Someone asked me about this, so i replied in style
This tutorial can be applied to almost any language: Correctly sized diagonal movement is a very simple thing, but also quite important. Everyone should implement this concept in their games as:
(A) its very easy, and
(B) moving too fast diagonally gives people easy ways to avoid enemies, and looks and feels very wrong.
The example code used is Pseudo.
Comments are C++ style '
// comment till end of line', and C-style
/* comment till :*/ .
You must understand or know of
pythagoras theorum to do read Method(A) below, and be able to use
sine and
cosine to understand Method(B).
Contents / OverviewFirstly,
DIAGRAM HERE.
(Open in new window so you can Alt+Tab between text and diagram)
(contains all figures used in this tutorial)
A common approach to movement in fangames is to have X change by 'speed' and have Y change by 'speed'. However, when the user wants the character to mvoe in both an X direction, and a Y direction, the diagonal movement ends up too fast
(see fig 0 ).
There are two ways of understanding how to fix this: One (I'll call 'B') is more general/mathematical, and can be applied to moving in any direction (not just 8 direction moving that we want). The other (I'll call 'A' ) is more geometric and intuitive. Youll have guessed by now that ill do A first.
...
For this method, it must be understood that if a triangle has sides of length a, b, c, and the entire triangle is scaled uniformally such that a is q times larger, then the new triangles lengths are
q*a, q*b, q*c.
ie, let triangle ABC be a scaled copy off of triangle abc, such that
A = q*a.
Then:
B = q*b, and C = q*c.(See fig 1).
Method(A):We want the diagonal movement of the player to be of length
'speed', but when moving in both an x and a y direction, it ends up larger than speed.
How much larger though?
We can only move our character in terms of his x coordinate and y coordinate.
So, we can check if we are moving diagonally: If we are, reduce the x movement (
xmove) and y movement (
ymove) so that the diagonal movement is smaller.
Our current algorithm:
/*
Just pretend that the amount of x to move, and amount of y to move are calculated by this.
The actual code might involve if KeyDown(LeftArrow) then xmove = -1 and so on
*/
GetMovement( xmove, ymove )
/*
Our algorithm:
*/
if ( xmove is not 0 ) and ( ymove is not 0 ) // moving in both x and y directions, ie diagonally
x = x + fraction * xmove // Where fraction is less than 1, so that movement is reduced.
y = y + fraction * ymove //
else
x = x + xmove // We could further question if xmove was 0 and only change y.
y = y + ymove // But this is simple and neat as it is.
If we find out what the value of
fraction is, we can have equal movement in 8 directions.
Consider the case
where speed is 1. Then the total distance moved ends up as
sqrt( 1*1 + 1*1 ), by pythagoras (where sqrt( A ) means 'the square root of A' ).
So it ends up as
sqrt( 1 + 1 ) -->
sqrt( 2 )Look at
Fig 0: if speed was 1, the
'actual movement' would be sqrt( 2 ).
This is sqrt( 2 ) times too much;
ie:
speed = 1,
'actual movement' = sqrt( 2 )
So, the diagonal
'actual movement' = sqrt( 2 ) * speed, which is sqrt( 2 ) times the value we want 'actual movement' to be. If we divide 'actual movement' by sqrt( 2 ), then we will get speed ( in this case we tested, where speed = 1 ).
Of course (of course!) we cannot just change speed to a lower value as 'actual movement' will decrease also. However, its not only true that 'actual movement' is sqrt( 2 ) times as much as speed, for speed = 1. It doesnt matter what speed is: actual movement is always sqrt( 2 ) times as much.
Reassuring example (though obviously not proof):
speed = 3
'actual movement' = sqrt( 3*3 + 3*3 ) = sqrt( 18 ) = sqrt( 9 * 2) = sqrt( 2 ) * sqrt( 9 ) = sqrt( 2 ) * 3 = sqrt( 2 ) * speed, which is sqrt( 2 ) times too much!
Now, consider a triangle whos diagonal side is of length 'speed'. We could scale the triangle with lengths 'xmove', 'ymove' and 'actual length' down, such that its lengths were
Axmove, Bymove, and speed.
(See fig 2 ) So, by the triangle scale rule, because to scale down from 'actual length' to 'speed', you need to divide by sqrt( 2 ) (as weve shown), then that means also that:
Axmove = xmove / sqrt( 2 )
Bymove = ymove / sqrt( 2 )Because this triangle has diagonal length 'speed', then the x and y values we went to move are the length of the other sides, so we just need to find out what these lengths are, and convert xmove and ymove to them when moving diagonally.
If you look back at the algorithm above, youll remmember that we wanted
fraction*xmove and
fraction*ymove to be such that when moving those by those values, the diagonal movement ended up as 'speed'.
Well, using the triangle we just scaled,
Axmove = fraction*xmove, and
Bymove = fraction*ymove.
So,
fraction*xmove = xmove / sqrt( 2 )
fraction*ymove = ymove / sqrt( 2 )( See fig 2 )Therefore
fraction = 1 / sqrt( 2 ).
sqrt( 2 ) is approximately 1.414, and 1/sqrt(2 ) is approximately 0.707.
Rather than actually using expensive sqrt commands, just making fraction a constant in the above code, such that fraction = 0.707, will mean that everything is
solved!
(or put in the values of fraction: Though that makes things less readable and harder to change).
Method(B):The second way of understanding this is through trigonometry.
A circle has equal distance from its centre, to the point on its edge/circumference in any direction. For example, if you wanted to draw a perfect, circle, but your compass was cheap and crap (most cheap ones are), you would only need another pencil: Rotate it from the centre of the circle, and keep the pencil your drawing with at the same position as rotate the 'radius' pencil. Its hard to keep the pencil in place, but it proves the point.
The x component from the circles centre to any point on its circumference, and the same y component, can be calculated using the length from the centre to this point (ie radius) and the angle.
The angles for 8 directions are every 45 degrees from 0?? to 360?? (or every Pi/4 degrees from 0 to 2*Pi in radians)
Where r is the radius, and A is the angle,
x = r * cos ( A )
y = r * sin ( A ) Ill point out that for anyone worries about wether it should be '-', or about converting coordinates to upside down pc screens ...All you have to do is think vertically flipped. No 'conversion' is ever needed in code. Its not like you though up was 'positive' before maths class anyway!
r is speed of course (calculations from the circle allowing us equal movement in any direction), and A is of the set
k * 45?? (or Pi/4) where
k = ?? 0, 1, 2, 3 ...So, cos of A and sin of A will be one of
?? 1 / sqrt( 2 ) for
45??, 135??, 225??, 315??, and either
1 or 0 for
0??, 90??, 180??, 270??.Right is 0??.
Down and to the right is 45??
Down is 90??
Down and to the left is 135?? etc.
Example1:When moving left ( direction: 180??), x = r * cos( 180?? ), y = r * sin( 180?? ) => x = r * (-1), y = r * 0 => x = -r, y = 0, which is perfectly moving left by a distance of 'r'.
Example2:When moving down and to the right (45??), x = r * cos( 45?? ), y = r * sin( 45?? ) = > x = r * 1/sqrt( 2 ), y = r* 1/ sqrt( 2 ).
Checking the total distance moved, we get:
distance = sqrt( ( r * 1/sqrt( 2 ) )^2 + ( r* 1/ sqrt( 2 ) )^2 )
= sqrt( r*r / sqrt(2)*sqrt(2) + r*r / sqrt(2)*sqrt(2) )
= sqrt( r*r / 2 + r*r /2 )
= sqrt( 2*( r*r/2) )
= sqrt( r*r )
= r
which is the distance we want to move.
x and y both move positively, and so the movement is right and down.
This shows that the
x = r * 1/sqrt( 2 ), and
y = r * 1/sqrt( 2 ) when at 45??.
For other angles, the signs work out as well, and as r = speed, we have:
x = speed * 1/sqrt( 2 )
y = speed * 1/sqrt( 2 )
So fraction is 1/sqrt( 2 ) as before.
CorollaryBecause Trigonometry allows direction in any angle, we can do more things with these ideas:
First it must be known that to find the angle from an x and y movement, you take the arctan of y/x.
How this is defined depends on your api, but it may be something like: arctan( y, x ) or atan( y, x ).
To find the radius, we use pythagoras ie sqrt( x*x + y*y ), as you know.
cos and sin can be used in code as well, to allow you to move in any direction:
x = x + speed * cos ( Angle );
y = y + speed * sin ( Angle );
This can be used for things such as fireballs that some lanterns shoot at you in zelda games, or the bones that stalfos throw at you.
Because cos and sin are not the fastest things in the world, if your 'bone' or fireball keeps moving in the direction it started in until it hits a wall, you can store off the cos of the angle and sin of the angle in an xmove and ymove particularly for the bone.
Sometimes, you might want things that change direction constantly.
Consider teh vulture in The legend of Zelda: A link to the past - The one that circles around you in the Desert Of Mystery.
For a simple emulation, You could make its position:
vulture.x = player.x + radius * cos( Angle )
vulture.y = player.y + radius * sin( Angle )
Angle = Angle + 3??Then it would spin around the player in a circle, as angle increases.
Adding in a check for Angle being >= 360??, and then reducing it by 360?? so its looping back round again, would be worth it, just so that its better for cos and sin to use (less accuracy for very large numbers), and that its easier for you to query the value of Angle for eg being >180 so that means vulture is above player etc.
To further simulate the vulture, by making radius increase, the vulture will move outwards in a spiral, getting further away while rotating around the player.
By decreasing, he will get closer and closer to the player.
If you make it decrease until a minimum, then increase towards a maximum then decrease again etc, you can make the vulture move in and out while circling the player like the link to the past one does.
Cubic
polynomials could be used more cheaply to make this very smoothly, or just sin like so:
radius = ( BiggestRadius + SmallestRadius ) *0.5 + (BiggestRadius - SmallestRadius)*0.5*sin( RadiusChange )Where RadiusChange ranges through 0 to 360.
( BiggestRadius + SmallestRadius ) *0.5 is the MiddleRadius, and (BiggestRadius - SmallestRadius)*0.5 is how much to add to that to get the biggest radius, as well as how much to subtract to get the smallest radius.
Because sin goes smoothly (continuously) from
0 up to 1, down to 0, down to -1, up to 0 and so on,
then radius will go from:
Middle up to Biggest, down to Middle, Down to Smallest, Up to Middle and so on.
□
NotesFinal note: If trying to mimic The legend of Zelda: Link to the past, notice that link actually moves slower diagonally than left ot right. fraction would be less than 1/ sqrt( 2 )
References[1] Me.
[/font]