Search This Blog

Wednesday, October 27, 2010

Tutorial: iPhone Game Programming- professional Bricks (Part III)

Click here for Part I

Click here for Part II

In Part II, we dove in to the implementation of pretty much every function except the core gameplaying logic. This part will now discuss the implementation of the core game playing logic and how I handle touch detections.



Before we do that, here is the video again of what we are implementing.


Game On!


Update: After I posted this code, in a subsequent update, I separated the direction variable from the speed variable. That makes code handling cleaner.In this version you will see the ball getting messed up in some situations. I'll leave it to you to fix that part. I also added 10 levels and a high score list - easy to do - not part of the code I posted here [objc]
//=============================================================================
// This is the heart of the game. Checks for collisions, hits, misses etc. Can be cleaned up better
- (void) playGame:(NSTimer *)timer
{
static int hack_hit_count = 0;
if (kGamePause) {return;}

//First Check for brick collision
// basically, we iterate through the array of bricks
// and compare if the ball location intersects with any of the bricks location
// From a display perspective, we are not really deleting the brick. We are simply making its alpha (transparency)
// to 0 so it looks like its gone. Therefore, when we check if the ball has hit a brick, we also need to check if
// the brick is not transparent. If it is, its been hit before.
[/objc]

Well, what can I say. I've already commented the code well enough. Do I really need to explain more below?

Brick Collision Logic starts here


[objc]
#pragma mark Brick Collision logic

int youWin=1; // if this is 0, then there is atleast one brick that has not been hit. If 1, all bricks
// have been hit.
for (int y=0; y<BRICKS_ROWS; y++)
{
for (int x=0; x<BRICKS_COLUMNS; x++)
{

// make sure the ball intersects the brick propoerly.
// since the ball is circular, take a hit only if its 3x1 pixels into the brick intersection
CGRect brick_inside = bricks[y][x].frame;
brick_inside.size.width -=3;
brick_inside.size.height-=1;
brick_inside.origin.x +=3;
brick_inside.origin.y +=1;

int ballHitBrick = CGRectIntersectsRect(ball.frame, brick_inside);
int missileHitBrick = (CGRectIntersectsRect(missileView.frame, bricks[y][x].frame));

[/objc]

Lets stop here. Basically, the game logic starts with me iterating through each brick (double-for-loop). Why? Because the bricks are the center of the game. Everything (except for ball hits paddle/misses paddle) centers around the bricks. First thing I do is that for each brick, I check if the ball location intersects with the brick. How? I use the convenient "CGRectIntersectsRect" function that basically returns a yes or no depending on whether two frames intersect at any co-ordinate points. However, remember that the ball is a sphere - so is it possible it may not actually have hit and the edge of the frame of the ball hit the edge of the frame of the brick. To make it it a little more precise, I actually checking if the ball hit a "sub-frame" of the brick which is 3 pixels by 1 pixel inside (X:Y) the brick frame. While playing the game, I felt this avoid some situations where other bricks get exploded even though you feel they should have been left alone. I suppose it would be more accurate if I computed the ball radius, matched it up with the distance of intersection etc. but that was too much work. This seemed to be pretty effective too.... So anyway, ballHitBrick will be a 1 if the ball hit the "inside" of the brick as explained above. Similarly, we also need to know if the missile hit the brick. Same logic - but now with the missile position. [objc]
if ((bricks[y][x].alpha==1) && (brick_description[y][x]>=NORMAL_BRICK))
{
[/objc]

Let's see what's going on here. First, we need to do all the collision processing only if: a) The brick alpha is 1 b) the brick is described to be a normal brick, steel brick or super brick Why? Because if the alpha is not 1, that means we have hit it before (you will see that when we hit a brick, we slowly reduce its alpha to 0 using animation. So theoretically, if the ball hit a brick and a missile hit it immediately after - its not 2 hits, because you already hit it). Finally, if the brick is not a normal brick, steel brick or super brick, then what is it? Its a "no brick" (i.e. the brick_description array described that that brick should not be displayed - no brick). From a display perspective that brick is not shown, but when checking the array, its still there, with transparency 0. So we skip these conditions. [objc]

if (brick_description[y][x] !=METAL_BRICK) {youWin=0;}

if (ballHitBrick || (missileHitBrick) && (missileView.hidden==NO))
{

[/objc]

If the ball hit the brick, or the missile hit the brick, then we've got a hit! [objc]
// Coming here means that the ball has hit a brick that has not been hit before
// or, the missile hit a brick

if ((brick_description[y][x]==NORMAL_BRICK) || (brick_description[y][x]>=SUPER_BRICKS))
{
[/objc]

Remember I said this game grew in spurts? I never thought of super bricks first. I only had a normal brick (value 1). Then I added a steel brick (value 2) Then I added super bricks. As it turns out, a normal brick can explode, and so can all super bricks but not a steel brick. So, we need to show explosion only for these kinds of bricks but skip steel bricks... [objc]
NSLog(@"YOU HIT:%d:%d",y,x);
if (kHackHitCount) {hack_hit_count++;}
//NSLog (@"HACK HIT COUNT %d & kHack:%d", hack_hit_count, kHackHitCount);
score += 10;
labelScore.text = [NSString stringWithFormat:@"%d", score];
[/objc]

So you hit a brick thats not steel, thats not transparent. You deserve a pat and a +10 in score. [objc]

if (missileHitBrick)
{
NSLog(@"MISSILE HIT");
missileView.hidden=YES; // after a hit, hide the missile
kMissileFiring=0; // allow other missiles to be fired
}

[self playAudio:explodeSound];
[self explodeBrick :y :x];
[/objc]

In addition to checking if the ball hit that brick, lets also see if the missile hit the brick. Yeah, no score addition for this. Real men don't use missiles. But anyway, lets play the awesome explosion sound and explode the brick. We pass the col & row of the brick that was hit to explodeBrick. what explodeBrick does then is animate the explosion image centering it on bricks[y][x].center - in other words right at the center of the hit brick. [objc]
bricks[y][x].alpha -= 0.1; // reduce alpha right away by a pt.
// just in case you hit it again before anim begins....
[UIView beginAnimations:@"fadebrick" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationDuration:0.8];
[UIView setAnimationDelegate:self];
bricks[y][x].alpha=0;
[UIView commitAnimations]; // fade out killed brick
[/objc]

And while the explosion plays out, let's also animate the brick to fade out in parallel. We can use beginAnimation here because as the brick is fading out, we don't need to know its current fade state or anything. Being extra paranoid, before I start the animation, I reduce the alpha by 0.1 anyway so that theoretically, if something else hits that brick immediately before the animation begins to reduce alpha the hit won't count again. Maybe this is not necessary. [objc]

if (brick_description[y][x]==LIFE_BRICK) // this means you get a life
{

if (livesleft<LIVES)
{
lives[livesleft].alpha=1;
lives[livesleft].transform = CGAffineTransformIdentity;
++livesleft;
[self playAudio:gotlifeSound];

}
NSLog(@"you got a life");
}
[/objc]

If you hit a super brick which is a life brick, lets add to your lives (if you are already at max lives, we don't add again). Remember the lives array shows your current lives? Each time you lose a life, we reduce the opacity of one icon of the total lives to 0. So when you get a new life, we turn the opacity of the most recent life lost back to 1 so it shows up on the screen. [objc]
else if (brick_description[y][x]==MISSILE_BRICK) // you get a few missiles
{
kMissilesLeft+=5;
haveMissileView.hidden=NO;
NSLog(@"You have 5 more missiles. Your missile count is %d", kMissilesLeft);
}

} // normal brick
[/objc]

If you hit a missile superbrick, we just add 5 more to your available missiles. These compound. So if you hit two missile bricks, you get 10. Also, we want to show you when you have missile power. So on the lower right, we enable the display of a small missile icon (it is created and positioned as hidden in interface builder initially) [objc]
else
{ // you hit a steel bar
[self playAudio:metalSound];
tBallSpeedX = 5 + arc4random() % 5; // let the ball 'shoot' out faster if it hits metal
//if (ballSpeed.x < 0) {tBallSpeedX = -tBallSpeedX;}
NSLog(@"You hit steel. Ball speed temporarily upped to %d",tBallSpeedX);
}
[/objc]

But if you hit a steel brick, play a clang sound, and add a "random" delta to the X speed of the ball so when the ball bounces it will act like it just darts out instead of a normal bounce out. This delta is reset to 0 when it bounces off the screen boundaries, so the feeling you get is that the ball shot out after hitting a steel brick and then resumes normal operation on next bounce [objc]
if (!missileHitBrick) //don't reverse ball direction if a missile hit
{
if ((ballSpeed.x>0) && (ballSpeed.y>0)) { ballSpeed.y = -ballSpeed.y;}
else if ((ballSpeed.x>0) && (ballSpeed.y<0)) { ballSpeed.y = -ballSpeed.y;}
else if ((ballSpeed.x<0) && (ballSpeed.y>0)) { ballSpeed.x = -ballSpeed.x;}
else if ((ballSpeed.x<0) && (ballSpeed.y<0)) { ballSpeed.y = -ballSpeed.y;}

if (ballSpeed.x <0) tBallSpeedX = -tBallSpeedX;
if (ballSpeed.y <0) tBallSpeedY = -tBallSpeedY;
}

} // CGRectIntersect
} // if you hit a solid brick
} //x
} //y
[/objc]

Now, finally, if your ball hit a brick, it needs to 'bounce off' the brick too. It can't keep going on the same trajectory as before it hit the brick. the above lines do just that. Of course, if the missile hit the brick you don't want the ball to bounce (Okay, I think there are some issues here - it may be possible both hit the same brick at the same time, which I am ignoring...)

Do we have a winner here?


[objc]
#pragma mark Winning Logic
/*********** You've won **********************/
if ((kHackHitCount) && (hack_hit_count>=HACK_HITCOUNT))
{
hack_hit_count=0;
youWin=1;
NSLog(@"**Hack hitcount reached");
}
[/objc]

After we've checked all the bricks, time to check for other conditions. This fragment checks if you have "Hack mode" enabled. If it is (kHackHitCount is 1) and you've made 4 hits to bricks (we increment hack_hit_count by 1 each time you hit a brick - see brick collision code) then the flag youWin is set to 1 (which means the level is complete). If you check the brick collision logic earlier, if we detect that there is no brick left with opacity 1, then we also set this flag to 1 (we don't count steel bricks as they can't be killed) [objc]
if (youWin) // all bricks over
{
NSLog(@"Inside youWin");
[self resetAlphasforLives:-1 forBricks:0]; // remove all bricks, don't touch lives
[self playAudio:clapSound];
if (gameLevel == GAME_LEVELS)
{
NSLog(@"*************Inside game over***************");
labelGameOver.text = @"You Rock!";
labelGameOver.hidden=NO; // this will display the Game Over message
[gameTimer invalidate]; gameTimer=nil; // kill all timers - game is over
[backgroundTimer invalidate]; backgroundTimer=nil;
[harderTimer invalidate]; harderTimer=nil;
return;
}
else {
NSLog(@"Inside move to next nevel");
labelGameOver.text = [NSString stringWithFormat:@"Level %d", gameLevel+1];
labelGameOver.hidden=NO;
[gameTimer invalidate]; gameTimer=nil;
[backgroundTimer invalidate]; backgroundTimer=nil;
[harderTimer invalidate]; harderTimer=nil;
return;
}
}
[/objc]

So after that check, if youWin is 1, you have won. If there are pending levels, we skip to the next level, else show a game over text. In either case, we disable all timers so the game stops. When the user taps, we either reset the game or move to the next level. You will see this part of the code when we discuss the part of the code that detects user taps.

Strike?


[objc]#pragma mark Paddle hits ball logic

// Now lets check if the paddle hit the ball

if (CGRectIntersectsRect(ball.frame, paddle.frame) && ball.center.y<paddle.center.y)
{

[self playAudio:hitSound];
NSLog(@"HIT");
ballSpeed.y = -ballSpeed.y;

// let's check if the paddle hit the ball on its edge. If so, we want to make sure
// the ball bounces correctly (example, nudge from left edge of paddle should cause the
// ball to bounce back up and left, not right. And so forth)

float edge = ball.center.x-paddle.center.x;
// if this value is 20 or more, its an edge hit. I found this threshold out
// by priniting the difference between paddle center and ball center for various hits.
// Pretty unscientific, I'll admit.

[/objc]

The other thing we need to check is if the ball hit the paddle (if it did not, you lose a life. If it did, the ball needs to bounce up back to the bricks So that's what we check here. CGRectIntersectsRect again. Very convenient function, huh? We also want to spice up the bounce a bit. If the user hits the ball with the edge of the paddle, then we want the ball to bounce differently (if I left nudge I want the ball to go to the left and if its a right nudge it should bounce to the right). To do that, we calculate the distance of the center of the ball from the center of the paddle. If this difference is less, then the ball hit close to the center of the paddle. The larger the distance, that would mean its closer to the edge of the paddle. That's the logic here of the edge variable [objc]
#pragma mark Edge hit logic
if (abs(edge)>=20)
{
NSLog(@"Edge Hit Detected");
int paddleCenterWidth = paddle.frame.size.width/2;
if ( (paddle.center.x+paddleCenterWidth < self.view.bounds.size.width-10)
&& (paddle.center.x - paddleCenterWidth > 10))
{
ballSpeed.x = (edge<0)? -kBallSpeedX:kBallSpeedX;
NSLog(@"Edge skew activated:Ball Speed set to %f",ballSpeed.x);
}
}
tBallSpeedX = 0; tBallSpeedY =0;
labelScore.text = [NSString stringWithFormat:@"%d", score];

}
[/objc]

So if it did hit the edge, we basically change the X direction to point correctly so that the ball moves in the direction of the nudge.

And then there is the bounce check


[objc]
#pragma mark Ball bouncing off screen check

// now check if the ball is going beyond screen boundaries, if so,
// bring it back
if (ball.center.x+ballSpeed.x>self.view.bounds.size.width-4 || ball.center.x+ballSpeed.x-4<0)

{[self playAudio: boingSound]; ballSpeed.x = -ballSpeed.x; tBallSpeedX=0; tBallSpeedY=0;}
if (ball.center.y+ballSpeed.y-4 >self.view.bounds.size.height || ball.center.y+ballSpeed.y-4 <0)
{ballSpeed.y = -ballSpeed.y;[self playAudio: boingSound]; tBallSpeedX=0; tBallSpeedY=0;}

int x,y;
x= ball.center.x+ballSpeed.x+tBallSpeedX;
y = ball.center.y+ballSpeed.y+tBallSpeedY;
if (x<0) { x = 0;}
if (y<0) { y = 0;}
// catchall
if (x>self.view.bounds.size.width-4) { x=self.view.bounds.size.width-4;}
if (y>self.view.bounds.size.height-4) { y=self.view.bounds.size.height-4;}
ball.center = CGPointMake(x, y);
[/objc]

The next thing to check is if the ball has hit any of the boundaries. self.view.bounds.size.(width or height) are convenient ways to access the width and height of the current view (which in our case is the full screen). Its better to use this than to hard code values 320 and 480, for example. The code above is quite simple - if the ball has hit the edge of the screen, change X or Y of the ball to bounce off.

Miss!


And then, we finally need to check if we missed the ball. If we missed the ball, then you need to: a) reduce lives b) take of a life paddle display on the top left c) We also want to add a cute rotation to the life that is being taken away (you will see the life paddle do a quick spin to death) d) And if that was your last life, game gets over [objc]
#pragma mark Paddle missing the ball logic
// When you miss the ball, we want the ball to fall off the screen, not just disappear immediately
// when your paddle misses. So we set this youmissed flag to 1 when the paddle misses and the ball keeps going down
// till it reaches the edge of the screen. When it does, we decrease the score, kill a life and play sound
static int youmissed=0;
if ((ball.center.y > paddle.center.y) && (youmissed==0))
{
youmissed=1;
labelScore.text = [NSString stringWithFormat:@"%d", score];

}

[/objc]

One more thing. When you miss, we want the ball to go all the way to the end of the screen. So when we detect the miss, we set a flag (youmissed=1). Then when the ball falls of the screen with youmissed set to 1, that is when we do all the stuff we need to do when you miss. [objc]
if (ball.center.y >= self.view.bounds.size.height-4) // have we run off screen?
{
score -= 10;
labelScore.text = [NSString stringWithFormat:@"%d", score];
--livesleft;

// if you lose a life, lets rotate the life icon around and then make it disappear
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.2];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(lifeDeathAnimeDidStop)];
CGAffineTransform transform1 = CGAffineTransformMakeScale(1,1);
CGAffineTransform transform2 = CGAffineTransformMakeRotation(179.9 * M_PI /180.0);
lives[livesleft].transform = CGAffineTransformIdentity;
lives[livesleft].transform = CGAffineTransformConcat(transform1, transform2);
[UIView commitAnimations];

// After you die, set ball back to center
ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
// and make sure the ball goes down
ballSpeed.y = kBallSpeedY;
[self playAudio:missSound];
youmissed=0;
kGamePause = 1; // lets freeze the game till he taps
labelPause.hidden = NO;
[/objc]

And then here we check if the ball's Y co-ordinate reached the bottom of the screen. If it did, then we need to lose a life. If you notice, we don't just remove the life icon. We want to rotate it out. So we use CGAffineTransform. Basically, the way this Animation works is that you start an 'animation block' by saying 'beginAnimations' and then after that statement, specify the positions of your UIView objects. You specify the start and the stop by simply specifying the same property twice (odd, I know). For example, here I say, first set lives[livesleft].transform to "no transformation" (thats whatCGAffineTransformIdentity means). Immediately after that I say set lives[livesleft].transform to a combination of scaling it and rotating it (CGAffineTransformConcat). Then when I say commitAnimation, IOS takes care of transitioning from the first setting to the second setting based on rules you specify. Where are the rules? Its the lines that say [objc]
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.2];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[/objc]

Which basically says Animate from start to end in 0.2 seconds and do the animation in constant speed (thats what Linear means) And boom. There you have it. The lives paddle smoothly disappears in rotating bliss..... (I am not using the scale property - I first thought I will scale and rotate - it looked crappy so I reset scale to 1 - which means original size) Finally, after you kill a life with the fantastic animation above, you reset the ball to the center and pause the game so that we wait for input before we go on (give the user time to reset his hands). But where is the pause? We are just setting a flag. Well, if you recall, we exit the playGame callback if the pause flag is on. That effectively pauses the game, as all animation and game detection logic freezes.

Tap strategy


Lets briefly discuss what we use taps for in our game: a) When the game is paused, a tap will bring it out of pause mode b) When you tap and drag your finger on the screen, the paddle needs to move to where your finger is c) When you double tap your paddle, you will shoot a missile (if you have a missile) d) And finally, we have a bunch of controls at the bottom of the screen that you can tap to enable. They are: 1) Volume on off 2) Help screen 3) Triple tap on the lower left to enable 'hack mode' we talked about earlier. So we basically need to multiplex all these functions into our touch detection function [objc]
/=============================================================================
// used to detect any touches on the screen and trigger different operations

-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// I am not forcing you to touch the paddle. You can touch and drag anywhere to move the paddle
// example, its easier to drag below the paddle that way you see the paddle as well
UITouch *mytouch = [[event allTouches] anyObject];

[/objc]

This part returns the coordinates of the touch location in the variable mytouch. Great, now we can use that location to detect everything we need [objc]
touch = paddle.center.x - [mytouch locationInView:mytouch.view].x;
NSArray *allTouches = [touches allObjects];

// Did you tap on the info view ?
if (CGRectContainsPoint(infoView.frame, [[allTouches objectAtIndex:0] locationInView:self.view]))
{
if (helpView.hidden=YES)
{

helpView.hidden=NO;
[self.view bringSubviewToFront:helpView];

kGamePause=1; // pause the game when you show help
return;
}

}
[/objc]

Here we use CGRectContainsPoint to detect if our touch point falls inside the info icon. If it does, then we need to show the help screen. [objc]
// did you tap on the help screen after it was displayed?
if ((CGRectContainsPoint(helpView.frame, [[allTouches objectAtIndex:0] locationInView:self.view]) &&(helpView.hidden==NO)))
{
helpView.hidden=YES;
return;
}
[/objc]

And then if we tap the help screen after it is displayed, we need to get back to the game (so all we do is hide the help screen). [objc]
// did you tap on the volume icon?
if (CGRectContainsPoint(volumeView.frame, [[allTouches objectAtIndex:0] locationInView:self.view]))
{
NSLog(@"Volume tapped");
kVolumeIsOn ^=1;
// toggle the volume image and status on and off with each tap
if (kVolumeIsOn)
{
[volumeView setImage:[UIImage imageNamed: @"volumeon.png"]];
}
else {
[volumeView setImage:[UIImage imageNamed: @"volumeoff.png"]];
}
return;

}
[/objc]

Same logic, but for the volume icon. Also, we toggle between a volumeon and volumeoff image as we tap on the volume icon. [objc]
if (labelGameOver.hidden == NO) // game was over so restart if user touches anywhere
{

if ((gameLevel == GAME_LEVELS) || (livesleft == 0) )
// game is over, so restart from level 1
{
NSLog(@"After touch: Game is over, reset");
labelGameOver.hidden = YES;
// reset all parameters of the game and lets go again
[self resetAlphasforLives:1 forBricks:-1];
[self resetGame];
ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
// make the ball go down
if (ballSpeed.y < 0) ballSpeed.y = -ballSpeed.y;
labelScore.text = [NSString stringWithFormat:@"%d", score];

// we need to restart the timers, because we killed them when the game got over
//[self initTimer];
}
[/objc]

Then, if in the gamelogic we display "GameOver" (that's what labelGameOver.hidden==NO means), then it means after a tap, we need to reset the game and go back to level 1. That's what we are doing here. [objc]
else // go to next level
{
NSLog(@"inside touchesBegan:next level");
gameLevel++;
[backgroundImageView setImage:[UIImage imageNamed: backgroundArray[gameLevel-1]]];
labelCurrentLevel.text = [NSString stringWithFormat:@"%d",gameLevel];
labelGameOver.hidden=YES;
ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
[self initTimer];
[self remapBricks];
}
}
[/objc]

Otherwise, it means the tap will take us to the next level (the game paused after you finished the level and you need to go to the next level) [objc]
if (kGamePause) // if so, then I need to resume with touch
{
[harderTimer invalidate]; // if you paused, don't keep the timer for making it harder alive.
kGamePause = 0;
labelPause.hidden=YES;
[self.view bringSubviewToFront:helpView];
harderTimer = [NSTimer scheduledTimerWithTimeInterval:kMakeItHarder target:self selector:@selector(makeItHarder:) userInfo:nil repeats:YES];

}
[/objc]

And here, we check if the game was paused. IF it was, we just hide the pause text and let the game continue after the tap. The other thing we do is reset the harderTimer. Why? Because if the timer makes the game harder after 60 seconds and you paused in between for 45 seconds, we don't want the difficulty to go up in 15 seconds of game play. So to keep it simple, we just reset the count. [objc]
else
{
// double tapping shoots a missile
if ([mytouch tapCount] == 2)
{
NSLog(@"Tap Twice - Missile Launch");
[self launchMissile];
}

// triple tapping in the lower left part of the screen toggles 'hack mode'
else if (([mytouch tapCount]==3) &&
(CGRectContainsPoint(hackView.frame, [[allTouches objectAtIndex:0] locationInView:self.view])))
{
NSLog(@"Tap thrice - HACK MODE!!");
kHackHitCount ^=1;
if (kHackHitCount) {hackView.hidden=NO;} else {hackView.hidden=YES;}

} // tapcount=3

} // not paused

}
[/objc]

Finally, here we detect the double and triple taps. tapCount returns the # of taps. two taps means missile lauch and 3 taps enables hack mode

That was touch, how about drag?


[objc]
// this is called when I drag my finger after first touching the screen
// this will move the paddle
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch = [[event allTouches] anyObject];
float distance = ([mytouch locationInView:mytouch.view].x + touch) - paddle.center.x;
float newx = paddle.center.x + distance;

// we want to make sure the paddle is not dragged off screen, so limit its movement to a point
// where the full paddle is visible when dragged
int paddleCenterWidth = paddle.frame.size.width/2;
if (newx < paddleCenterWidth) {newx=paddleCenterWidth;}
if (newx > self.view.bounds.size.width-paddleCenterWidth) {newx=self.view.bounds.size.width-paddleCenterWidth;}
paddle.center = CGPointMake(newx, paddle.center.y);
}
[/objc]

There you go. touchesMoved is called when you drag your finger across. the logic here is as you drag, we get the location of your finger and set the paddle there. This is relative to where you first touched (that location is recorded in the previous function).

There has to be an Accelerometer too


[objc]

// Another way to move the paddle. Just rotate the phone...
- (void) accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)accel
{
float newPaddleX= paddle.center.x + (accel.x *12);
int paddleCenterWidth = paddle.frame.size.width/2;
if (newPaddleX > paddleCenterWidth && newPaddleX < self.view.bounds.size.width-paddleCenterWidth)
paddle.center=CGPointMake(newPaddleX, paddle.center.y);
}

[/objc]

Remember when we said in the .h file that we implement the accelerometer protocol as well? This API is called when the accelerometer detects movement. The paddle moves as you tilt the phone. This is really just to show off. For a game like bricks, the accelerometer based control is horrid. IF you are lying down and trying to play, the paddle keeps tilting away as well. But hey, it has sex appeal. Honestly, I'd disable this.

And now what you really want: The project and the source


Grab it HERE. To the best of my knowledge all WAV files included have a public domain license and I can include it. If not, do let me know and I'll be happy to remove it. All images are from openclipart as I said before. I've removed background.mp3 which plays the backround image. You can put in your own mp3 there and call it background.mp3

Where to next?


I plan to add network gaming and network scoring. And upload to the appstore. How much will it take to make a million on appstore if I put it at $0.99? Hmm about 1.4 million purchases. Crud!

9 comments:

  1. [...] wordpress barfs with long posts. So the playGame function and the code for detecting touches is in part III. Watch the latest videos on [...]

    ReplyDelete
  2. The link to the source is not working.

    ReplyDelete
  3. That's awesome thank you so much for sharing!

    ReplyDelete
  4. Hello Arjun the link is not working plz let me know the download link please...

    ReplyDelete
  5. I'm not a guru on this subject, but I do know great writing when I read it. This really is excellent content with great useful info.

    ReplyDelete
  6. Aw, this was an incredibly nice post. Finding the time and actual effort to generate a
    good article… but what can I say… I hesitate a whole lot and don't seem
    to get anything done.

    ReplyDelete
  7. I'm truly enjoying the design and layout of
    your website. It's a very easy on the eyes which makes it much more pleasant for
    me to come here and visit more often. Did you hire out a designer
    to create your theme? Great work!

    ReplyDelete
  8. It's hard to find your website in google.
    I found it on 20 spot, you should build quality backlinks ,
    it will help you to get more visitors. I know how to help you, just search in google - k2 seo tips

    ReplyDelete