This is part of an ELC Tech Network

iPhone Game Programming Tutorial Part 2- User Interaction, Simple AI, Game Logic

Ok folks, here it is. The next tutorial in our iPhone game programming tutorial (sorry for the delay).  Today, I will be discussing the basics of player interaction, simple game AI, and game logic.  We will also be exploring how to do simple collision detection so we know when the ball hits a paddle. Per popular request, I will be adding “Challenges” to the bottom of the tutorials from now on to give some more advanced ideas for improvement. Let’s begin. Start by opening your code from part 1…

User Interaction

The first thing we will implement is user interaction.  All we really want to do is move the paddle’s X location to the X location of the touch from the user.  This will be a very simple implementation and could be much better (I will add this as a challenge at the bottom of the tutorial).  Open iTennisViewController.m and add the following code.

screenshot_01

Just as we did in a previous tutorial, we are overriding the touchesMoved method.  This will detect when the user “drags” their finger on the screen.  First, I added the “else if” statement inside of touchesBegan that simply forwards all events to touchesMoved if the game is in a running state.

The first 2 lines inside of touchesMoved simply detect the location of the user’s touch.  Next, we need to create a new CGPoint from the X location of the touch and the Y location of the yellow racquet (player racquet).  Objective-C won’t simply let us say racquet_yellow.center.x = location.x.  This is probably because CGPoint is immutable (not editable).

Finally, the center of the player’s racquet is set to our new location.

Collision Detection

*Update, the user Naren has pointed out a much simpler collision detection.  The code has been updated to reflect it. Inside the gameLoop method of iTennisViewController.m add the following code

screenshot_011

So, Apple has provided us with a very handy methods to check if to object frames collide.  Its called CGRectIntersectsRect.  We simply hand this method the frame of our ball and racquet.  When the ball collides with the racquet, we want to reverse its Y velocity.  The next if statement is required because sometimes we get in a state where the ball gets “trapped” on a paddle bouncing back and for and not going anywhere.  So, we want to ensure the ball’s velocity only gets reversed if it is front of each racquet. (Note, the NSlog is not needed, it was just for debugging)

Simple Game AI

Next, we are going to discuss how simple AI can be added to allow a computer player to play iTennis with you.  Many of you might not know just how much is involved in a decent Artificial intelligence.  I could go on and on with nerdy math, philosophy, heuristics and the like, but I’m not.  I will show you some super NOOB, very easy to understand game AI.  Basically, the computer will “watch” the ball and move in the direction of it in hopes of hitting it.  Let’s get started… 

We first need to define a constant that will define how fast the computer player can move.  Add the following define to the top of iTennisViewController.m

picture-2

As you start testing, you can adjust this number.  This is basically defining how fast the computer player can move in order to get to the ball.  The higher you make this number, the “better” the computer player will be.  You could actually make your computer unbeatable if this number were high enough.  Now, add the following code right under your collision detection code:

picture-3

The first “if” statement is to add some difficulty for the computer.  It basically checks to see if the ball is on “his” side of the court.  The computer will not move or respond to the ball unless it’s on his side.  It could be omitted, but makes for a more interesting game.  The next if statements check to see if the X coordinates of the center of the ball are different than the X coordinates of the center of the racquet.  If the ball is to the right of the computer’s racquet, the X coordinate of the computer’s racquet is increased by kCompMoveSpeed. If the ball is to the left of the computer’s racquet, the X coordinate of the computer’s racquet is decreased by kCompMoveSpeed. 

It should now be clear how adjusting the kCompMoveSpeed variable, will affect the computer’s performance…

Now, you can actually hit Build and Go to see the game in action.  As you can see the computer responds to the ball and hits it most of the time.  There is still one last piece to make the game fun. Scoring!

Game Mechanics: Scoring

Now, we need a way to keep score.  This is actually some more simple collision detection.  We are basically checking to see if the ball collides with the back walls.  First, let’s define a few variables and a method.  Open up iTennisViewController.h.  Add the following highlighted code:

Advertisement

picture-4

We need integer representations of the score so we can add points when the player and the computer score.  Also, we will need a function called reset that will be called to reset the ball to the center of the screen.  Now we need to define one more variable.  Add the following line to your defines at the top of iTennisViewController.m

picture-5

This variable is pretty self explanatory, but in case you didn’t catch on, it defines the amount of points needed to win.  I just made it 5 for a quick game.  Now, let’s check to see if a player or computer scored.  Right after your AI code, add the following code:

picture-6

Ok, first we see 2 if statements.  The are basically checking to see if the ball has hit/passed the top or bottom of the screen.  If it passes the top, the player score gets incremented.  If it passes the bottom, the computer score gets incremented.  The next line contains a little bit of fanciness if you are new to programming.  We are calling the reset function, but what is that expression we are passing to it? 

Well, if you look at the definition of reset, it takes a BOOL value that determines if the game is over.  So this can either be true or false.  We are simply passing true or false when the expression is evaluated.  So, (player_score_value >= kScoreToWin) will evaluate to false when the player_score_value variable is less than 5.  Once this variable reaches 5, it will return true and pass it into the reset function.

Why do this? Well it saves us lines of code and complexity.  So now you don’t have to do if(player_score_value >= kScoreToWin) ) [self reset:YES]; }else{[self reset:NO];} . make sense?

Now, let’s define the reset function. Add the following method to iTennisViewController.m

picture-7

The first thing we do is pause the game.  Remember pausing the game causes the “Tap to Begin” Message to Display. Next, we center the ball on the screen.  If YES/true was passed in for the newGame variable, we need to do a few things.  First, we check who won by comparing the computer and player scores.  Next, we update the “tapToBegin” message to notify the player who won.  You could add another label for this, but I am just recycling this one… Finally, we reset the player and computer scores to 0 because a new game is starting.

If it is not a new game, we need to reset the tapToBegin message to display “tapToBegin”.  This has to be done in case the message was altered to say “Player/Computer wins!”. Finally, we update the labels on the screen to reflect the new scores.  Now you should be good to go…

Click Build and Go and battle the computer in an epic game of iTennis!  If you have any comments or questions, leave them here or ask me on Twitter.  You can also download the source for this tutorial here

As Promised, Here are some challenges

 

  • Improve on user interaction – Make it so when the user taps, the racquet moves towards the tap rather than moves directly to the tap location
  • Improve collision detection – When the ball hits the paddle, use some simple physics to make the speed of the paddle affect the speed (and direction) of the ball
  • Improve on the AI – add some randomness to your AI, make it attempt to “predict” where the ball is going to be
  • Improve Scoring – Make it used tennis scores 15, 30 , etc…
  • Improve scoring – Make it so you must win by 2 points

 

Stay tuned for the next tutorial when I will be going over game audio, splash screen, about, and some other polishing… Happy iCoding!

This entry was posted in iPhone Game Programming, iPhone Programming Tutorials and tagged , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

129 Comments

  1. iPhone Dev.
    Posted February 28, 2010 at 1:29 pm | Permalink

    Hi,
    Xcode says: error: expected identifier or ‘(‘ before ‘if’ in: ViewController.m

    // Begin Simple AI

    if(ball.center.y <= self.view.center.y) {

    What's wrong?

    • Shubhojit
      Posted March 14, 2010 at 12:09 am | Permalink

      I believe by now you would have sorted the mystery

      but still

      you need to put the

      //Begin Simple AI
      if stmt inside the -(void) gameLoop { }
      method

      then its all good

  2. Shubhojit
    Posted March 12, 2010 at 9:41 pm | Permalink

    Hi! i am also getting the error

    Xcode says: error: expected identifier or ‘(‘ before ‘if’ in: ViewController.m

    // Begin Simple AI

    if(ball.center.y <= self.view.center.y) {

    What's wrong?

    plz reply

  3. iPhone Dev.
    Posted March 17, 2010 at 12:48 pm | Permalink

    // Begin Simple AI
    if(ball.center.y <= self.view.center.y) {
    if(ball.center.x < racquet_green.center.x) {
    CGPoint compLocation = CGPointMake(racquet_green.center.x – kCompMoveSpeed, racquet_green.center.y):
    racquet_green.center = compLocation;
    }

  4. iPhone Dev.
    Posted March 17, 2010 at 12:49 pm | Permalink

    Same problem again!

  5. J. Cohen
    Posted April 1, 2010 at 2:56 pm | Permalink

    If the ball is 16×16 it won’t work. It took me an hour to figure out why the ball kept falling through my paddle, and after trying about 40 things, it turned out that resizing the ball to 18×18 fixed it. Strange, because the .png image is 16×16 in size.

    I wish I understood why. Strangest thing is it worked off the computer’s paddle, but just went right through mine.

  6. Bill
    Posted April 8, 2010 at 11:52 am | Permalink

    Most games now have the ability to use tilt instead of touch screen. Is there an easy way to incorporate this so when I tilt my iPhone the paddles move?

  7. Bill
    Posted April 8, 2010 at 12:22 pm | Permalink

    Is it possible to create a link to tutorial part 1-4 so I can easily navigate thru this tutorial?

  8. Arndt Bieberstein
    Posted April 14, 2010 at 10:58 am | Permalink

    I just found a Bug in my Code.

    When you make the racquet too small the interaction method (CGRectInteractsRect…) never
    gets an intersection between racquet and ball.

    Buuuuut
    Very very very nice tutorial :)

  9. Edmond
    Posted April 14, 2010 at 1:32 pm | Permalink

    Hi, i got an error “Method definition not in @implementation context”, and I’m new to programming and i don’t know what that means. Also u spelled racquet_yellow and raquet_yellow is there a difference. I’m not sure what i did wrong so i posted most of my coding from the “iTennisViewController.m”. Can you please help me out?

    //
    // iTennisViewController.m
    // iTennis
    //
    // Created by ed on 4/5/10.
    // Copyright __MyCompanyName__ 2010. All rights reserved.
    //

    #import “iTennisViewController.h”

    #define kScoreToWin 5
    -(void) gameLoop{// Begin Scoring Game Logic}
    if(ball.center.y = kScoreToWin)];
    }
    }
    if(ball.center.y > self.view.bounds.size.height) {
    computer_score_value++;
    [self reset:(player_score_value >= kScoreToWin)];

    #define kCompMoveSpeed 15
    - (void) gameLoop{ // Begin Simple AI}
    if(ball.center.y <= self.view.center.y){
    if(ball.center.x racquet_green.center.x){
    CGPoint compLocation = CGPointMake(racquet_green.center.x + kCompMoveSpeed, racquet_green.center.y);
    racquet_green.center = compLocation;
    }
    }

    #define kGameStateRunning 1
    #define kGameStatePaused 2

    #define kBallSpeedX 10
    #define kBallSpeedY 15

    @implementation iTennisViewController
    @synthesize ball,raquet_yellow,raquet_green,player_score,computer_score,gameState,ballVelocity,tapToBegin;

    -(void) gameLoop {
    if(gameState == kGameStateRunning) {

    ball.center = CGPointMake(ball.center.x + ballVelocity.x , ball.center.y +ballVelocity.y);

    if(ball.center.x > self.view.bounds.size.width || ball.center.x self.view.bounds.size.height || ball.center.y <0){
    ballVelocity.y = -ballVelocity.y;
    }
    } else {
    if(tapToBegin.hidden){
    tapToBegin.hidden = NO;
    }
    }
    }

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if(gameState == kGameStatePaused) {
    tapToBegin.hidden = YES;
    gameState = kGameStateRunning;
    }
    }

    - (void)dealloc {
    [super dealloc];
    [ball release];
    [raquet_green release];
    [raquet_yellow release];
    [player_score release];
    [computer_score release];
    [tapToBegin release];
    }

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if(gameState == kGameStatePaused) {
    tapToBegin.hidden = YES;
    gameState = kGameStateRunning;
    } else if(gameState == kGameStateRunning) {
    [self touchesMoved:touches withEvent:event];
    }
    }

    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [[event allTouches] anyObject];
    CGPoint location = [touch locationInView:touch.view];
    CGPoint xLocation = CGPointMake(location.x,raquet_yellow.center.y);
    racquet_yellow.center = xLocation;
    }

  10. belbelts
    Posted April 19, 2010 at 3:41 pm | Permalink

    I think that this tutorial was very great. I’m now trying to make a game because of this tutorial, but I have a problem. In my game I need a lot of walls, so I made an array inside the game loop that creates the walls. I then ran the app and after a few minutes the game started to slow down.

    • belbelts
      Posted April 19, 2010 at 6:41 pm | Permalink

      what do I do? please comment

  11. Balloonboy
    Posted April 24, 2010 at 12:05 am | Permalink

    I am having problem with:

    Improve collision detection – When the ball hits the paddle, use some simple physics to make the speed of the paddle affect the speed (and direction) of the ball.

    What I’m trying to do is to make it so that:
    - Every time the ball hits the paddle it increases the speed a little.
    - The ball’s deflection angle determined by how far is the ball from the center of the paddle when it hits.

    Can anyone help me on these?

    • Harrison
      Posted August 23, 2010 at 9:28 am | Permalink

      On collision compare the ball.center.x to the racquets.center.x and change the deflection and speed based on difference between them

  12. Buru
    Posted May 1, 2010 at 12:27 pm | Permalink

    Hi, I’m having som toubles when trying to make the Game Mechanics: Scoring part.
    When I click ‘build’, en error appears in the line : (iTennisViewController.m)
    - (void) reset:(BOOL) newGame { (!) ‘RESET’ UNDECLARED (2)
    self.gameState = kGameStatePaused;
    …………………

    and at the end of the code, at the last line after all the dealloc part :
    ….
    }
    @end /!\ Incomplete implementation of class ‘ItennisViewController’ (2)

    Can somebody help me? Thanks all and thanks for the tutorial!

    • Buru
      Posted May 2, 2010 at 3:45 am | Permalink

      ok! I’ve sloved the problem ^^ I had to change the ‘order’

      • Tom
        Posted May 23, 2010 at 1:57 am | Permalink

        Buru, what was the solution to the final
        @end
        warning message, I am getting the same error,

        I am also getting a ‘touchesMoved’ undeclared error, ( whatever I place as the second section gets this undeclared error)

        Thank you so much, I would be very grateful if you could help with these problems,

        The Code is below,

        #import “iTennisViewController.h”
        #define kCompMoveSpeed 15
        #define kGameStateRunning 1
        #define kGameStatePaused 2

        #define kScoreToWin 5

        #define kBallSpeedX 10
        #define kBallSpeedY 15

        @implementation iTennisViewController
        @synthesize ball,racquet_yellow,racquet_green,player_score,computer_score,gameState,ballVelocity,tapToBegin;

        - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        if(gameState == kGameStatePaused) {
        tapToBegin.hidden = YES;
        gameState = kGameStateRunning;
        } else if(gameState == kGameStateRunning) {
        [self touchesMoved:touches withEvent:event];
        }

        -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
        UITouch *touch = [[event allTouches] anyObject];
        CGPoint location = [touch locationInView:touch.view];
        CGPoint xLocation = CGPointMake(Location.x.racquet_yellow.center.y);
        racquet_yellow.center = xLocation;
        }

        -(void) gameLoop {
        if(gameState == kGameStateRunning) {

        ball.center = CGPointMake(ball.center.x + ballVelocity.x , ball.center.y + ballVelocity.y);

        if(ball.center.x > self.view.bounds.size.width || ball.center.x self.view.bounds.size.height || ball.center.y < 0) {
        ballVelocity.y = -ballVelocity.y;
        }
        // Crash detection CGRectIntersectsRect.

        if(CGRectIntersectsRect(ball.frame,racquet_yellow.frame)){
        if(ball.center.y < racquet_yellow.center.y) {
        ballVelocity.Y = -ballVelocity.y;

        }
        }
        if(CGRectIntersectsRect(ball.frame,racquet_green.frame)){
        if(ball.center.y < racquet_green.center.y) {
        ballVelocity.Y = -ballVelocity.y;
        } else {
        if(tapToBegin.hidden) {
        tapToBegin.hidden = NO;
        }
        }

        // Begin Simple AI}
        if(ball.center.y <= self.view.center.y){
        if(ball.center.x racquet_green.center.x){
        CGPoint compLocation = CGPointMake(racquet_green.center.x + kCompMoveSpeed, racquet_green.center.y);
        racquet_green.center = compLocation;
        }
        }
        // Begin Scoring Game Logic}
        if(ball.center.y = >= kScoreToWin)]:
        }

        if(ball.center.y > self.view.bounds.size.height) {
        computer_score_value++;
        [self reset:(computer_score_value >= kScoreToWin)];
        }

        -(void)reset:(BOOL)newGame{
        self.gameState = kGameStatePaused;
        ball.center = self.view.center;
        if(newGame) {
        If (computer_score_value > player_score_value) {
        tapToBegin.text =@”Computer Wins”;
        } else {
        tapToBegin.text =@”Player Wins!”;

        }
        computer_score_value = 0;
        player_score_value = 0;

        } else {
        tapToBegin.text = @”Tap to Begin”;
        }
        player_score.text = [NSString stringWithFromat:@"%d", player_score_value];
        computer_score.text = [NssString stringWithFromat:@"%d",computer_score_value];
        }

        // Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
        - (void)viewDidLoad {
        [super viewDidLoad];
        self.gameState = kGameStatePaused;
        ballVelocity = CGPointMake(kBallSpeedX,kBallSpeedY);
        [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(gameLoop) userInfo:nil repeats:YES];
        }

        /*
        // Override to allow orientations other than the default portrait orientation.
        - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
        // Return YES for supported orientations
        return (interfaceOrientation == UIInterfaceOrientationPortrait);
        }
        */

        - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning]; // Releases the view if it doesn’t have a superview
        // Release anything that’s not essential, such as cached data
        }

        - (void)dealloc {
        [super dealloc];
        [ball release];
        [racquet_green release];
        [racquet_yellow release];
        [player_score release];
        [computer_score release];
        [tapToBegin release];
        }

        }
        }
        }
        }

        @end

  13. Ehab Amer
    Posted May 23, 2010 at 6:44 am | Permalink

    I’ve figured out why sometimes the ball would fall through the paddle, its not that the intersection doesn’t happen but its because that sometimes the first intersection happens WHEN the ball’s center is already behind the paddle.

    there r 3 ways to fix this :)

    1- increase the size of the ball, as already mentioned by “J. Cohen”
    2- slow down the ball on the Y axis

    (those 2 solutions make the intersection happen before the ball’s center passes the paddle)

    3- just remove the center checking condition and change the direction directly on collision (which i don’t prefer)

  14. shus
    Posted June 5, 2010 at 8:59 pm | Permalink

    how do i get the exact dimensions of the object other then a square.

  15. MAT
    Posted June 14, 2010 at 8:55 pm | Permalink

    Just something I noticed with the initial code. While the collision with green racquet works, yellow one always misses the ball. By decreasing kBallSpeedY constant from 15 to 10, then the skipped pixels are caught with the ball’s and racquet’s boundary every time.

  16. alex
    Posted June 17, 2010 at 9:11 am | Permalink

    to sort out your problems with collision detection: if speed of ball is greater than ballsize+paddlesize it will not detect collision.

    solution:
    check for collisions along the _path_ of the ball from current position to next position. if collision happens set ball to point of collision and change velocity.

  17. TessoMC
    Posted July 28, 2010 at 2:26 pm | Permalink

    Im having the strangest problem..
    When i hit build and go, it all works fine, till the simulator launches the app, it show the itennis loading screen, and then… just quits the game.

    Any thoughts?

    • Noah
      Posted August 20, 2010 at 2:24 pm | Permalink

      I’m having the same problem here. I have no idea what is causing this. When I run debugging, it says “GDB: Program received signal SIGABRT.”

  18. Gio
    Posted August 2, 2010 at 8:28 am | Permalink

    There is an error I’m getting with the player movement, it says “Request of member “view” in something not a structure or union” on the line with CGPoint location. can anyone help?

  19. jorge
    Posted August 20, 2010 at 9:57 am | Permalink

    hi me puedes enseñar a como poner el score cuando el personaje del juego se mueve tiene que dar puntos en el escore deacuerdo a cuanto se mueve puede obtener un puntaje
    GRacias
    espero tu respuesta

8 Trackbacks

  1. [...] iPhone Game Programming Tutorial Part 2- User Interaction, Simple AI, Game Logic [...]

  2. [...] iPhone Game Programming Tutorial Part 2- User Interaction, Simple AI, Game  [...]

  3. [...] 36. Game tutorial-2 [...]

  4. [...] the first part deals with the creation of different folders within the main project folder.  The second part talks about user interaction, Simple game AI, game logic etc.The third and the final part contains [...]

  5. [...] e “Bubi Devs”. La versione originale inglese del tutorial è disponibile a questo indirizzo: “iPhone Game Programming Tutorial, Part 2 – iCodBlog“. I meriti quindi relativamente alla versione inglese, sono del legittimo [...]

  6. [...] iPhone Game Programming Tutorial Part 2- User Interaction, Simple AI, Game Logic [...]

  7. [...] iTennis iPhone Game Tutorial – Lesson 02 [...]

  8. [...] di un videogioco per iPhone. Un semplice Pong style che ci consente di apprendere di più sulla collisione di oggetti, Ai, processi di interazione degli utenti (parte 2), creazione di splash page [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">