How To Make a Letter / Word Game with UIKit: Part 3/3

This third and final part of the series will be the most fun of them all! In this part, you’re going to be adding a lot of cool and fun features By Marin Todorov.

Leave a rating/review
Save for later
Share

Welcome to the our tutorial series about creating a letter / word game with UIKit for the iPad.

If you successfully followed through the first and second parts of this tutorial, your game should be pretty functional. But it still lacks some key game elements and could use a bit more eye candy.

This third and final part of the series will be the most fun of them all! In this part, you’re going to be adding a lot of cool and fun features:

  • Visual effects to make tile dragging more satisfying
  • Gratuitous sound effects
  • In-game help for players
  • A game menu for choosing the difficulty level
  • And yes… explosions! :]

That’s quite a lot, so let’s once again get cracking!

Enhanced Tile Dragging: Not a Drag

When you run the completed project from Part 2 of this tutorial, you have this:

Your players can drag tiles around on the screen just fine, but the effect is very two-dimensional. In this section you’ll see how some simple visual effects can make the dragging experience much more satisfying.

When the player begins dragging a tile, it would be nice if the tile got a little bigger and showed a drop shadow below itself. These effects will make it look like the player is actually lifting the tile from the board while moving it.

To accomplish this effect, you’ll be using some features of QuartzCore. I’ve already included the QuartzCore framework in the starter project, so all you have to do to access it is add the proper import statement.

Open TileView.m and add the following import:

#import <QuartzCore/QuartzCore.h>

Now add the following code inside initWithLetter:andSideLength:, just after the line where you enabled user interactions:

//create the tile shadow
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOpacity = 0;
self.layer.shadowOffset = CGSizeMake(10.0f, 10.0f);
self.layer.shadowRadius = 15.0f;
self.layer.masksToBounds = NO;

UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.bounds];
self.layer.shadowPath = path.CGPath;

Every UIView has a property called layer – this is the CALayer class layer used to draw the view. Luckily CALayer has a set of properties used to create a drop shadow. The property names mostly speak for themselves, but a few probably need some extra explanation.

  • masksToBounds should be set to NO if you are creating a drop shadow, because a drop shadow is rendered outside of the view’s bounds. If it were set to YES, you wouldn’t see the shadow.
  • shadowPath is a UIBezierPath that describes the shadow shape. Use this whenever possible to speed up the shadow rendering. In the code above, you use a rectangle path (same as the tile’s bounds), but you also apply rounding to the rectangle’s corners via shadowRadius, and you effectively have a shadow with the form of the tile itself. Nice!
  • Note the line self.layer.shadowOpacity = 0;. This makes the shadow invisible. So you create the shadow when the tile is initialized, but you show and hide it when the user drags the tile.

Add the following code at the end of touchesBegan:withEvent::

    //show the drop shadow
    self.layer.shadowOpacity = 0.8;

This turns on the shadow when the user starts dragging. Setting the opacity to 0.8 gives it a bit of transparency, which looks nice.

Add the following code at the end of touchesEnded:withEvent::

    self.layer.shadowOpacity = 0.0;

This turns off the shadow. The effect will look even better when you change the tile size, but build and run the app now just to see it working:

It’s a nice subtle touch that makes the user will appreciate – even if subconsciously.

Now to handle resizing the tile so that it looks like it’s being lifted off the board. First add the following code to the end of touchesBegan:withEvent::

    //enlarge the tile
    self.transform = CGAffineTransformScale(self.transform, 1.2, 1.2);

This sets the size of the tile to be 120% of the current tile size. Build and run. You’ll see the tiles get larger when you drag them:

However, when you stop dragging the tile, it doesn’t go back to its regular size. And if you keep tapping the same tile, things quickly get out of control:

It’s the tile that ate your iPad!! This won’t do. Thankfully, it’s an easy fix.

First add the following private variable to the variables section of TileView.m:

    CGAffineTransform _tempTransform;

You’ll use _tempTransform to temporarily store the tile view’s original transform before the player drags it, so that you can restore it when the drag is complete. The transform stores things like scale and rotation.

Now add the following line to touchesBegan:withEvent:, just above where you set the transform:

    //save the current transform
    _tempTransform = self.transform;

This saves a copy of the original transform so you can restore it later.

Now add this to the end of touchesEnded:withEvent::

    //restore the original transform
    self.transform = _tempTransform;

This restores the tile’s size to its pre-drag state. Build and run again and tile behavior should be fixed. Or is it?

Now the tiles aren’t straightening out when they are placed correctly!

This is a simple error, caused by the order in which you added the lines in touchesEnded:withEvent:. First you notified the tile’s delegate, and then you restored the original transform. But remember how the delegate also modifies the tile’s transform?

Fix this by moving the line you just added up above the line that reads if (self.dragDelegate). Now your method should look like this:

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
  [self touchesMoved:touches withEvent:event];
  
  self.transform = _tempTransform;

  if (self.dragDelegate) {
    [self.dragDelegate tileView:self didDragToPoint:self.center];
  }

  self.layer.shadowOpacity = 0.0;
}

While you’re at it, add the following method right after touchesEnded:withEvent::

//reset the view transoform in case drag is cancelled
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.transform = _tempTransform;
    self.layer.shadowOpacity = 0.0;
}

iOS calls touchesCancelled:withEvent: in certain special situations, like when the app receives a low memory warning. Your use of it ensures that the tile’s display is properly restored.

Another build and run, another hope that things will be working as expected! Well, almost… You may have already noticed it while developing the app, but what’s wrong with the following screenshot?

As you can see, the tile the player is dragging is displayed below another tile! That really destroys the illusion of lifting the tile.

This occurs because a parent view always displays its subviews in the order that they are added to it. That means any given tile may be displayed above some tiles and below others. Not good.

To fix this, add the following code to the end of touchesBegan:withEvent::

  [self.superview bringSubviewToFront:self];

This tells the view that contains the tile (self.superview) to display the tile’s view above all other views. Dragging should feel a lot better now.

Challenge: If you want to make tile dragging even cooler, how about if touching a tile that’s underneath other tiles animated those tiles out of the way, so it looks like the pile of tiles is disturbed by lifting the bottom tile? How would you implement that?

Challenge: If you want to make tile dragging even cooler, how about if touching a tile that’s underneath other tiles animated those tiles out of the way, so it looks like the pile of tiles is disturbed by lifting the bottom tile? How would you implement that?

Contributors

Over 300 content creators. Join our team.