sup { fontsize: 80%; verticalalign:super; } blockquote { paddingleft: 30px }
Note from Ray: Tony has ported this popular tutorial from Cocos2D to Sprite Kit. We hope you enjoy!
Welcome back to the Trigonometry for Game Programming series!
In the first part of the series, you learned the basics of trigonometry and experienced for yourself how useful it is for making games. You saw that math doesn’t have to be boring – as long as you have a fun project to apply it to, such as making your own games.
Oh yes, and you built the foundation for a solid little space shooter game!
In this second and final part of the series, you will add missiles, an orbiting asteroid shield and an animated “game over” screen to your game. Along the way, you’ll learn more about sine and cosine and see some other useful ways to put the power of trig to work in your games.
Get ready to ride the sine wave back into space!
Getting Started
This tutorial picks up where you left off in the last part. If you don’t have it already, here is the project with all of the source code up to this point.
As of right now, your game has a spaceship and a rotating cannon, each with health bars. While they may be sworn enemies, neither has the ability to damage the other, unless the spaceship flies right into the cannon.
It’s time for some fireworks. You will now give the player the ability to fire missiles at the cannon by swiping the screen. The spaceship will then launch a missile in the direction of that swipe. For that, you first need to enable touch handling on the layer.
Open the Xcode project and add these instance variables to MyScene.m:
@implemenation MyScene { ... SKSpriteNode *_playerMissileSprite; CGPoint _touchLocation; CFTimeInterval _touchTime; } 
Also add these lines at the bottom of the code inside initWithSize
:
_playerMissileSprite = [SKSpriteNode spriteNodeWithImageNamed:@"PlayerMissile"]; _playerMissileSprite.hidden = YES; [self addChild:_playerMissileSprite]; 
You initially set the missile sprite to be hidden and only make it visible when the player fires.
Now add the methods to handle the touches, starting with the one that is called whenever the user first puts her finger on the touchscreen:
 (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint location = [touch locationInNode:self]; _touchLocation = location; _touchTime = CACurrentMediaTime(); } 
This is pretty simple – you store the touch location and the time of the touch in the new instance variables.
The actual work happens in touchesEnded
, which you add next:
 (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if (CACurrentMediaTime()  _touchTime < 0.3 && _playerMissileSprite.hidden) { UITouch *touch = [touches anyObject]; CGPoint location = [touch locationInNode:self]; CGPoint diff = CGPointMake(location.x  _touchLocation.x, location.y  _touchLocation.y); float diffLength = sqrtf(diff.x*diff.x + diff.y*diff.y); if (diffLength > 4.0f) { // TODO: more code here } } } 
This determines whether there hasn’t been too much time between starting and ending the swipe, 0.3 seconds at most (otherwise, the missle doesn’t fire). Also, the player can only shoot one missile at a time, so the touch is ignored if a missile is already flying.
The last bit figures out what sort of gesture the user made: was it really a swipe, or just a tap? You only launch missiles on swipes, not taps.
Note: You have done this sort of calculation a couple of times already – subtract two coordinates, then use the Pythagorean Theorem to find the distance between them.
It is common in games to combine the x and y values into one type such as CGPoint
. Such a value is referred to as a vector, a topic large enough that it deserves its own tutorial.
There are two ways you could make the missile fly.
The first would be to create new _playerMissileSpeedX
and Y
instance variables and fill them in with the correct speed values, based on the angle that you’re aiming the missile. Inside update
, you would then add this speed to the missile sprite’s position, and check whether the missile flew outside of the visible screen so the player can fire again. This is very similar to how you made the spaceship move in Part 1 of this tutorial.
However, the missile doesn’t need to change course on its way: it always flies in a straight line. So you can take an alternative approach: calculating the destination point when you launch the missile. Then you can set a moveTo
action on the missile sprite and let Sprite Kit handle the rest. This also saves you from having to check whether the missile has left the visible screen.
I bet you saw this coming: the second approach is exactly what you are going to implement right now, and you will use trig to do it.
To begin, add the following lines to the TODO section in touchesEnded
:
float angle = atan2f(diff.y, diff.x); _playerMissileSprite.zRotation = angle  SK_DEGREES_TO_RADIANS(90.0f); _playerMissileSprite.position = _playerSprite.position; _playerMissileSprite.hidden = NO; 
This is nothing fancy. You calculate the angle, set up the sprite’s rotation and position, and make the missile sprite visible.
Now, however, comes the math. You know the starting position of the missile (which is the position of the player’s ship) and you know the angle (derived from the player’s swipe motion). What you need to calculate now is the destination point of the missile based on these facts.
There are actually four different situations that you need to handle, so let’s first consider the one where the user swiped in a downward motion:
The destination point always lies just outside the screen instead of exactly on the edge of the screen, because it looks better if the missile completely flies out of sight. For this purpose, add a constant at the top of MyScene.m and call it Margin:
const float Margin = 20.0f; 
To find the destination point, you need more than just the angle. Fortunately, if you know that the player is shooting downward, you also know the vertical distance the missile needs to fly. That is simply the starting yposition of the missile, plus the margin. (Remember that the bottom of the screen is y = 0 in Sprite Kit.)
Now you have the angle and the adjacent side. To get the destination point, you simply need to calculate the opposite side of the triangle and add it to the starting xposition of the missile.
That sounds like a job for the tan() function – not the arc tangent that you’ve been using up to now, but the normal one:
tan(angle) = opposite / adjacent
Or for your purposes:
opposite = tan(angle) * adjacent
There’s only one snag. The angle that is pictured in the above diagram isn’t really the angle you measured, because the angle you get back from atan2f()
is always relative to the 0 degree line of the circle:
Note that in this particular diagram, the adjacent and opposite sides are swapped – it is now the adjacent you’re looking for. So the formula becomes:
adjacent = opposite / tan(angle)
You could probably make this work, but that division makes me cringe. What if tan(angle) happens to be 0? Then you’ll have a divisionbyzero on your hands. I’d much rather stick to the first formula, the one that calculates the opposite.
It turns out that this is actually no problem if you remember that beta = (90 – alpha). In other words, if you position yourself in the opposite corner of the triangle and use an angle of (90 – alpha), then you can use that first formula without problems.
Be careful: now that the opposite side points the other way around, you need to subtract it rather than add it to the starting xposition.
Let’s put all this in code. Modify the touchesEnded
method to look like the following:
if (diffLength > 4.0f) { float angle = atan2f(diff.y, diff.x); _playerMissileSprite.zRotation = angle  SK_DEGREES_TO_RADIANS(90.0f); _playerMissileSprite.position = _playerSprite.position; _playerMissileSprite.hidden = NO; float adjacent, opposite; CGPoint destination; // 1 angle = M_PI_2  angle; adjacent = _playerMissileSprite.position.y + Margin; opposite = tanf(angle) * adjacent; destination = CGPointMake(_playerMissileSprite.position.x  opposite, Margin); // 2 //set up the sequence of actions for the firing SKAction *missileMoveAction = [SKAction moveTo:destination duration:2.0f]; SKAction *missileDoneMoveAction = [SKAction runBlock:(dispatch_block_t)^() { _playerMissileSprite.hidden = YES; }]; SKAction *moveMissileActionWithDone = [SKAction sequence:@[_missileShootSound, missileMoveAction, missileDoneMoveAction]]; [_playerMissileSprite runAction:moveMissileActionWithDone]; } 
Add the following line to the end of the @implemenation MyScene
section:
SKAction *_missileShootSound;

Also add this line at the bottom of the code inside initWithSize
method:
_missileShootSound = [SKAction playSoundFileNamed:@"Shoot.wav" waitForCompletion:NO]; 
This does two things:
 It adjusts the angle so you’re looking at it from the other corner. It then calculates the length of the adjacent side, and uses the tangent function to find the length of the opposite side. Finally, it calculates the destination coordinate.
 It creates a sequence of actions (each runs one after the other), starting with a sound action, followed by
moveTo
. Once the sprite has reached its destination, themissileDoneMoveAction
action will make the sprite invisible again.
Build and run, and try it out. Move the ship so that it is about halfway centered on the screen and then swipe down. You should see a missile fly along the path that you swiped. Note that you can only fire one missile at a time – the player should have to wait until the previous missile has disappeared from the screen before firing again.
If you try swiping in any other direction, you’ll notice that the missile doesn’t quite fly where you want. That’s because currently you have only handled the situation where the destination point lies below the bottom of the screen. Each of the other screen edges requires slightly different calculations.
Replace the code that calculates the destination point (the “// 1″ section) with this:
if (angle <= M_PI_4 && angle > 3.0f * M_PI_4) { // Shoot down angle = M_PI_2  angle; adjacent = _playerMissileSprite.position.y + Margin; opposite = tanf(angle) * adjacent; destination = CGPointMake(_playerMissileSprite.position.x  opposite, Margin); } else if (angle > M_PI_4 && angle <= 3.0f * M_PI_4) { // Shoot up angle = M_PI_2  angle; adjacent = _winSize.height  _playerMissileSprite.position.y + Margin; opposite = tanf(angle) * adjacent; destination = CGPointMake(_playerMissileSprite.position.x + opposite, _winSize.height + Margin); } else if (angle <= M_PI_4 && angle > M_PI_4) { // Shoot right adjacent = _winSize.width  _playerMissileSprite.position.x + Margin; opposite = tanf(angle) * adjacent; destination = CGPointMake(_winSize.width + Margin, _playerMissileSprite.position.y + opposite); } else // angle > 3.0f * M_PI_4  angle <= 3.0f * M_PI_4 { // Shoot left adjacent = _playerMissileSprite.position.x + Margin; opposite = tanf(angle) * adjacent; destination = CGPointMake(Margin, _playerMissileSprite.position.y  opposite); } 
You have actively divided the screen into four sections, like slices of a pie:
Each of these sections calculates a slightly different destination point. Notice that the code for shooting left comes last, because here you have the issue of switching from +180 to 180 degrees again (recall that this is the range of values that atan2()
returns).
Now you should be able to fire in any direction! Build and run and give it a try.
There is still one problem (how could there not be?). Sometimes the missile appears to be going faster than at other times.
That’s because currently, the duration of the animation is hardcoded to last 2 seconds. If the missile needs to travel far, then it goes faster in order to cover more distance in the same amount of time.
Instead what you want is for each missile to always travel at the same speed. The hypotenuse comes to the rescue once more!
First, add a new constant at the top of MyScene.m:
const float PlayerMissileSpeed = 300.0f; 
This is the distance that you want the missile to travel per second. By calculating the length of the hypotenuse, you know the actual distance that the missile travels. To get the needed duration of the animation, you divide those two distances.
Sounds complicated? Nah…
In touchesEnded
, change the code that creates the missileMoveAction
action (the // 2 section) to the following:
float hypotenuse = sqrtf(adjacent*adjacent + opposite*opposite); NSTimeInterval duration = hypotenuse / PlayerMissileSpeed; //set up the sequence of actions for the firing SKAction *missileMoveAction = [SKAction moveTo:destination duration:duration]; 
That’s all there is to it. Build and run the app again. Now the missile always flies at the same speed, no matter how far or close the destination point is.
And that’s how you use trig to set up a moveTo
action. It’s a bit involved, but then it’s largely fire & forget because Sprite Kit does all the work of animating for you.
Hitting Your Targets
Right now, the missile completely ignores the cannon – your spaceship might as well be firing a beam of green light!
That’s about to change. As before, you will use a simple radiusbased method of collision detection.
Add a new constant to the source at the top of MyScene.m:
const float CannonHitRadius = 25.0f; 
For simplicity’s sake, you will assume that the missile itself doesn’t have a radius, so whenever its center point goes within the cannon hit radius, that will be sufficient to register as a collision. That’s precise enough for this game.
In preparation to play the hit sound, add an instance variable at the bottom of the implementation
for the sound action:
SKAction *_missileHitSound;

And initialize the SKAction in the initWithSize
method, by adding this line at the bottom:
_missileHitSound = [SKAction playSoundFileNamed:@"Hit.wav" waitForCompletion:NO]; 
Now, add a new method:
 (void)updatePlayerMissile:(NSTimeInterval)dt { if (!_playerMissileSprite.hidden) { float deltaX = _playerMissileSprite.position.x  _turretSprite.position.x; float deltaY = _playerMissileSprite.position.y  _turretSprite.position.y; float distance = sqrtf(deltaX*deltaX + deltaY*deltaY); if (distance < CannonHitRadius) { [self runAction:_missileHitSound]; _cannonHP = MAX(0, _cannonHP  10); _playerMissileSprite.hidden = YES; [_playerMissileSprite removeAllActions]; } } } 
This should be old hat by now: you calculate the distance and then play a sound effect via a Sprite Kit SKAction when the distance has become smaller than the hit radius.
Note that you need to call removeAllActions
on the missile sprite, because you want it to stop moving. This tells the moveTo
SKaction that it is no longer needed.
Call this new method from within update
. Place it directly below the call to updatePlayer
.
[self updatePlayerMissile:_deltaTime]; 
Build and run, then try it out. Finally you can inflict some damage on the enemy!
Challenges for the 1337
Here’s a challenge for you: can you make the cannon shoot back at the spaceship?
Try to see if you can figure it out – you already know all the required pieces, and this will be some really good practice to make sure you understand what we’ve covered so far.
Try it out for yourself before you look at the solution!
Solution Inside: Make the cannon shoot back  SelectShow 

To implement this on your own, all you have to do is create a new missile sprite (using CannonMissile.png), calculate the destination, and send the missile on its way. You know the angle because the turret already points at the player. The destination point for the Collision detection with the player works the same way as before: the missile has hit as soon as the distance between the missile and the player becomes less than a certain radius. To make the game extra challenging for the player, allow the cannon to shoot more than one missile at a time. 
Got that, and think you’re some hot stuff? Here’s another challenge for you!
Currently your missiles fly to their destination point in a straight line. But what if the missiles were heatseeking? A heatseeking missile adjusts its course when it detects that the player has moved.
You’ve got the power of trig on your side, so how would you do it? Hint: instead of calculating the speed and direction of the missile just once, you would do it again and again on each frame. Give it a try.
Solution Inside: Make the missile heat seeking  SelectShow 

Since you’re calculating the speed and direction of the missile on each frame, you can no longer use a Make sure to give the guided missile a limited lifetime, so the player can avoid it if he keeps dodging it long enough, or the game might become a bit too hard to play! 
How’d you do? Is your spaceship dodging guided missiles like Tom Cruise, or still flying around scotfree?
Adding an Orbiting Shield
To make the game more challenging, you will give the enemy a shield. The shield will be a magical asteroid sprite that orbits the cannon and destroys any missiles that come near it.
Add a couple more constants to the top of MyScene.m:
const float OrbiterSpeed = 120.0f; // degrees per second const float OrbiterRadius = 60.0f; // degrees const float OrbiterCollisionRadius = 20.0f; 
And some new instance variables as well:
@implementation MyScene { ... SKSpriteNode *_orbiterSprite; float _orbiterAngle; // in degrees } 
I added the “in degrees” comments above because unlike the other angles, which were in radians, here you’ll work with degrees. They are just easier to wrap your head around.
Initialize the new sprite inside initWithSize
after all the previous code:
_orbiterSprite = [SKSpriteNode spriteNodeWithImageNamed:@"Asteroid"]; [self addChild:_orbiterSprite]; 
And add a new method:
 (void)updateOrbiter:(NSTimeInterval)dt { // 1 _orbiterAngle += OrbiterSpeed * dt; _orbiterAngle = fmodf(_orbiterAngle, 360.0f); // 2 float x = cosf(SK_DEGREES_TO_RADIANS(_orbiterAngle)) * OrbiterRadius; float y = sinf(SK_DEGREES_TO_RADIANS(_orbiterAngle)) * OrbiterRadius; // 3 _orbiterSprite.position = CGPointMake(_cannonSprite.position.x + x, _cannonSprite.position.y + y); } 
The asteroid will orbit around the cannon. In other words, it describes a circular path, round and round and round and round. To accomplish this, you need two pieces: the radius that determines how far the asteroid is from the center of the cannon, and the angle that describes how far it has rotated around that center point.
This is what updateOrbiter
does:
 It increments the angle by a certain speed (from the
OrbiterSpeed
constant), adjusted for the delta time. Because we as humans like to think of angles as anything between 0 and 360 degrees, you usefmodf()
to wrap the angle around back to 0 once it becomes greater than 360. You cannot use the % operator for this, because it only works on integers, butfmodf()
does the same thing for floats (andfmod()
does the same thing for doubles). Wrapping the angles isn’t strictly necessary but it helps you stay sane when you’re trying to debug something like this.  It calculates the new x and ypositions for the orbiter using the sine and cosine functions. These take the radius (which forms the hypotenuse of the triangle) and the current angle, and return the adjacent and opposite sides, respectively. More about this in a second.
 It sets the new position of the sprite by adding the x and ypositions to the center position of the cannon.
You have briefly seen sinf()
and cosf()
in action, but it may not have been entirely clear how they worked. Sure, you have the formulas memorized (really? :]) and you know that both of these functions can be used to calculate the lengths of the other sides, once you have an angle and the hypotenuse.
But aren’t you curious why you can actually do that? I thought you were!
Let’s draw a circle:
The illustration above exactly depicts the situation of the asteroid orbiting around the cannon. The circle describes the path of the asteroid and the origin of the circle is the center of the cannon.
The angle starts at zero degrees but increases all the time until it ends up right back at the beginning. As you can see it, is the radius of the circle that determines how far away from the center the asteroid is placed.
So, given the angle and the radius, you can derive the x and ypositions using the cosine and sine, respectively:
Now let’s take a look at a plot of a sine wave and a cosine wave:
On the horizontal axis are the degrees of a circle, from 0 to 360, or 0 to 2π radians if you’re a mathematician. The vertical axis usually goes from 1 to +1, but if your circle has a radius that is greater than one (and they tend to) then the vertical axis really goes from –radius to +radius.
As the angle increases from 0 to 360, find the angle on the horizontal axis in the plots for the cosine and sine waves. The vertical axis then tells you what the values for x and y are:
 If the angle is 0 degrees, then cos(0) is 1*radius but sin(0) is 0*radius. That corresponds exactly to the (x, y) coordinate in the circle: x is equal to the radius, but y is 0.
 If the angle is 45 degrees, then cos(45) is 0.707*radius and so is sin(45). This means x and y are both the same at this point on the circle. (Note: if you’re trying this out on a calculator, then switch it to DEG mode first. You’ll get radically different answers if it’s in RAD mode, no pun intended.)
 If the angle is 90 degrees, then cos(90) is 0*radius and sin(90) is 1*radius. You’re now at the top of the circle where the (x, y) coordinate is (0, radius).
 And so on… To get a more intuitive feel for how the coordinates in the circle relate to the values of the sine, cosine and even tangent functions, try out this cool interactive circle.
Make sense? Awesome. Did you also notice that the curves of the sine and cosine are very similar? In fact, the cosine wave is simply the sine wave shifted by 90 degrees. Go ahead and impress your friends and family with your knowledge of the mathematical origins of sine and cosine. :]
Back to coding. Add a call to updateOrbiter
at the bottom of update
:
(void)update:(NSTimeInterval)currentTime { ... [self updateOrbiter:_deltaTime]; } 
Build and run the app. You should now have an asteroid that perpetually circles the enemy cannon.
You can also make the asteroid spin around its own axis. Add the following line to the bottom of updateOrbiter
:
_orbiterSprite.zRotation = SK_DEGREES_TO_RADIANS(_orbiterAngle); 
By setting the rotation to _orbiterAngle
, the asteroid always stays oriented in the same position relative to the cannon, much like the moon always shows the same side to the earth. Even though it looks like it isn’t spinning, it certainly is!
Insert a minus sign to give the asteroid extra spin that you can see. Pick whichever effect you like best. Build and run to play around with it for a bit.
Let’s give the orbiter a purpose. If the missile comes too close, the asteroid will destroy it before it gets a chance to do any damage to the cannon. Add the following at the bottom of updateOrbiter
:
if (!_playerMissileSprite.hidden) { float deltaX = _playerMissileSprite.position.x  _orbiterSprite.position.x; float deltaY = _playerMissileSprite.position.y  _orbiterSprite.position.y; float distance = sqrtf(deltaX*deltaX + deltaY*deltaY); if (distance < OrbiterCollisionRadius) { _playerMissileSprite.hidden = YES; [_playerMissileSprite removeAllActions]; _orbiterSprite.scale = 2.0f; [_orbiterSprite runAction:[SKAction scaleTo:1.0f duration:0.5f]]; } } 
No surprises for you here. It’s the same code you’ve seen several times now. Just remember to stop the moveTo
action that is on the missile sprite. For added visual effect, you also scale the asteroid sprite momentarily. This makes it look like the orbiter “ate” the missile.
Build and run to see your new orbiting shield in action.
Game Over, With Trig!
There is still more that you can do with sines and cosines. They’re not just useful for calculating things with triangles – they also come in handy for animations.
A good place to show an example of such an animation is the game over screen. Add a few new instance variables in the implementation block at the top of MyScene.m:
@implementation MyScene { ... SKLabelNode *_gameOverLabel; SKSpriteNode *_darkenLayer; BOOL _gameOver; CFTimeInterval _gameOverElapsed; } 
And a new method:
 (void)checkGameOver:(NSTimeInterval)dt { // 1 if (_playerHP > 0 && _cannonHP > 0) // not game over yet { return; } if (!_gameOver) { // 2 _gameOver = YES; _gameOverElapsed = 0.0; [self stopMonitoringAcceleration]; // 3 UIColor *fillColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:1.0f]; _darkenLayer = [SKSpriteNode spriteNodeWithColor:fillColor size:_winSize]; _darkenLayer.alpha = 0.0; _darkenLayer.position = CGPointMake(_winSize.width/2.0f, _winSize.height/2.0f); [self addChild:_darkenLayer]; // 4 NSString *text = (_playerHP == 0) ? @"GAME OVER" : @"Victory!"; _gameOverLabel = [[SKLabelNode alloc] initWithFontNamed:@"Helvetica"]; _gameOverLabel.text = text; _gameOverLabel.fontSize = 24.0f; _gameOverLabel.position = CGPointMake(_winSize.width/2.0f + 0.5f, _winSize.height/2.0f + 50.0f); [self addChild:_gameOverLabel]; } else { // 5 if (_darkenLayer.alpha < 200) { float newOpacity = fminf(200.0f, _darkenLayer.alpha + 255.0f * dt); _darkenLayer.alpha = newOpacity; } } } 
This method both checks whether the game is done, and if so, handles the game over animation:
 The game keeps on going until either the player or the cannon runs out of hit points.
 When the game is over, set
_gameOver
to YES and disable the accelerometer.  Create a new, allblack color layer and add it on top of everything else. Set its opacity to 0 so that it is completely seethrough. Elsewhere in this method you will animate the opacity value of this layer so that it appears to fade in.
 Add a new text label and place it on the screen. The text is either “Victory!” if the player won or “Game Over” if the player lost.
 The above steps only happen once to set up the game over screen – every time after that, the code enters the
else
clause. Here you animate the opacity of the new color layer from 0 to 200 – almost completely opaque, but not quite.
Call checkGameOver
at the bottom of update
:
(void)update:(NSTimeInterval)currentTime { ... [self checkGameOver:_deltaTime]; } 
And add a small snippet of logic to the top of touchesEnded
:
 (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if (_gameOver) { SKScene *scene = [[MyScene alloc] initWithSize:self.size]; SKTransition *reveal = [SKTransition flipHorizontalWithDuration:1.0]; [self.view presentScene:scene transition:reveal]; return; } ... } 
This restarts the game when the user taps on the game over screen.
Build and run, then try it out. Shoot at the cannon or collide your ship with it until one of you runs out of hit points. The screen will fade to black and the game over text will appear:
The game no longer responds to the accelerometer, but the animations still keep going. That gives it a nice twist!
This is all fine and dandy, but where are the sine and cosines? As you may have noticed, the fade in animation of the black layer was very linear. It just goes from transparent to opaque in a conventional fashion.
You can be better than conventional – you can use sinf()
to subtly change the timing. This is often known as “easing” and the effect you will apply in particular is known as an “ease out”.
Add the new constant at the top of MyScene.m:
const CFTimeInterval DarkenDuration = 2.0; 
Next, change the code in the else
statement in checkGameOver
to:
 (void)checkGameOver:(NSTimeInterval)dt { ... } else { _gameOverElapsed += dt; if (_gameOverElapsed < DarkenDuration) { float t = _gameOverElapsed / DarkenDuration; t = sinf(t * M_PI_2); // ease out _darkenLayer.alpha = (200.0f * t)/255.0; } } } 
_gameOverElapsed
keeps track of how much time has passed since the game ended. It takes two seconds to fade in the black layer (DarkenDuration
). The variable t
determines how much of that duration has passed by. It always has a value between 0.0 and 1.0, regardless of how long DarkenDuration
really is.
Then you perform the magic trick:
t = sinf(t * M_PI_2); // ease out 
This converts t
from a linear interpolation into one that breathes a bit more life into things:
Build and run to see the new “ease out” effect. If you find it hard to see the difference, then try it with that “magic” line commented out, or change the duration of the animation. The effect is subtle, but it’s there.
Note: If you want to play with the values and test things out quick you can comment out the return in the if (_playerHP > 0 && _cannonHP > 0) // not game over yet
block.
There is one more thing I’d like to show you. Let’s make the game over text bounce, because things that bounce are always more fun.
Inside that else
clause in checkGameOver
, add the following so that the method looks like so:
 (void)checkGameOver:(NSTimeInterval)dt { ... } else { _gameOverElapsed += dt; if (_gameOverElapsed < DarkenDuration) { float t = _gameOverElapsed / DarkenDuration; t = sinf(t * M_PI_2); // ease out _darkenLayer.alpha = (200.0f * t)/255.0; } // Game Over Label Position float y = fabsf(cosf(_gameOverElapsed * 3.0f)) * 50.0f; _gameOverLabel.position = CGPointMake(_gameOverLabel.position.x, _winSize.height/2.0f + y); } } 
OK, what’s happening here? Recall what a cosine looks like:
If you take the absolute value of cosf()
– using fabsf()
– then the section that would previously go below zero is flipped. The curve already looks like something that bounces, don’t you think?
Because the output of these functions lies between 0.0 and 1.0, you multiply it by 50 to stretch it out a little. The argument to cosf()
is normally an angle, but you’re giving it the _gameOverElapsed
time to make the cosine move forward through its curve.
The factor 3.0 is just to make it go a bit faster. You can tinker with these values until you have something that you think looks cool.
Build and run to check out the bouncing text:
You’ve used the shape of the cosine to describe the bouncing motion of the text label. These cosines are useful for all sorts of things!
One last thing you can do is let the bouncing motion lose height over time. You do this by adding a damping factor. Create a new instance variable in the MyScene
implementation block:
@implementation MyScene { ... float _gameOverDampen; } 
In checkGameOver
, set _gameOverDampen
. Do this at the beginning of the if
block, like so:
 (void)checkGameOver:(NSTimeInterval)dt { ... if (!_gameOver) { // 2 _gameOver = YES; _gameOverDampen = 1.0f; ... } ... } 
In the else
block, change the code underneath the “// Game Over Label Position” comment to be the following:
float y = fabsf(cosf(_gameOverElapsed * 3.0f)) * 50.0f * _gameOverDampen; _gameOverDampen = fmaxf(0.0f, _gameOverDampen  0.3f * dt); _gameOverLabel.position = CGPointMake(_gameOverLabel.position.x, _winSize.height/2.0f + y); 
It’s mostly the same as before, but you multiply the yvalue with the damping factor and, simultaneously, you reduce this damping factor slowly from 1.0 back to 0.0 (but never less than 0; that’s what the fmaxf()
prevents). Build and run, then try it out!
Where to Go from Here?
Here is the full example project from this Trigonometry for Game Programming tutorial series.
Congratulations, you have peered even deeper into the true natures of sine, cosine and tangent, and you have tried them out to see some examples of how they’re useful in a real game. I hope you’ve seen how handy Trigonometry really is for games!
Note that this series didn’t talk so much about arcsin and arccos. They are much less useful in practice than arctan. One common use for arccos is to find the angle between two arbitrary vectors – for example, to model the reflection of a light beam in a mirror, or to calculate how bright an object should be depending on its angle to a light source.
You can find another great example of the usefulness of trigonometric functions in the Tiny Wings tutorial. It uses cosine to give the hills from the game nicely curved shapes.
If you fancy using your new found skills for more game development, but don’t know where to start, then why not try out our book iOS Games by Tutorials. It will certainly kick start your development!
Drop by the forums to share your successes and agonies with trig. And use your new powers wisely!
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.
Trigonometry for Game Programming [Sprite Kit Version]: Part 2/2 is a post from: Ray Wenderlich
The post Trigonometry for Game Programming [Sprite Kit Version]: Part 2/2 appeared first on Ray Wenderlich.
Comments