This article takes a high-level but hopefully useful look and some common trigonometric functions with the aim of clarifying how they can be used as solutions in our game coding to create some cool movement and drawing effects. As this article is aimed at beginners in game coding it makes no assumptions about the readers mathematical knowledge either. In this article Calculating heading in 2D games: Using trigonometric functions part 1 we will explore how to calculate heading but let’s have a quick look at what else these mystical functions can achieve for us that we will cover in the future articles of this series.
Introduction to trigonometric functions
Trigonometric functions like sine (sin) and cosine (cos) are often used in game programming for a number of tasks. For example, rotating a spaceship or determining horizontal and vertical velocity based upon the angle the character/ball/missile is moving, moving a non-player-character in a more pleasing way than simply left, right, up, down or diagonal (think about the deadly swooping of a Galaxians enemy). And what about a ripple effect; perhaps an explosion or pebble-splash, starting from a centre point and radiating outwards at an increasing radius.
There are no end of uses for these trigonometric functions but the key to understanding how to use them in all of these situations is to understand a bit more about the functions themselves. We don’t actually need to understand the math contained in the function but we should be able to understand the range of results returned by a function. But before we go into it, what exactly is a trigonometric function?
What is a trigonometric function
A function is simply a calculation, a sum or algorithm. The point is that we use the function by giving it an input (a number) and the function gives us back an output, an answer. A trigonometric function is just a function used in the field of trigonometry, which loosely put is the study of triangles. So they are functions (designed usually by a mathematical genius or two) which we can use to get a result to a problem that would otherwise have been significantly more challenging.
As an example of what’s inside a trigonometric function here is how to find the sine of an angle WITHOUT using the sine button on the calculator. We don’t need to understand anything on that page to make games but it is kind of fun to see how complex this stuff is. Perhaps even more interesting is the history of trigonometry. The previous link and its out-links start with the Egyptians and eventually arrives at early microprocessors and B58 bombers and is truly fascinating. But back to games, how do we make use of these functions.
Calculating heading with trigonometric functions
The programming languages and API’s provide the complex math wrapped up in a simple method/function call. For example, in Java, we could use this code snippet to control the horizontal and vertical velocity of say a spaceship to make sure it travelled in precisely the same direction that it was facing.
// facingAngle can be any angle between 1 and 360 degrees // the Math.toRadians method simply converts the more conventional // degree measurements to radians which are required by the cos and sin methods. horizontalVelocity = speed * Math.cos(Math.toRadians(facingAngle)); verticalVelocity = speed * Math.sin(Math.toRadians(facingAngle));
As we see in the code snippet we used the cosine of an angle to get the horizontal velocity and the Sine for the vertical.
Very similar code would work in all the other major languages. As example projects are completed over the coming months I will link out to them from the header of this article under the “Projects that demonstrate these concepts” heading. If you are happy that this works and that is all you care about, that is fine, copy the code and close this article. If you want to know why it works and get an insight into using sine and cosine for other game programming solutions then read on.
However, the code snippet in itself doesn’t really explain why it works. We will go into some depth with the spaceship’s heading example and look a bit deeper into the values returned by the sine and cosine functions, then we will be able to move on, in future articles to work out how to achieve the other game coding tasks mentioned in the introduction.
Take a look at this next diagram.
For simplicities sake, let’s say we always move 1 pixel each time we update our spaceship’s coordinates. The spaceship is the orange circle. Assuming we have a simple Cartesian coordinate system where y increases as we go up the screen and x increases as we go to the right. Then as shown in the diagram, if we want to go up we increase y by 1, right we increase x by 1, down we decrease y by 1 etc. Furthermoreh the slightly more awkward 45-degree angles can also be fairly simply catered for. If we want to go 45 degrees to the right and up we add 1 to both x and y. If we wanted to go 45 degrees down and to the left we would subtract 1 from both x and y.
But of course a real vehicle can travel at any angle it wants to and our games will be a whole lot more satisfying if they let the player do the same. What the problem can be simplified to is this. We need to find the ratio of the distance travelled on the x-axis compared to the y-axis. Once we have this ratio we can multiply by any factor we like (as long as we multiply both sides of the ratio by the same amount) to vary the speed at which we travel. Enter sine and cosine.
Sine and Cosine
We have one minor stumbling block before we can start testing numbers on a calculator to see how we can set the heading of our spaceship at all those awkward angles. Remember the snippet of Java code earlier? It was two method calls wrapped up together.
... horizontalVelocity = speed* Math.cos(Math.toRadians(facingAngle)); ...
We are passing the result of converting the facingAngle converted into radians, into the cos/sin function/method. This is because sin and cos work with radians, not degrees. Radians are simply another measurement for angles. But if we are going to understand how we can calculate our spaceship’s horizontal and vertical ratio we need to take a quick look at radians.
Radians
A radian is a measurement of an arc of a circle. A radian is roughly 57.3 degrees. This might seem arbitrary and invented but it is really degrees that are arbitrary and invented, but we have become used to dealing with them. A radian is precisely the arc of a circle measured to the same length of that circle’s radius.
There are pi (3.14…) radians in 180 degrees and therefore 2 pi (6.28…) radians in a circle. From this, we can work out that pi divided by 180 multiplied by the angle the spaceship is facing is the number of radians we need to send into the sin or cos function.
cos or sin(pi / 180 * facingAngle)
Now if we experiment using a few angles that our spaceship might be heading and analyse the results. Let’s use the angles we already know the answers for.
Perhaps ironically, most simple personal calculators expect DEGREES as an input to sin and cos. If you are unsure what your calculator uses or get different results to those in the table try these neat online calculators for sine and cosine where you can specify degrees or radians (choose radians).
facingAngle | Convert to radians | Radians | Sin | Cos | Approximate ratio sin:cos |
---|---|---|---|---|---|
45 | pi / 180 * 45 | 0.78 | 0.70 | 0.71 | 1:1 |
180 | pi / 180 * 180 | 3.14 | 0.00 | -0.99 | 0:-1 |
225 | pi / 180 * 225 | 3.92 | -0.70 | -0.71 | -1:-1 |
270 | pi / 180 * 270 | 4.71 | -1.0 | 0 | -1:0 |
Remember that the important thing about the answers is not just the actual value but the ratio between the two values returned by sin and cos. So that when we multiply by our spaceship’s xVeloxity and yVelocity variables by a speed variable the heading will be the same as facingAngle. Bearing in mind that 0 degrees means facing 3 o’clock/east/right; let’s look at a few of the results in the table.
As a quick aside, zero degrees returns zero from both sin and cos. This is not a problem because of course, 360 degrees is the same heading as zero degrees.
The first value in the table is when facingAngle = 45 degrees (pointing up to the right/North East). We can see that the value of sin is 0.70 and cos is 0.71. and excluding the discrepancy of 1 one hundredth, if we assigned the value from sin to verticalVelocity and cos to horizontalVelocity would have our ship headed in exactly the intended direction.
The next row in the table is 180 degrees. If we refer back to the diagram we can see that the horizontal velocity should be -1 and the vertical velocity should be 0. We compare our expectations to the values of sin and cos and see that indeed they are exactly correct. Cos is near-as-exactly -1 and sin equals 0.
We can go on and check the rest of the values in the table but what would be more interesting is to try and demonstrate the effectiveness of the method on some more awkward angles.
Consider the following:
- 1 degree in radians = 0.017
- 360 degrees in radians = 6.28
Considering the above two facts, let’s plot a graph with every value between and including those values for the values of sine and cosine. The actual values of sine and cosine are not shown in the image below but their relationship to each other and the uniform, constrained and extremely useful extents of those values are. Almost certainly you will have seen these before. They are the sine and cosine waves.
So we can see that we can rely on the values returned by the cosine function to give us a velocity for horizontal movement based on the angle our spaceship is facing as long as we plug that same value into Sine and use it for the vertical velocity.
We can also use sine and cosine for orienting (rotating) graphics; because what is the point in heading in the exact angle as calculated above if we are looking the other way? But that is a topic for part 2 of this series.
Nice tutorial, just found this website and it seems like it has a lot to offer for game development. The explanation of what a Radian is done very well. When I first heard about these, I was lost and it took quite some time to get a grasp on it.
Not sure how long the site has been around, but keep up the hard work!
Thanks!
Thanks for your comment Dave. The site was here at the end of 2014 but the first proper articles were 2015. Many thanks for your comment.
I’ve been reading your book for a while and i think you got a very nice working ‘game’ example and a framework to work with. Even you provide a ‘gentle’ introduction to java in the beginning that very helpfull for absolute beginner to java/programmer i should say. I thought if this book came out all way back a couple years ago atleast, probably i would run into a Computer Science education for sure. But nevertheless, your book gave me like a ressurection on my buried dream as a game developer. So now, i stumbled upon CS course online at night while working as usual in the field that is not my actual passion. The only thing to compensate is by make it a hobby, and your book suits my need well. Thanks a lot.
Hi Electra,
Thanks very much for your nice message. It is heartwarming to hear your opinions. Yes, it is important to keep your passion alive and never stop thinking how, perhaps, you might one day make them your reality.
John
Do you guys use typical maths +x direction for angles that are 0 degrees?
I was using +y , ie north, but was going against my math training
Zero degrees is three o’clock.
Hi John, I have been working on a project (tank) shooter, I have 2 circles on the screen, but I am having problems making my enemy shoot when player is on the left, right, top, or below the enemy. I cant seem to get it working, I tried using
int angle = atan2(mEnemy.getPosition().y – mPlayer.getPosition().y, mEnemy.getPosition().x – mPlayer.getPosition().x);
angle = angle * (180 / PI);
std::cout << "Value is" << angle << "\n";
I am using SFML………..any tips to point in the the right direction?
I also have had angle as a double but it gives weird output. How would I go about doing this, I created a picture imgur to show what I am talking about, any help in direction on how to solve this problem? in short I want the enemy to shoot towards the player wherever he is on screen. here is a pic http://imgur.com/a/YDait
Are you trying to calculate the direction between the two objects to shoot from one to the other? If so you need to calculate the ratio of the rate of change between horizontal and vertical. This can then form a horizontal and vertical speed(or gradient). Here is a copy & paste from the shoot function of a Bullet class I used to shoot bullets from the player to a movable crosshair. Hope this helps a bit.
void Bullet::shoot(float startX, float startY,
float targetX, float targetY)
{
// Keep track of the bullet
m_InFlight = true;
m_Position.x = startX;
m_Position.y = startY;
// Calculate the gradient of the flight path
float gradient = (startX - targetX) / (startY - targetY);
// Any gradient less than zero needs to be negative
if (gradient < 0)
{
gradient *= -1;
}
// Calculate the ratio between x and t
float ratioXY = m_BulletSpeed / (1 + gradient);
// Set the "speed" horizontally and vertically
m_BulletDistanceY = ratioXY;
m_BulletDistanceX = ratioXY * gradient;
// Point the bullet in the right direction
if (targetX < startX)
{
m_BulletDistanceX *= -1;
}
if (targetY < startY)
{
m_BulletDistanceY *= -1;
}
// Finally, assign the results to the
// member variables
m_XTarget = targetX;
m_YTarget = targetY;
// Set a max range of 1000 pixels
float range = 1000;
m_MinX = startX - range;
m_MaxX = startX + range;
m_MinY = startY - range;
m_MaxY = startY + range;
// Position the bullet ready to be drawn
m_BulletShape.setPosition(m_Position);
}
Thanks John, I am so grateful that you have this website, you are helping beginners step into game programming much easier. I have asked other “pro” programmers this same question I asked you and they just gave vague answers or were just plain lazy in explaining, while making me feel like an idiot in the process. You actually posted code to show the procedure, which has helped me more then anyone else…it boggles me why experienced programmers would act like this……. i have been searching for an answer from google and other people for like 3 weeks with no success. thank you so much, i am also still working through the udemy beginner c++ course still tinkering with timberman.
Glad to be of help! The code is from the next project in the course.