Hello Guest, please login or register.
Did you miss your activation email?
Login with username, password and session length.

Pages: [1]   Go Down

Author Topic: [Example / Listing] 'Proper' Diagonal movement  (Read 2216 times)

0 Members and 1 Guest are viewing this topic.

aab

^ Evolved from a Hobbit
[Example / Listing] 'Proper' Diagonal movement
« on: June 19, 2006, 09:20:13 pm »
  • *
  • Reputation: +0/-0
  • Offline Offline
  • Gender: Male
  • Posts: 992
Introduction

This 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  :D



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 / Overview

Firstly, 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:

Code: [Select]

  /*
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.


Corollary

Because 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.




Notes

Final 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]
« Last Edit: March 15, 2012, 08:06:45 pm by Niek »
Logged




I ♥ Sol
.... I ♥ Sol ? wtf how long has that been there? >_> *rrrrrrrrar*
  • MySpace
Re: 'Proper' Diagonal movement
« Reply #1 on: June 19, 2006, 09:34:21 pm »
  • *
  • Reputation: +0/-0
  • Offline Offline
  • Gender: Male
  • Posts: 563
Ah pretty much what you sent me via PM when I asked for help, and it helped much, still took a while to get it working and all but thats because of only being able to add whole numbers to the X and Y positions but found a way around that hehe.

Thanks again :D.
Logged

I ♥ Open Girlfriend (What!? I didn't add this.. must have been Solly!)
  • My DevArt Account

mit

Re: 'Proper' Diagonal movement
« Reply #2 on: June 19, 2006, 10:00:07 pm »
  • QBASIC programmer since age 4. Take that, world.
  • *
  • Reputation: +0/-0
  • Offline Offline
  • Posts: 1079
Heh, that's a pretty long and full explanasion of just two lines of code :p
Oh, and for Game Maker users, remember that there are functions built in for this;
Code: [Select]
vulture.x = player.x + radius * cos( Angle )
vulture.y = player.y + radius * sin( Angle )
is the same as
Code: [Select]
vulture.x = player.x + lengthdir_x(radius, Angle)
vulture.y = player.y + lengthdir_y(radius, Angle)
which may or may not be faster, I'm not sure. Easier when you can't remember which is for x and which is for y.
Logged
Programmer / Spriter / Level designer / Game Director / Web Designer / Music Sequencer for
Random Highscore table:

Play the Kousou Arcade today!
  • Kousou Games
Pages: [1]   Go Up

 


Contact Us | Legal | Advertise Here
2013 © ZFGC, All Rights Reserved



Page created in 0.039 seconds with 40 queries.

anything