User Interactivity – Mini Golf

(For more resources related to this topic, see here.)

Using user input and touch events

A touch event occurs each time a user touches the screen, drags, or releases the screen, and also during an interruption. Touch events begin with a user touching the screen. The touches are all handled by the UIResponder class, which then causes a UIEvent object to be generated, which then passes your code to a UITouch object for each finger touching the screen. For most of the code you work on, you will be concerned about the basic information. Where did the user start to touch the screen? Did they drag their finger across the screen? And, where did they let go? You will also want to know if the touch event got interrupted or canceled by another event. All of these situations can be handled in your view controller code using the following four methods (You can probably figure out what each one does.):

-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

These methods are structured pretty simply, and we will show you how to implement these a little later in the article. When a touch event occurs on an object, let's say a button, that object will maintain the touch event until it has ended. So, if you touch a button and drag your finger outside the button, the touch-ended event is sent to the button as a touch up outside event. This is very important to know when you are setting up touches for your code. If you have a set of touch events associated with your background and another set associated with a button in the foreground, you need to understand that if touchesBegan begins on the button and touchesEnded ends on the background, touchesEnded is still passed to your button.

In this article, we will be creating a simple Mini Golf game. We start with the ball in a standard start position on the screen. The user can touch anywhere on the screen and drag their finger in any direction and release. The ball will move in the direction the player dragged their finger, and the longer the distance from their original position, the more powerful the shot will be. In the Mini Golf game, we put the touch events on the background and not the ball for one simple reason. When the user's ball stops close to the edge of the screen, we still want them to be able to drag a long distance for more power.

Using gestures in iOS apps

There are special types of touch events known as gestures. Gestures are used all over in iOS apps, but most notable is their use in maps. The simplest gesture would be tap, which is used for selecting items; however, gestures such as pinch and rotate are used for scaling and, well, rotating.

The most common gestures have been put together in the Gesture Recognizer classes from Apple, and they work on all iOS devices. These gestures are tap, pinch, rotate, swipe, pan, and long press. You will see all of the following items listed in your Object Library:

  • Tap: This is an simple touch and release on the screen. A tap may also include multiple taps.

  • Pinch: This is a gesture involving a two-finger touch and dragging of your fingers together or apart.

  • Rotate: This is a gesture involving a two finger-touch and rotation of your fingers in a circle.

  • Swipe: This gesture involves holding a touch down and then dragging to a release point.

  • Pan: This gesture involves holding a touch and dragging. A pan does not have an end point like the swipe but instead recognizes direction.

  • Long press: This gesture involves a simple touch and hold for a prespecified minimum amount of time.

The following screenshot displays the Object Library:

Let's get started on our new game as we show you how to integrate a gesture into your game:

  1. We'll start by making our standard Single View Application project. Let's call this one MiniGolf and save it to a new directory, as shown in the following screenshot:

  2. Once your project has been created, select it and uncheck Landscape Left and Landscape Right in your Deployment Info.

  3. Create a new Objective-C class file for our game view controller with a subclass of UIViewController, which we will call MiniGolfViewController so that we don't confuse it with our other game view controllers:

  4. Next, you will need to drag it into the Resources folder for the Mini Golf game:

  5. Select your Main.storyboard, and let's get our initial menu set up.

  6. Drag in a new View Controller object from your Objects Library. We are only going to be working with two screens for this game.

  7. For the new View Controller object, let's set the custom class to your new MiniGolfViewController:

  8. Let's add in a background and a button to segue into our new Mini Golf View Controller scene.

  9. Drag out an Image View object from our Object Library and set it to full screen.

  10. Drag out a Button object and name it Play Game.

  11. Press control, click on the Play Game button, and drag over to the Mini Golf View Controller scene to create a modal segue, as shown in the next screenshot. We wouldn't normally do this with a game, but let's use gestures to create a segue from the main menu to start the game.

  12. In the ViewController.m file, add in your unwind code:

    - (IBAction)unwindToThisView:(UIStoryboardSegue *)unwindSegue { }

    In your ViewController.h file, add in the corresponding action declaration:

    - (IBAction)unwindToThisView:(UIStoryboardSegue *)unwindSegue;

  13. In the Mini Golf View Controller, let's drag out an Image View object, set it to full screen, and set the image to gameBackground.png.

  14. Drag out a Button object and call it Exit Game and a Label object and set Strokes to 0 in the text area. It should look similar to the following screenshot:

  15. Press control and click-and-drag your Exit Game button to the Exit unwind segue button, as shown in the following screenshot:

If you are planning on running this game in multiple screen sizes, you should pin your images and buttons in place. If you are just running your code in the iPhone 4 simulator, you should be fine.

That should just about do it for the usual prep work. Now, let's get into some gestures. If you run your game right now, you should be able to go into your game area and exit back out. But what if we wanted to set it up so that when you touch the screen and swipe right, you would use a different segue to enter the gameplay area? The actions performed on the gestures are as follows:

  1. Select your view and drag out Swipe Gesture Recognizer from your Objects Library onto your view:

  2. Press control and drag your Swipe Gesture Recognizer to your Mini Golf View Controller just like you did with your Play Game button.

  3. Choose a modal segue and you are done.

For each of the different types of gestures that are offered in the Object Library, there are unique settings in the attributes inspector. For the swipe gesture, you can choose the swipe direction and the number of touches required to run. For example, you could set up a two-finger left swipe just by changing a few settings, as shown in the following screenshot:

You could just as easily press control and click-and-drag from your Swipe Gesture Recognizer to your header file to add IBAction associated with your swipe; this way, you can control what happens when you swipe. You will need to set Type to UISwipeGestureRecognizer:

Once you have done this, a new IBAction function will be added to both your header file and your implementation file:

- (IBAction)SwipeDetected:(UISwipeGestureRecognizer *)sender; and - (IBAction)SwipeDetected:(UISwipeGestureRecognizer *)sender { //Code for when a user swipes }

This works the same way for each of the gestures in the Object Library.

Using touch start, move, and end

Let's continue with the gameplay area. For the actual game, we are going to need a few things, such as a background with the play area, a score or stroke counter, a ball, and a cup for the ball to fall into. We want the player to be able to touch and drag in the direction they choose for the ball, which will also determine the speed based on the distance the player drags their finger. We will need to have an area in which the ball will be contained and whenever the ball hits a wall, we want it to bounce back.

We are going to have to add four methods to manage our touches. Let's take a quick look at these before we get into the game code. When a user first places their finger down, touchesBegan sends a message with data for a set of touches. We can then pull the location on the screen for the touch and store it in a variable as the start dragging position. When the user drags their finger around, it is tracked in the touchesMoved method. I am going to use the moved method to track the player's end touch position. When the user lifts their finger off the screen, we will use the touchesEnded method to calculate the ball direction along with the final distance between touch start and touch end for our ball's velocity. If the touch gets interrupted or canceled, the touchesCancelled method will get called. We will use this to release the touch information. The aforementioned methods are shown as follows:

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*) event { } - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*) event { } - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*) event { } - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { }

Lets get started:

  1. As we already put our Exit button and Stroke Counter label in the game, let's start by linking them to the code.

  2. Press control and drag from the Label field to our MiniGolfViewController.h header file and create an outlet, which we will call StrokeCount:

Now, it's time to create a link with the Exit button, so we can clean up our code when we leave the game and go back to the menu:

  1. Press control and click-and-drag from the Exit Game button to the MiniGolfViewController.h header file.

  2. Set the connection type to Action and name it exitGame, and set Type to id.

We need to add our cup image to the game area as well:

  1. Drag out an Image object and set the image to the cup.png file from your images group.

  2. Set the image frame as follows in the size inspector: X to 145, Y to 50, Width to 24, and Height to 24. This is shown in the following screenshot:

Initializing our code

Now, we can start to add the code. Let's start by adding in a few more properties to our header file for our game state, player's ball, and the final cup's position. The following is how our MiniGolfViewController.h file should end up like:

#import <UIKit/UIKit.h> @interface MiniGolfViewController : UIViewController @property NSString *gameState; @property UIImage *playerBall; @property UIImageView *playerBallView; @property CGRect ballRect; @property CGRect cupRect; @property (strong) NSTimer *updateTimer; @property (strong, nonatomic) IBOutlet UILabel *StrokeCount; - (IBAction)exitGame:(id)sender; @end

These are all the property types that we have worked with previously. We will discuss these more as we work our way through the rest of the implementation code.

We start our implementation code by adding some variables. courseBounds is a rectangular area where the ball is constrained to the hole you are playing on (not to be confused with the cup that the ball will fall into). strokeCounter is your score for this hole, and ballVelocity is, well... your ball's velocity or speed. touchStartPos and touchEndPos are the starting and ending positions for your player's touches, respectively. The aforementioned variables are shown in the following code:

#import "TouchEventsViewController.h" @implementation TouchEventsViewController CGRect courseBounds; CGPoint ballDirection; float ballVelocity; int strokeCounter; CGPoint touchStartPos; CGPoint touchEndPos; - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)
nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization } return self; }

In our viewDidLoad method, we have to set our initial gameState variable. As you will see throughout the code, we use a game state mostly to keep our users from hitting the ball while it is moving, as shown in the following code:

- (Void)Viewdidload { [Super Viewdidload]; Self.Gamestate = @"Loading";

We then add our ball's image view, image, and position. Set the ball's position based on the bottom of the screen, just in case we are using an iPhone with a smaller screen, and wrap it up by adding the ball view to the main view:

//Assign a ball image to a variable self.playerBall = [UIImage imageNamed:@"ball.png"]; //Create a ball to move around self.playerBallView =[[UIImageView alloc] initWithImage: self.playerBall]; CGFloat screenheight = [UIScreen mainScreen].bounds.size.height; self.ballRect = CGRectMake(160,screenheight - 100, 24,24); self.playerBallView.frame = self.ballRect; [self.view addSubview: self.playerBallView];

This is where we initialize a few variables needed throughout the code. The cupRect variable is the center point of the cup. This is when the ball intersects the point in which we drop the ball into the cup and end the round. The CourseBounds variable is a rectangle that defines the area that the ball is allowed to roll. If the ball intersects with the sides of this rectangle, the ball will bounce back into play. The last three variables are initializing variables used for gameplay. ballDirection is stationary to start so the x and y directions are set to 0. The ballVelocity variable is set to 0, as the ball is not moving and therefore has no speed. strokeCounter is the number of times you have hit the ball and is set to 0 on initialization. We throw that into our StrokeCount label on the screen, and then we start the timer that updates every thirtieth of a second. We wrap up the method by setting gameState to Waiting, as we are waiting for the player to touch the screen. These actions can be performed using the following code snippet:

self.cupRect = CGRectMake(152, 57, 10,10); courseBounds = CGRectMake(110,30, 100,470); ballDirection = CGPointMake(0, 0); ballVelocity = 0; strokeCounter = 0; self.StrokeCount.text = [NSString stringWithFormat:
@"Stroke: %d", strokeCounter]; self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:.03 target:self selector:@selector(update) userInfo:nil repeats:YES]; self.gameState = @"Waiting"; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. }

Touch events

The following is the description of our touch code. We start when the user first touches their device and the touchesBegan method gets called. We check gameState and then we pick up our touches data and place it into our touch variable. Once we have the UITouch object, we can get the position that the user first pressed. We store that in our touchStartPos variable. We use locationInView to get the touch location in relation to touch.view. This will be the start position of an imaginary line that we will use to calculate the direction and distance multipliers for our ball. The code is as follows:

- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*) event { if([self.gameState isEqual: @"Playing"]) return; // do something with the touches that just landed on the view UITouch *touch = [touches anyObject]; touchStartPos = [touch locationInView:touch.view]; }

While the player is dragging their finger around on the iPhone's glass, we will track the finger's position. The touchesMoved method is sent every time the player's finger moves its position on the glass. We pick up that new position the same way we pick up the player's touch start position. The only difference is that touchEndPos is being updated multiple times, as shown in the following code snippet:

- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*) event { // do something with the touches that moved on the view UITouch *touch = [touches anyObject]; touchEndPos = [touch locationInView:touch.view]; }

Finally, when the user lifts their finger off the glass, the touchesEnded method gets invoked. As we already have the start and end positions for the touch events, we don't need to pick it up again. We just process the information we have. We get the distance based on the positions using the Pythagorean Theorem. The distance is the square root of your x positions squared plus your y positions squared. Ta-da! We have now proven everyone wrong who has ever said you would never use geometry in the real world. The pow and sqrt functions are automatically included in Objective-C in the math.h library.

If the player didn't just tap the screen, we change the game state, increment the number of strokes, and set the ball's velocity and direction. This data is all going to be picked up in our update method, which we will discuss next. The touchesEnded method is shown as follows:

- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*) event { if([self.gameState isEqual: @"Playing"]) return; // do something with the touches that ended CGFloat distance = sqrt(pow((touchEndPos.x - touchStartPos.x), 2.0) +
pow((touchEndPos.y - touchStartPos.y), 2.0)); if(distance > 0) { self.gameState = @"Playing"; strokeCounter++; self.StrokeCount.text = [NSString stringWithFormat:@"Stroke: %d",
strokeCounter]; ballVelocity = distance * 0.01; ballDirection = CGPointMake((touchEndPos.x - touchStartPos.x) * 0.01,
(touchEndPos.y - touchStartPos.y) * 0.01); } }

Oh, but first, in the event our touch data gets canceled, we are going to clear the data so that the ball doesn't move and the player doesn't lose a stroke just because they received a call or were inadvertently interrupted:

- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { // do something with the touches that were cancelled touchStartPos = CGPointMake(0,0); touchEndPos = CGPointMake(0,0); ballVelocity = 0; self.gameState = @"Waiting"; }

Updating the screen

This is the update method, where our ball is moved around the screen, and we check to see if our ball has hit the side of the courseBounds area. We start by making sure gameState is set to Playing, so we are not wasting frame updates. We pick up ballDirection and check it against the sides of the bounding box. If the ball hits on the left- or right-hand side, we adjust the ball's x direction to be inverted; and if the ball hits the top or bottom, we invert the y direction. We do this by getting the absolute value of the ball's x and y directions as a float (fabsf) and invert the number. The updating of the screen is shown in the following code snippet:

//Call this method to move a ball -(void)update { if(![self.gameState isEqual: @"Playing"]) return; if (self.ballRect.origin.x <= courseBounds.origin.x) { //hit left ballDirection.x = fabsf(ballDirection.x); } else if ((self.ballRect.origin.x + self.ballRect.size.width) >= (courseBounds.origin.x+courseBounds.size.width)) { //hit right ballDirection.x = -fabsf(ballDirection.x); } if (self.ballRect.origin.y <= courseBounds.origin.y) { //hit top ballDirection.y = fabsf(ballDirection.y); } else if (self.ballRect.origin.y + self.ballRect.size.height >
= (courseBounds.size.height+courseBounds.origin.y)) { //hit bottom ballDirection.y = -fabsf(ballDirection.y); }

Now that we know the ball's direction, we can adjust the ball's position based on the new direction, velocity, and position. As long as the ball has a velocity, we reduce the velocity to give the ball the effect of slowing down using the following code snippet:

//Update the balls position self.ballRect = CGRectOffset(self.ballRect, ballDirection.x * ballVelocity, ballDirection.y * ballVelocity); self.ballRect.origin.x += ballDirection.x * ballVelocity; self.ballRect.origin.y += ballDirection.y * ballVelocity; //if the ball is moving slow it down over time if(ballVelocity > 0) { ballVelocity -= .05; } //if the ball stops, change the game state to waiting if(ballVelocity <= 0) { self.gameState = @"Waiting"; ballVelocity = 0; } //if the ball intersects the cup, set the state to done. if(CGRectIntersectsRect(self.ballRect, self.cupRect)) { self.gameState = @"Done"; self.ballRect = CGRectMake(150, 55, 12, 12); ballVelocity = 0; } self.playerBallView.frame = self.ballRect; }

When the user hits the Exit button, let's clear out all of our variables:

- (IBAction)exitGame:(id)sender { [self.updateTimer invalidate]; self.updateTimer = nil; self.ballRect = CGRectMake(0,0, 24,24); [self.playerBallView removeFromSuperview]; self.playerBallView = nil; ballDirection = CGPointMake(0, 0); ballVelocity = 0; }

That's it for our touch methods and our Mini Golf game. It's a pretty simple scene to set up and get working, once you know how to pick up the player's interaction.

Some things you can try on your own are adding multiple holes for the user to play through; usually eight or sixteen holes are what the player is looking for. Maybe you could add in a swipe to help guide the ball as it is moving. Add in a rotate or zoom for more user interactivity during the game. Ultimately, it's all up to you now; your imagination is your only limitation.


In this article, we learned how the user can interact with their iPhone, and how to put that data to use in our games. We discussed different ways we can pick up touch data, motion data, tilting, and gestures. We took everything we learned about touches and gestures and applied them to a simple putt-putt golf game.

Resources for Article:

Further resources on this subject:

You've been reading an excerpt of:

Learning Objective-C by Developing iPhone Games

Explore Title