Trigonometry for Game Programming: Part 1/2
Learn Trigonometry for Game Programming in this 2part tutorial series, where you’ll learn the theory and then practice by making a space shooter game! By Matthijs Hollemans.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Trigonometry for Game Programming: Part 1/2
65 mins
Getting Started: It’s All About Triangles
Your Arsenal of Functions
Know Angle and Length, Need Sides
Know 2 Sides, Need Angle
Know 2 Sides, Need Remaining Side
Have Angle, Need Other Angle
To Skip, or Not to Skip?
Getting Started
Steering with Accelerometers
Begin the Trigonometry!
Radians vs. Degrees
Bouncing Off the Walls
Blending Angles for Smooth Rotation
Using Trig to Find Your Target
Adding Health Bars... That Move!
Using Trig for Collision Detection
Adding Some Spin
Where to Go from Here?
This is a post by iOS Tutorial Team member Matthijs Hollemans, an experienced iOS developer and designer. You can find him on Google+ and Twitter.
Does the thought of doing mathematics give you cold sweats? Are you ready to give up on your career as a budding game developer because the math just doesn’t make any sense to you?
Don’t fret – math can be fun, and this cool 2part game tutorial will back up that claim!
Here’s a little secret: as an app developer, you don’t really need to know a lot of math. If you can add or multiply two numbers together, you’re already halfway there. Most of the computations that we do in our professional lives don’t go much beyond basic arithmetic.
That said, for making games it is useful to have a few more math skills in your toolbox. You don’t need to become as smart as Archimedes or Einstein, but a basic understanding of trigonometry, combined with some common sense, will take you a long way.
In this tutorial, you will learn about some important trigonometric functions and how you can use them in your games. Then you’ll get some practice applying the theories by developing a simple spaces shooter iPhone game that requires a lot of trigonometry, using the Cocos2D game framework.
Don’t worry if you’ve never used Cocos2D before or are going to use a different framework for your game – the mathematics we’ll cover in this tutorial will apply to your engine no matter what. And you don’t need any prior experience, as I’ll walk through the process stepbystep.
If you supply the common sense, this tutorial will get you up to speed on the trig, so let’s get started!
It sounds like a mouthful, but trigonometry simply means calculations with triangles (that’s where the tri comes from).
You may not have realized it until now, but games are full of triangles. For example, imagine you have a spaceship game, and you want to calculate the distance between these ships:
You have X and Y position of each ship, but how can you find the length of that line?
Well, you can simply draw a line from the center point of each ship to form a triangle like this:
Then, since you know the X and Y coordinates of each ship, you can compute the length of each of the new lines. Now that you know the lengths of two sides of the triangle, you can use some Trigonometry to compute the length of the diagonal line – the distance between the ships.
Note that one of the corners of this triangle has an angle of 90 degrees. This is also known as a right triangle, and that’s the sort of triangle you’ll be dealing with in this tutorial.
Any time you can express something in your game as a triangle with a 90degree right angle – such as the spatial relationship between the two sprites in the picture – you can use trigonometric functions to do calculations on them.
So in summary, Trigonometry is the mathematics that you use to calculate the lengths of the sides of these triangles, as well as the angles between those sides. And that comes in handy more often that you might think.
For example, in this space ship game you might want to:
All of this and more you can do with the power of Trigonometry!
First, let’s get the theory out of the way. Don’t worry, I’ll keep it short so you can get to the fun coding bits as quickly as possible.
These are the parts that make up a right triangle:
In the picture above, the slanted side is called the hypotenuse. It always sits across from the corner with the 90degree angle (also called a right angle), and it is always the longest of the three sides.
The two remaining sides are called the adjacent and the opposite, as seen from one particular corner of the triangle, the bottomleft corner in this case.
If you look at the triangle from the other corner (topright), then the adjacent and opposite change places:
Alpha (α) and beta (β) are the names of the two other angles. You can call these angles anything you want (as long as it sounds Greek!) but usually alpha is the angle in the corner of interest and beta is the angle in the opposing corner. In other words, you label your opposite and adjacent sides with respect to alpha.
The cool thing is that if you only know two of these things, trig allows you to find out all the others using the sine, cosine and tangent functions. For example, if you know an angle and the length of one of the sides, then the sine, cosine and tangent functions can tell you the length of the other sides:
Think of the sin, cos, and tan functions as “black boxes” – you plug in numbers and get back results. They are prewritten functions you can call from almost any programming language.
Let’s consider an example. Say you know the alpha angle between the ships is 45 degrees, and the length between the ships (the hypotenuse) is 10 points long. You can then plug these values into the formula:
sin(45) = opposite / 10
To solve this for the hypotenuse, you shift things around a bit:
opposite = sin(45) * 10
If you call the builtin sin function, you’ll find the sine of 45 degrees is 0.707 (rounded off), and filling that in that gives you the result:
opposite = 0.707 * 10 = 7.07
There is a handy mnemonic for remembering what these functions do that you may remember from high school: SOHCAHTOA (where SOH stands for Sine is Opposite over Hypotenuse, and so on), or if you need something more catchy: Some Old Hippy / Caught Another Hippy / Tripping On Acid. (That hippy was probably a mathematician who did a little too much trig.) :]
The above formulas are useful when you already know an angle, but that is not always the case – sometimes it is the angle you are looking for. Then you need to know the lengths of at least two of the sides and plug these into the inverse functions:
In other words, if sin(a) = b, then it is also true that arcsin(b) = a. Of these inverse functions, you will use the arc tangent (arctan) the most in practice. Sometimes these functions are also notated as sin^{1}, cos^{1}, and tan^{1}, so don’t let that fool you.
Is any of this sinking in or sounding familiar? Good, but you’re not done yet with the theory lesson – there is still more that you can calculate with triangles.
Sometimes you may know the length of two of the sides and need to know the length of the third, like in the example at the beginning of this tutorial where you wanted to find the distance between the two space ships.
This is where Trigonometry’s Pythagorean Theorem comes to the rescue. Even if you forgot everything else about math, this is probably the one formula you do remember:
a^{2} + b^{2} = c^{2}
Or, put in terms of the triangle that you saw earlier:
opposite^{2} + adjacent^{2} = hypotenuse^{2}
If you know any two of these sides, then calculating the third is simply a matter of filling in the formula and taking the square root. This is a very common thing to do in games and you’ll be seeing it several times in this tutorial.
Note: Want to drill this formula into your head while having a great laugh at the same time? Search YouTube for “Pythagoras song” – it’s an inspiration for many!
Lastly, the angles. If you know one of the nonright angles from the triangle, then figuring out the other ones is a piece of cake. In a triangle, all angles always add up to a total of 180 degrees. Because this is a right triangle, you already know that one of the angles is 90 degrees. That leaves:
alpha + beta + 90 = 180
Or simply:
alpha + beta = 90
The remaining two angles must add up to 90 degrees. So if you know alpha, you also know beta, and viceversa.
And those are all the formulas you need to know! Which one to use in practice depends on the pieces that you already have. Usually you either have the angle and the length of at least one of the sides, or you don’t have the angle but you do have two of the sides.
Enough theory. Let’s put this stuff into practice.
In the next few sections, you will be setting up a basic Cocos2D project with a space ship that can move around the screen using the accelerometer. This won’t involve any trigonometry (yet), so if you already know Cocos2D and feel like this guy:
Then feel free to skip ahead to the Begin the Trigonometry! section below – I have a starter project waiting for you there.
But if you’re the type who likes to code everything from scratch, keep reading! :]
To get started, download the latest version of the Cocos2D v2.1 branch. At the time of writing, this is the “unstable” version cocos2diphone2.1rc1.
After Cocos2D is done downloading and unzipping, you want to install the project templates. Open a Terminal window to the directory where you downloaded Cocos2D and enter the following command:
You should see “Installing cocos2d templates” and a bunch of messages. Now you’re ready to work with Cocos2D.
Fire up Xcode and create a new application from the “cocos2d iOS” template you just installed.
Name the project TrigBlaster and set the device family to iPhone. Build and run the template asis. If all works OK, you should see the following:
You are going to use ARC in this project, but Cocos2D projects have it disabled by default. Luckily, enabling ARC is really easy. Just go to Edit\Refactor\Convert to ObjectiveC ARC. Expand the dropdown and select only the last four files (main.m, AppDelegate.m, HelloWorldLayer.m, and IntroLayer.m), then click Check and finish the steps in the wizard.
Note: When you convert a project to ARC or make other large changes, Xcode will prompt you to enable snapshots for the current project. A snapshot saves the state of the entire project as if you copied and pasted the project into another directory. You can learn more about snapshots over here. For the purposes of this tutorial, select disable.
Next download the resources for this tutorial. This file contains the images for the sprites and the sound effects. Unzip it and drag the Images and Sounds folders into Xcode to add them to the project. In the import dialog, make sure Copy items into destination group’s folder is checked.
Great, the preliminaries are over and done with – now let’s get coding for real.
Because this is a simple game, you will be doing all your work inside a single class: the HelloWorldLayer
. Right now, this layer contains a bunch of stuff that you don’t need.
Replace the contents of HelloWorldLayer.h with:
This game doesn’t use GameKit, so you got rid of that. You also changed HelloWorldLayer
from a plain CCLayer
to a CCLayerColor
so that you can have a background color other than just black.
There is also a bunch of code in the .m file that you don’t need for this game. Replace HelloWorldLayer.m with:
The scene method is unchanged from the template, but init
now calls [super initWithColor]
to set a purple background color on the layer.
Build and run, and you should see nothing but purple:
Let’s make things a bit more exciting by adding a spaceship to the scene. You will need some instance variables for this, so modify the @implementation
line in HelloWorldLayer.m as follows:
The _winSize
variable stores the dimensions of the screen, which is useful because you will be referring to that quite often. The _playerSprite
variable holds the spaceship sprite.
Now change init
to the following:
This is all pretty basic if you have worked with Cocos2D before. The player sprite is positioned in the bottomright corner of the screen. Remember that with Cocos2D, it is the bottom of the screen that has ycoordinate 0, unlike in UIKit, where y = 0 points to the top of the screen.
Build and run to try it out, and you should see the following:
To move the spaceship, this game uses the iPhone’s builtin accelerometers. Unfortunately, the iOS Simulator cannot simulate the accelerometers. That means that from now on, you will need to run the app on your device to test it.
Note: If you are unsure how to put the app on your device, check out this extensive tutorial that explains how to obtain and install the certificates and provisioning profiles that allow Xcode to run apps on your device. It’s not as intimidating as it looks, but you do need to sign up for the paid Apple developer program.
To move the spaceship with the accelerometers, you’ll obviously be rocking your device from side to side. During testing I found that this sometimes caused the screen to autorotate from landscape right to landscape left, which is really annoying when you’re in the middle of a heated battle.
To prevent that from happening, go into the Target Settings screen and in the Summary tab under Supported Interface Orientations, deselect all options except for Landscape Right.
Using the accelerometers from code is pretty straightforward. You first ask Cocos2D to activate the accelerometers. Once enabled, it sends you periodic updates with the values of the three accelerometers that are built into the iPhone. You then use these values to change the speed of the spaceship.
First, add new instance variables to keep track of the accelerometer values. You only store the values for two of the accelerometers; the “Z” one isn’t used by this game.
A good place to activate the accelerometers is inside init
. Add the following line to it:
Finally, add the method that receives the accelerometer values:
This bit of logic is necessary to filter – or “smoothen” – the data that you get back from the accelerometers so that it appears less jittery.
Note: An accelerometer records how much gravity currently pulls on it. Because the user is holding the iPhone in her hands, and hands are never completely steady, there are a lot of tiny fluctuations in this gravity value. We are not so much interested in these unsteady motions as in the larger changes in orientation that the user makes to the device. By applying this simple lowpass filter, you retain this orientation information but filter out the less important fluctuations.
Now that you have a stable measurement of the device’s orientation, how can you use this to make the player’s spaceship move?
Movement in games often works like this:
Of course, you have a great mathematician to thank for these equations: Sir Isaac Newton!
You need some more instance variables to pull this off. There is no need to keep track of the player’s position because the CCSprite
already does that for you, but acceleration and speed are your own responsibility.
Add these instance variables:
It’s good to set some bounds on how fast the spaceship can travel or it would be pretty hard to maneuver. Infinite acceleration sounds like a good idea in theory but in practice it doesn’t work out so well (besides, even Einstein thinks there are limits to how fast you can go).
Add the following lines directly below the #import
statement:
This defines two constants, the maximum acceleration (400 points per second squared) and the maximum speed (200 points per second).
Now add the following bit of logic to the bottom of accelerometer:didAccelerate:
:
This is a basic technique for controlling a sprite using the accelerometers. When the device is tilted to the left, you give the player maximum acceleration to the left. Conversely, when the device is tilted to the right, you give the player maximum acceleration to the right. Likewise for the up and down directions.
Note: You’re using the _accelerometerY
value for the xdirection. That’s as it should be. Remember that this game is in landscape, so the Yaccelerometer runs from left to right in this orientation, and the Xaccelerometer from top to bottom.
You’re almost there. The last step is applying the _playerAccelX
and Y
values to the speed and position of the spaceship. You will do this in the game’s update
method. This method is called 60 times per second, so it’s the natural place to perform all of the game logic.
Add updatePlayer
to the class:
If you’ve programmed games before (or studied physics), then this should look very familiar. Here’s how it works:
Add the current acceleration to the speed.
The acceleration is expressed in points per second (actually, per second squared, but don’t worry about that) but update:
is performed a lot more often than once per second. To compensate for this difference, you multiply the acceleration by the elapsed or “delta” time, dt
. Without this, the spaceship would move about sixty times faster than it should!
Clamp the speed so that it doesn’t go faster than MaxPlayerSpeed
if it is positive or MaxPlayerSpeed
if it is negative. You could write this using an if
statement instead:
However, I like the succinctness of the oneline version. fminf()
makes sure the speed doesn’t become larger than MaxPlayerSpeed
because it always picks the smallest of the two, while fmaxf()
makes sure the speed doesn’t drop below MaxPlayerSpeed
because it always picks the largest value. It’s a neat little trick.
Finally, add the following line to init
to make update
active:
Then add update
itself:
That should do it. Build and run the game. You can now control the spaceship by tilting the device.
If you skipped ahead to this section, here is the starter project at this point. Build and run it on your device – you’ll see there’s a spaceship that you can move around with the accelerometer.
You haven’t seen any trigonometry yet, so let’s put some into action.
Note: If you’ve used Cocos2D before, you might know Cocos2D comes with handy builtin methods like ccpLength, ccpAngle, and more that are wrappers over the raw trigonometry functions for ease of use. In this tutorial, you will not be using these functions – instead you will be going low level and issuing the raw trigonometry calls yourself, for the learning experience. In an actual game, you might want to use the wrapper methods however.
It would be cool – and much less confusing to the player! – to rotate the spaceship in the direction it is currently moving rather than having it always pointing upward.
To rotate the spaceship, you need to know the angle to rotate it. But you don’t know what that is, so how can you figure that out?
Let’s think about what you do know. You do have the player’s speed, which consists of two components: a speed in the xdirection and a speed in the ydirection:
If you rearrange these a little, you can see that they form a triangle:
Here you know the lengths of the adjacent (_playerSpeedX
) and the opposite (_playerSpeedY
) sides.
So basically, you know 2 sides of a right triangle, so you are in the Know 2 Sides, Need Angle case mentioned in the beginning of this tutorial. That means you need to use one of the inverse functions: arcsin, arccos or arctan.
You know the opposite and adjacent sides, so you want to use the arctan function to find the angle to rotate the ship. Remember, that looks like the following:
angle = arctan(opposite / adjacent)
The ObjectiveC math library comes with the atan()
function that computes the arc tangent, but it has one big issue: what if the xspeed is 0? In that case, the adjacent is 0 and dividing by 0 is mathematically undefined. Your app might crash or just behave weirdly when that happens.
Instead of using atan()
, it is better to use the function atan2()
, which takes two parameters and correctly handles the divisionbyzero scenario without you having to worry about it:
angle = atan2(opposite, adjacent)
Important: The angle that atan2f()
gives you is not the inner angle inside the triangle, but the angle that the hypotenuse makes with that 0degree line:
This is another reason why atan2()
is a lot more useful than plain, old atan()
.
So let’s give that a shot. Add the following two lines to the bottom of updatePlayer
:
Note that the arctan function you are using is actually called atan2f()
instead of just atan2()
. More about that in a moment. Also notice that the ycoordinate goes first. A common mistake is to write atan2f(x, y)
but that’s the wrong way around. Remember the first parameter is the opposite, and in this case the Y coordinate is the opposite.
Build and run the app to try it out:
Hmm, this doesn’t seem to be working quite right. What is wrong here?
Normal human beings tend to think of angles as values between 0 and 360. Mathematicians, however, measure angles in radians, which are expressed in terms of π (the Greek letter Pi, which sounds like pie but doesn’t taste as good).
It’s not essential to understand, but if you’re curious, one radian is the angle you get when you travel the distance of the radius along the arc of the circle. You can do that 2π times (roughly 6.28 times) before you end up at the beginning of the circle again.
So while you may think of angles as values from 0 to 360, a mathematician sees values from 0 to 2π. And that’s where your problem lies. atan2f()
returns a value in radians but Cocos2D’s sprite rotation property expects degrees. That’s why you only saw the sprite rotate a little: angles measured in radians are much smaller than angles measured in degrees.
Fortunately, it is easy to convert from one to the other. Change the line that sets _playerSprite.rotation
to:
The CC_RADIANS_TO_DEGREES
macro is Cocos2D’s way of doing the conversion but you can easily write your own function or macro to do this. I don’t have these formulas memorized but they are easy enough to derive yourself when you realize than an angle of 360 degrees corresponds to 2π:
Angle in degrees / 360 degrees = Angle in radians / 2π radians
To go from radians to degrees, the formula becomes:
Angle in degrees = (Angle in radians / 2π) * 360
And the other way around, from degrees to radians:
Angle in radians = (Angle in degrees / 360) * 2π
I point this out because forgetting to convert between radians and degrees is probably the most common mistake programmers make when they are dealing with trigonometry! So if you don’t see the rotation you expected, make sure you’re not mixing up your degrees and radians…
So what the @#! is π? Pi is the ratio of the circumference of a circle to its diameter. In other words, if you measure the outside of any circle and divide it by its diameter (which is twice the radius), you get the number 3.141592… and that is what we call π. It takes 2π radians to go all the way around a circle, which is why 360 degrees corresponds to 2π. Pi is a very important concept in mathematics that shows up all the time, especially with anything that is cyclical.
By the way, not everyone likes π. Some people believe that it would have been better to choose the ratio of the circumference to the radius, instead of to the diameter. This number is called tau (τ) and is equal to 2π, thus simplifying many of the calculations. You can read more about tau at tauday.com.
Anyway, back to the game. Build and run to see some glorious rotation in action.
Whoops, something is still not right. The spaceship certainly rotates but it seems to be pointing in the direction opposite to where it’s flying!
Here’s what’s happening: the sprite for the spaceship points straight up, which corresponds to the default rotation value of 0 degrees. But in mathematics, an angle of 0 degrees (or radians) doesn’t point upward, but to the right:
And that’s not the only problem: in Cocos2D, rotation happens in a clockwise direction, but in mathematics it goes counterclockwise.
To overcome these differences, change the line to:
This adds 90 degrees to make the sprite point to the right at an angle of 0 degrees, so that it lines up with the way atan2f()
does things. Then it adds the negative angle – in other words, subtracts the angle – in order to rotate the proper way around.
Build and run once more to try it out. You’ll finally have a spaceship that has its head on straight!
You have a spaceship that you can move using the accelerometers and you’re using trig to make sure it points in the direction it’s flying. That’s a good start.
Unfortunately, having the spaceship get stuck on the edges of the screen isn’t very satisfying. You’re going to fix that by making it bounce off the screen borders instead.
First, comment these lines from updatePlayer
:
Then add the following code to updatePlayer
, just after the commented lines:
This checks whether the spaceship hit any of the screen borders and if so, sets a BOOL
variable to YES. But what to do after such a collision takes place? To make the spaceship bounce off the border, you can simply reverse its speed and acceleration.
First, define a constant at the top of the file, above the @implementation
line:
Add the following lines in updatePlayer
, directly below the code you last added there:
If a collision registered, you flip the acceleration and speed values around. Notice that this also multiplies the acceleration and speed by a damping value, BorderCollisionDamping
.
As usually happens in a collision, some of the movement energy is dissipated by the impact. In this case, you make the spaceship retain only 40% of its speed after bumping into the screen edges.
Try it out. Smash the spaceship into the border and see what happens. Who said spaceships can’t bounce?
For fun, play with the value of BorderCollisionDamping
to see the effect of different values for this constant. If you make it larger than 1.0f, the spaceship actually gains energy from the collision.
Note: Now why is there an f behind those numbers in the code: 0.4f, 0.1f, 0.0f, and so on? And why did you use atan2f()
instead of just atan2()
? When you write games, you want to work with floating point numbers as much as possible because, unlike integers, they allow for digits behind the decimal point. This allows you to be much more precise.
There are two types of floating point numbers: floats and doubles (there is also a “long double”, but that’s the same as a double on the iPhone). Doubles are more precise than floats but they also take up more memory and are slower in practice. When you don’t put the f behind the number and just use 0.4, 0.1, 0.0, or when you use the version of a math function without the f suffix, you are working with doubles and not floats.
It doesn’t really matter if you use a double here and there. For example, the time value that CACurrentMediaTime()
returns is a double. However, if you’re doing hundreds of thousands of calculations every frame, you will notice the difference. I did a quick test on a couple of my devices and the same calculations using doubles were 1.5 to 2 times slower. So it’s a good habit to stick to regular floats where you can.
You may have noticed a slight problem. Keep the spaceship aimed at the bottom of the screen so that it continues smashing into the border over and over, and you’ll see a constant flicker between the up and down angles.
Using the arc tangent to find the angle between a pair of x and ycomponents works quite well, but only if those x and y values are fairly large. In this case, the damping factor has reduced the speed to almost zero. When you apply atan2f()
to very small values, even a tiny change in these values can result in a big change in the resulting angle.
One way to fix this is to not change the angle when the speed is very slow. That sounds like an excellent reason to give a call to our old friend, Pythagoras.
Right now you don’t have such a thing as “the ship’s speed”. Instead, you have two speeds: one in the xdirection and one in the ydirection. But in order to draw any conclusions about “the ship’s speed” – is it too slow to actually rotate the ship? – you need to combine these x and y speed values into one single value:
Here you are in the Know 2 Sides, Need Remaining Side case discussed earlier.
As you can see, the true speed of the spaceship, that is, how many points it moves across the screen in a second, is the hypotenuse of the triangle that is formed by the speed in the xdirection and the speed in the ydirection. Put in terms of the Pythagorean formula:
true speed^{2} = _playerSpeedX
^{2} + _playerSpeedY
^{2}
To find the actual value, you need to take the square root:
true speed = √(_playerSpeedX
^{2} + _playerSpeedY
^{2})
Add this code to updatePlayer
. First remove this block of code:
And replace it with this block of code:
Build and run. You’ll see the spaceship now acts a lot more stable at the edges of the screen. If you’re wondering where the value 40.0f came from, the answer is: experimentation. I put some NSLog()
statements into the code to look at the speeds at which the craft typically hit the borders, and then I tweaked this value until it felt right.
Of course, fixing one thing always breaks something else. Try slowing down the spaceship until it has stopped, then flip the device so the spaceship has to turn around and fly the other way.
Previously, that happened with a nice animation where you actually saw the ship turning. But because you just added some code that prevents the ship from changing its angle at low speeds, the turn is now very abrupt. It’s only a small detail, but it is the details that make great products.
The fix is to not switch to the new angle immediately, but to gradually “blend” it with the previous angle over a series of successive frames. This reintroduces the turning animation and still prevents the ship from rotating when it is not moving fast enough.
Blending sounds fancy, but it’s actually quite easy to implement. It does require you to keep track of the spaceship’s angle between updates, so add a new instance variable for it in the implementation block in HelloWorldLayer.m:
Change the rotation code in updatePlayer
from this:
To this:
The _playerAngle
variable combines the new angle and its own previous value by multiplying them with a blend factor. In humanspeak, this means the new angle only counts for 20% towards the actual rotation that you set on the spaceship. Of course, over time more and more of the new angle gets added so that eventually the spaceship does point in the proper direction.
Note that the line that sets the _playerSprite
’s rotation property is now outside the if
statement.
Build and run to verify that there is no longer an abrupt change from one rotation angle to another.
Now try flying in a circle a couple of times, both clockwise and counterclockwise. You’ll notice that at some point in the turn the spaceship suddenly spins in the opposite direction. It always happens at the same point in the circle. What’s going on?
Well, there is something you should know about atan2f()
. It does not return an angle in the convenient range of 0 to 360 degrees, but a value between +π and –π radians, or between +180 and 180 degrees to us nonmathematicians:
That means if you’re turning counterclockwise, at some point the angle will jump from +180 degrees to 180 degrees; or the other way around if you’re turning clockwise. And that’s where the weird spinning effect happens.
The problem is that when the new angle jumps from 180 degrees to 180 degrees, _playerAngle
is still positive because it is trailing behind a bit. When you blend these two together, the spaceship actually starts turning the other way around. It took me a while to figure out what was causing this!
To fix it, you need to recognize when the angle makes that jump and adjust _playerAngle
accordingly. Add a new instance variable at the bottom of the HelloWorldLayer
implementation block:
And change the rotation code one more time from this:
To this:
Build and run. That’ll fix things right up and you should have no more problems turning your spacecraft!
This may have seemed like an arcane little problem not worth so much time, but it's good to be aware that atan2f()
gives you angles from 180 degrees (or –π radians) to +180 degrees (or π radians). If you’re using arc tangent to calculate an angle and you get weird behavior like the spinning you saw here, then you might need to verify that your angles really are what you expect them to be.
This is a great start  you have a spaceship moving along pretty smoothly! But so far the spaceship's life is too easy and carefree. Let's spice this up by adding an enemy: a big cannon!
Add two new instance variables in the implementation block in HelloWorldLayer.m:
You'll set these sprites up in init
. Place this code before the creation of _playerSprite
so that the spaceship always gets drawn “above” the cannon:
The cannon consists of two sprites: the fixed base and the turret that can rotate to take aim at the player. Build and run, and you should have a brandnew cannon sitting smack in the middle of the screen.
Now you'll give the cannon a target to snipe at  I bet you know who!
Yep, its turret should point at the player at all times. To get this to work, you need to figure out the angle to rotate the turret so that it points toward the player.
Figuring this out will be very similar to how you figured out how to rotate the spaceship in order to point toward the direction it's moving in. The difference is that this time, the triangle won't be based on the speed of the spaceship. Instead it will be drawn between the center positions of the two sprites:
Again, you can use atan2f()
to calculate this angle. Add the following method:
The deltaX
and deltaY
variables measure the distance between the player sprite and the turret sprite. You plug these values into atan2f()
to get angle of rotation.
As before, you need to convert this angle to degrees and adjust for the differences between mathematics and Cocos2D. Remember that atan2()
always gives you the angle between the hypotenuse and the 0degree line. It is not the angle inside the triangle.
Don’t forget to call this new updateTurret:
method from update
, or nothing much will happen:
Build and run. The cannon is now always pointing at the spaceship. See how easy that was? That’s the power of trig for you!
Challenge: It is unlikely that a real cannon would be able to act so instantaneously – it would have to be able to predict exactly where the target was going. Instead, a real cannon would always be playing catch up.
You can accomplish this by “blending” the old angle with the new one, just like you did earlier. The smaller the blend factor, the more time the turret needs to catch up with the spaceship. See if you can implement this on your own.
Soon the player will be able to fire missiles at the cannon, and the cannon will be able to inflict damage on the player. To show the amount of hit points each object has remaining, you will add health bars to the scene using CCDrawNode
, a new feature of Cocos2D v2.1.
First, you have to do some prep. Add a couple of new constants to the top of the file:
Also add a couple of new instance variables in the implementation block:
Add the following to the bottom of init
to set up these new variables:
The _playerHealthBar
and _cannonHealthBar
objects are instances of CCDrawNode
, which is like a sprite except that you can draw arbitrary shapes into it. That is ideal for your health bars, which consist of a border and a filled rectangle.
Note that you placed the _cannonHealthBar
sprite slightly below the cannon, but didn’t assign a position to the _playerHealthBar
yet. That’s because the cannon never moves, so you can simply set the position of its health bar once and forget about it.
However, whenever the spaceship moves, you have to adjust the position of the _playerHealthBar
as well. That happens in updatePlayer
. Add the following lines to the bottom of that method:
Now all that's left is drawing the bars themselves. Add this new method to the bottom of the file:
This sets up an array of points for the corners of the rectangle that you'll be drawing, then draws it twice: once for the border, which always has the same size, and once for the amount of health, which changes depending on the number of remaining hit points.
Of course, you need to call this method, once for the player and once for the cannon. Add these two lines to update
:
Build and run. Now both the player and the enemy have health bars:
Right now, the spaceship can fly directly over the cannon without consequence. It would be more fun if it didn't have it so easy and instead suffered damage for colliding with the cannon. This is where this tutorial enters the sphere of collision detection (don't miss that pun).
At this point, a lot of game devs think, “I need a physics engine!” and reach for Box2D. While it’s certainly true that you can use Box2D for this, it’s not that hard to do collision detection yourself, especially when you can use circles.
Detecting whether two circles intersect is a piece of cake: all you have to do is look at the distance between them (*cough* Pythagoras) and see if it is smaller than the radius of both circles.
Add two new constants to the top of HelloWorldLayer.m:
These are the sizes of the collision circles around the cannon and the player. Looking at the sprite, you’ll see that the radius of the cannon is slightly larger at 25 points, but it’s nice to have a bit of wiggle room. You don’t want your games to be too unforgiving or players will think it is unfair.
The fact that the spaceship isn’t circular at all shouldn’t deter you. Often a circle is a good enough approximation of the shape of your sprite, and it has the big advantage that it makes it easy to do the necessary calculations. In this case, the body of the ship is roughly 20 points in diameter (remember, the diameter is twice the radius).
Add a new method that does the collision detection:
You’ve seen how this works before: first you calculate the distance between the xpositions of the two sprites, then the ydistance. With these two values, you can calculate the hypotenuse, which is the true distance between these sprites. If that distance is smaller than the collision circles, a sound effect is played.
In update
, add a call to this new method, above the calls to drawHealthBar
:
Also add this line to init
to preload the sound effect:
Give it a whirl and drive the spaceship into the cannon.
Notice that the sound effect is a little odd. It’s because while the spaceship flies over the cannon, the game registers repeated collisions, one after another. There isn’t just one collision, there are many, and it plays the sound effect for every one of them.
Collision detection is only the first step. The second step is collision response. Not only do you want to play a sound effect, but you also want the spaceship to bounce off the cannon.
Add these lines inside the if
statement in checkCollisionOfPlayerWithCannon
:
This is similar to what you did for making the spaceship bounce off the screen borders. Build and run to see how it works.
It works pretty well when the spaceship is going fast when it hits the cannon. But if it's moving too slow, then even after reversing the speed, the ship stays within the collision radius and never makes its way out of it. So this solution has some problems.
Instead of bouncing the ship off the cannon by reversing its existing speed, you will simply expel the ship with a fixed speed so that it always moves outside of the collision area, no matter how fast or slow it was flying when it hit the cannon.
This requires you to add a new constant at the top of HelloWorldLayer.m:
Remove those lines you just added in checkCollisionOfPlayerWithCannon
and replace them with the following:
That is the speed you want the spaceship to have after it collides with the cannon. You need to calculate how much of that speed goes in the xdirection and how much in the ydirection, based on the angle of impact:
You can calculate the angle using arctan because you know the current speed of the player. To calculate how fast the player bounces off the cannon in the x and ydirections, you already have two pieces of information: the angle that you just calculated, and the new speed, CannonCollisionSpeed
, which is the hypotenuse of the triangle.
If you look back at the theory section, you will see that you can calculate the new xcomponent of the speed using the cosine function and the ycomponent using the sine function:
And that’s exactly what you're doing in the code above. Build and run, and you'll see the spaceship now bounces properly off the cannon and loses some hit points in the process.
For an additional effect, you can add spin to the spaceship after a collision. This is additional rotation that doesn't influence the flight direction. It just makes the effect of the collision more profound (and the pilot dizzy). Add a new instance variable in the implementation block:
In checkCollisionOfPlayerWithCannon
, add the following line inside the collision detection block:
This sets the amount of spin to a circle and a half, which I think looks pretty good. Now at the bottom of updatePlayer
, add the following code to add spin to the rotation:
The amount of spin quickly decreases over time – I chose a speed of 720 degrees per second. Once the spin has reached 0, it stops.
Build and run and set that ship spinning!
Here is the full example project from the tutorial up to this point.
Triangles are everywhere! You've seen how you can use this fact to breathe life into your sprites with the various trigonometric functions.
You have to admit, it wasn’t that hard to follow along, was it? Math doesn’t have to be boring if you can apply it to fun projects, such as making games.
But there's more to come: in Part 2 of this Trigonometry for Game Programming series, you'll add missiles to the game, learn more about sine and cosine, and see some other useful ways to put the power of trig to work in your games.
In the meantime, drop by the forums to share how it's going for you so far! Or if you really can't wait until part 2, why not brush up on some more Cocos2D with some of our other tutorials. Alternatively, if you want to start diving into more game development why not take a look at the games starter kits available on this site.
Credits: The graphics for this game are based on a free sprite set by Kenney Vleugels. The sound effects are based on samples from freesound.org.
This is a post by iOS Tutorial Team member Matthijs Hollemans, an experienced iOS developer and designer. You can find him on Google+ and Twitter.
 Have one ship shoot a laser in the direction of the other ship
 Have one ship start moving in the direction of another ship to chase
 Play a warning sound effect if an enemy ship is getting too close
 angle = arcsin(opposite/hypotenuse)
 angle = arccos(adjacent/hypotenuse)
 angle = arctan(opposite/adjacent)
 new xspeed = cos(angle) * hypotenuse
 new yspeed = sin(angle) * hypotenuse
 Set the acceleration based on some form of user input, in this case from the accelerometer values.
 Add the new acceleration to the spaceship’s current speed. This makes the object speed up or slow down, depending on the direction of the acceleration.
 Add the new speed to the spaceship’s position to make it move.

if (_playerSpeedX < MaxPlayerSpeed) { _playerSpeedX = MaxPlayerSpeed; } else if (_playerSpeedX > MaxPlayerSpeed) { _playerSpeedX = MaxPlayerSpeed; }
 Add the current speed to the sprite’s position. Again, speed is measured in points per second, so you need to multiply it by the delta time to make it work correctly.
 Clamp the new position to the sides of the screen. You don’t want the player’s spaceship to go offscreen, never to be found again!
./installtemplates.sh f
#import "cocos2d.h"
@interface HelloWorldLayer : CCLayerColor
+ (CCScene *)scene;
@end
#import "HelloWorldLayer.h"
#import "SimpleAudioEngine.h"
@implementation HelloWorldLayer
+ (CCScene *)scene
{
CCScene *scene = [CCScene node];
HelloWorldLayer *layer = [HelloWorldLayer node];
[scene addChild:layer];
return scene;
}
 (id)init
{
if ((self = [super initWithColor:ccc4(94, 63, 107, 255)]))
{
}
return self;
}
@end
@implementation HelloWorldLayer
{
CGSize _winSize;
CCSprite *_playerSprite;
}
 (id)init
{
if ((self = [super initWithColor:ccc4(94, 63, 107, 255)]))
{
_winSize = [CCDirector sharedDirector].winSize;
_playerSprite = [CCSprite spriteWithFile:@"Player.png"];
_playerSprite.position = ccp(_winSize.width  50.0f, 50.0f);
[self addChild:_playerSprite];
}
return self;
}
@implementation HelloWorldLayer
{
. . .
UIAccelerationValue _accelerometerX;
UIAccelerationValue _accelerometerY;
}
self.accelerometerEnabled = YES;
 (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
const double FilteringFactor = 0.75;
_accelerometerX = acceleration.x * FilteringFactor + _accelerometerX * (1.0  FilteringFactor);
_accelerometerY = acceleration.y * FilteringFactor + _accelerometerY * (1.0  FilteringFactor);
}
@implementation HelloWorldLayer
{
. . .
float _playerAccelX;
float _playerAccelY;
float _playerSpeedX;
float _playerSpeedY;
}
const float MaxPlayerAccel = 400.0f;
const float MaxPlayerSpeed = 200.0f;
if (_accelerometerY > 0.05)
{
_playerAccelX = MaxPlayerAccel;
}
else if (_accelerometerY < 0.05)
{
_playerAccelX = MaxPlayerAccel;
}
if (_accelerometerX < 0.05)
{
_playerAccelY = MaxPlayerAccel;
}
else if (_accelerometerX > 0.05)
{
_playerAccelY = MaxPlayerAccel;
}
 (void)updatePlayer:(ccTime)dt
{
// 1
_playerSpeedX += _playerAccelX * dt;
_playerSpeedY += _playerAccelY * dt;
// 2
_playerSpeedX = fmaxf(fminf(_playerSpeedX, MaxPlayerSpeed), MaxPlayerSpeed);
_playerSpeedY = fmaxf(fminf(_playerSpeedY, MaxPlayerSpeed), MaxPlayerSpeed);
// 3
float newX = _playerSprite.position.x + _playerSpeedX*dt;
float newY = _playerSprite.position.y + _playerSpeedY*dt;
// 4
newX = MIN(_winSize.width, MAX(newX, 0));
newY = MIN(_winSize.height, MAX(newY, 0));
_playerSprite.position = ccp(newX, newY);
}
if (_playerSpeedX < MaxPlayerSpeed)
{
_playerSpeedX = MaxPlayerSpeed;
}
else if (_playerSpeedX > MaxPlayerSpeed)
{
_playerSpeedX = MaxPlayerSpeed;
}
[self scheduleUpdate];
 (void)update:(ccTime)dt
{
[self updatePlayer:dt];
}
float angle = atan2f(_playerSpeedY, _playerSpeedX);
_playerSprite.rotation = angle;
_playerSprite.rotation = CC_RADIANS_TO_DEGREES(angle);
_playerSprite.rotation = 90.0f  CC_RADIANS_TO_DEGREES(angle);
//newX = MIN(_winSize.width, MAX(newX, 0));
//newY = MIN(_winSize.height, MAX(newY, 0));
BOOL collidedWithVerticalBorder = NO;
BOOL collidedWithHorizontalBorder = NO;
if (newX < 0.0f)
{
newX = 0.0f;
collidedWithVerticalBorder = YES;
}
else if (newX > _winSize.width)
{
newX = _winSize.width;
collidedWithVerticalBorder = YES;
}
if (newY < 0.0f)
{
newY = 0.0f;
collidedWithHorizontalBorder = YES;
}
else if (newY > _winSize.height)
{
newY = _winSize.height;
collidedWithHorizontalBorder = YES;
}
const float BorderCollisionDamping = 0.4f;
if (collidedWithVerticalBorder)
{
_playerAccelX = _playerAccelX * BorderCollisionDamping;
_playerSpeedX = _playerSpeedX * BorderCollisionDamping;
_playerAccelY = _playerAccelY * BorderCollisionDamping;
_playerSpeedY = _playerSpeedY * BorderCollisionDamping;
}
if (collidedWithHorizontalBorder)
{
_playerAccelX = _playerAccelX * BorderCollisionDamping;
_playerSpeedX = _playerSpeedX * BorderCollisionDamping;
_playerAccelY = _playerAccelY * BorderCollisionDamping;
_playerSpeedY = _playerSpeedY * BorderCollisionDamping;
}
float angle = atan2f(_playerSpeedY, _playerSpeedX);
_playerSprite.rotation = 90.0f  CC_RADIANS_TO_DEGREES(angle);
float speed = sqrtf(_playerSpeedX*_playerSpeedX + _playerSpeedY*_playerSpeedY);
if (speed > 40.0f)
{
float angle = atan2f(_playerSpeedY, _playerSpeedX);
_playerSprite.rotation = 90.0f  CC_RADIANS_TO_DEGREES(angle);
}
@implementation HelloWorldLayer
{
...
float _playerAngle;
}
float speed = sqrtf(_playerSpeedX*_playerSpeedX + _playerSpeedY*_playerSpeedY);
if (speed > 40.0f)
{
float angle = atan2f(_playerSpeedY, _playerSpeedX);
_playerSprite.rotation = 90.0f  CC_RADIANS_TO_DEGREES(angle);
}
float speed = sqrtf(_playerSpeedX*_playerSpeedX + _playerSpeedY*_playerSpeedY);
if (speed > 40.0f)
{
float angle = atan2f(_playerSpeedY, _playerSpeedX);
const float RotationBlendFactor = 0.2f;
_playerAngle = angle * RotationBlendFactor + _playerAngle * (1.0f  RotationBlendFactor);
}
_playerSprite.rotation = 90.0f  CC_RADIANS_TO_DEGREES(_playerAngle);
@implementation HelloWorldLayer
{
...
float _lastAngle;
}
float speed = sqrtf(_playerSpeedX*_playerSpeedX + _playerSpeedY*_playerSpeedY);
if (speed > 40.0f)
{
float angle = atan2f(_playerSpeedY, _playerSpeedX);
const float RotationBlendFactor = 0.2f;
_playerAngle = angle * RotationBlendFactor + _playerAngle * (1.0f  RotationBlendFactor);
}
_playerSprite.rotation = 90.0f  CC_RADIANS_TO_DEGREES(_playerAngle);
float speed = sqrtf(_playerSpeedX*_playerSpeedX + _playerSpeedY*_playerSpeedY);
if (speed > 40.0f)
{
float angle = atan2f(_playerSpeedY, _playerSpeedX);
// Did the angle flip from +Pi to Pi, or Pi to +Pi?
if (_lastAngle < 3.0f && angle > 3.0f)
{
_playerAngle += M_PI * 2.0f;
}
else if (_lastAngle > 3.0f && angle < 3.0f)
{
_playerAngle = M_PI * 2.0f;
}
_lastAngle = angle;
const float RotationBlendFactor = 0.2f;
_playerAngle = angle * RotationBlendFactor + _playerAngle * (1.0f  RotationBlendFactor);
}
_playerSprite.rotation = 90.0f  CC_RADIANS_TO_DEGREES(_playerAngle);
@implementation HelloWorldLayer
{
...
CCSprite *_cannonSprite;
CCSprite *_turretSprite;
}
_cannonSprite = [CCSprite spriteWithFile:@"Cannon.png"];
_cannonSprite.position = ccp(_winSize.width/2.0f, _winSize.height/2.0f);
[self addChild:_cannonSprite];
_turretSprite = [CCSprite spriteWithFile:@"Turret.png"];
_turretSprite.position = ccp(_winSize.width/2.0f, _winSize.height/2.0f);
[self addChild:_turretSprite];
 (void)updateTurret:(ccTime)dt
{
float deltaX = _playerSprite.position.x  _turretSprite.position.x;
float deltaY = _playerSprite.position.y  _turretSprite.position.y;
float angle = atan2f(deltaY, deltaX);
_turretSprite.rotation = 90.0f  CC_RADIANS_TO_DEGREES(angle);
}
 (void)update:(ccTime)dt
{
[self updatePlayer:dt];
[self updateTurret:dt];
}
const int MaxHP = 100;
const float HealthBarWidth = 40.0f;
const float HealthBarHeight = 4.0f;
@implementation HelloWorldLayer
{
...
int _playerHP;
int _cannonHP;
CCDrawNode *_playerHealthBar;
CCDrawNode *_cannonHealthBar;
}
_playerHealthBar = [[CCDrawNode alloc] init];
_playerHealthBar.contentSize = CGSizeMake(HealthBarWidth, HealthBarHeight);
[self addChild:_playerHealthBar];
_cannonHealthBar = [[CCDrawNode alloc] init];
_cannonHealthBar.contentSize = CGSizeMake(HealthBarWidth, HealthBarHeight);
[self addChild:_cannonHealthBar];
_cannonHealthBar.position = ccp(
_cannonSprite.position.x  HealthBarWidth/2.0f + 0.5f,
_cannonSprite.position.y  _cannonSprite.contentSize.height/2.0f  10.0f + 0.5f);
_playerHP = MaxHP;
_cannonHP = MaxHP;
_playerHealthBar.position = ccp(
_playerSprite.position.x  HealthBarWidth/2.0f + 0.5f,
_playerSprite.position.y  _playerSprite.contentSize.height/2.0f  15.0f + 0.5f);
 (void)drawHealthBar:(CCDrawNode *)node hp:(int)hp
{
[node clear];
CGPoint verts[4];
verts[0] = ccp(0.0f, 0.0f);
verts[1] = ccp(0.0f, HealthBarHeight  1.0f);
verts[2] = ccp(HealthBarWidth  1.0f, HealthBarHeight  1.0f);
verts[3] = ccp(HealthBarWidth  1.0f, 0.0f);
ccColor4F clearColor = ccc4f(0.0f, 0.0f, 0.0f, 0.0f);
ccColor4F fillColor = ccc4f(113.0f/255.0f, 202.0f/255.0f, 53.0f/255.0f, 1.0f);
ccColor4F borderColor = ccc4f(35.0f/255.0f, 28.0f/255.0f, 40.0f/255.0f, 1.0f);
[node drawPolyWithVerts:verts count:4 fillColor:clearColor borderWidth:1.0f borderColor:borderColor];
verts[0].x += 0.5f;
verts[0].y += 0.5f;
verts[1].x += 0.5f;
verts[1].y = 0.5f;
verts[2].x = (HealthBarWidth  2.0f)*hp/MaxHP + 0.5f;
verts[2].y = 0.5f;
verts[3].x = verts[2].x;
verts[3].y += 0.5f;
[node drawPolyWithVerts:verts count:4 fillColor:fillColor borderWidth:0.0f borderColor:clearColor];
}
[self drawHealthBar:_playerHealthBar hp:_playerHP];
[self drawHealthBar:_cannonHealthBar hp:_cannonHP];
const float CannonCollisionRadius = 20.0f;
const float PlayerCollisionRadius = 10.0f;
 (void)checkCollisionOfPlayerWithCannon
{
float deltaX = _playerSprite.position.x  _turretSprite.position.x;
float deltaY = _playerSprite.position.y  _turretSprite.position.y;
float distance = sqrtf(deltaX*deltaX + deltaY*deltaY);
if (distance <= CannonCollisionRadius + PlayerCollisionRadius)
{
[[SimpleAudioEngine sharedEngine] playEffect:@"Collision.wav"];
}
}
[self checkCollisionOfPlayerWithCannon];
[[SimpleAudioEngine sharedEngine] preloadEffect:@"Collision.wav"];
const float CannonCollisionDamping = 0.8f;
_playerAccelX = _playerAccelX * CannonCollisionDamping;
_playerSpeedX = _playerSpeedX * CannonCollisionDamping;
_playerAccelY = _playerAccelY * CannonCollisionDamping;
_playerSpeedY = _playerSpeedY * CannonCollisionDamping;
const float CannonCollisionSpeed = 200.0f;
float angle = atan2f(deltaY, deltaX);
_playerSpeedX = cosf(angle) * CannonCollisionSpeed;
_playerSpeedY = sinf(angle) * CannonCollisionSpeed;
_playerAccelX = 0.0f;
_playerAccelY = 0.0f;
_playerHP = MAX(0, _playerHP  20);
_cannonHP = MAX(0, _cannonHP  5);
@implementation HelloWorldLayer
{
...
float _playerSpin;
}
if (distance <= CannonCollisionRadius + PlayerCollisionRadius)
{
...
_playerSpin = 180.0f * 3.0f;
}
_playerSprite.rotation += _playerSpin;
if (_playerSpin > 0.0f)
{
_playerSpin = 2.0f * 360.0f * dt;
if (_playerSpin < 0.0f)
{
_playerSpin = 0.0f;
}
}