Search This Blog

Tuesday, October 26, 2010

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

Click here for Part I

Click here for Part III

In Part I, we discussed the approach to implement a professional looking Bricks game for the iphone. We also described the basic design approach and broke down the header file in detail so you get an idea of how things will be. This part will now discuss the implementation. Before we do that, here is the video again of what we are implementing.



As I wrote earlier, this being a pretty small game, the entire logic is stuff into BricksViewController.m. Let's first take a look at it in totality and then I'll proceed to break it up. Click on the "show source" box below to expand it.

[objc collapse="true"]

//
// BricksViewController.m
// Bricks
//
// Created by Arjun on 10/15/10.
// All rights reserved.
//

#import "BricksViewController.h"
#import "GBMusicTrack.h"

@implementation BricksViewController

@synthesize ball;
@synthesize paddle;
@synthesize cloud;
@synthesize labelScore;
@synthesize labelCurrentLevel;
@synthesize labelPause;
@synthesize labelGameOver;
@synthesize explodeView;
@synthesize backgroundImageView;
@synthesize missileView;
@synthesize haveMissileView;
@synthesize hackView;
@synthesize volumeView;
@synthesize infoView;
@synthesize helpView;
@synthesize hitSound;
@synthesize missSound;
@synthesize explodeSound;
@synthesize clapSound;
@synthesize boingSound;
@synthesize booSound;
@synthesize holycowSound;
@synthesize metalSound;
@synthesize gotlifeSound;
@synthesize missileSound;
@synthesize gameTimer;
@synthesize backgroundTimer;
@synthesize harderTimer;
@synthesize rainDrops;

//=============================================================================
- (void)viewDidLoad {

NSLog(@"Inside ViewDidLoad");
[super viewDidLoad];

//************************
kHackHitCount=0; // 1=skip levels quickly. 0=normal play
//************************

kBallSpeedX = 4;
kBallSpeedY = 4;
tBallSpeedX = 0;
tBallSpeedY = 0;
kMakeItHarder = 10; // Time in seconds to make things harder in the game
kGamePause = 1;
gameTimer = nil;
backgroundTimer = nil;
harderTimer = nil;
gameLevel =1;
kMissileFiring=0;
kMissilesLeft=0;
kVolumeIsOn=1;
kMakeItHarder = 60; // Time in seconds to make things harder in the game

backgroundArray[0]=@"level1.png";
backgroundArray[1]=@"level2.png";
backgroundArray[2]=@"level3.png";
backgroundArray[3]=@"level4.png";
[backgroundImageView setImage:[UIImage imageNamed: backgroundArray[gameLevel-1]]];

// set up the accelerometer - I want to use it to move my paddle
UIAccelerometer *myAccel = [UIAccelerometer sharedAccelerometer];
myAccel.updateInterval = 1.0f/30.0f;
myAccel.delegate=self;

ballSpeed = CGPointMake(kBallSpeedX,kBallSpeedY);
score = 0;
livesleft = LIVES;

// Initialize all the audio streams that we will be playing

UInt32 sessionCategory = kAudioSessionCategory_AmbientSound;
AudioSessionSetProperty (kAudioSessionProperty_AudioCategory,
sizeof (sessionCategory),
&sessionCategory);

[self initAudioStreams:&hitSound :@"hitme" :@"wav"];
[self initAudioStreams:&booSound :@"boo" :@"wav"];
[self initAudioStreams:&missSound :@"die" :@"wav"];
[self initAudioStreams:&clapSound :@"applause" :@"wav"];
[self initAudioStreams:&explodeSound :@"explode" :@"wav"];
[self initAudioStreams:&boingSound :@"boing" :@"wav"];
[self initAudioStreams:&holycowSound :@"holycow" :@"wav"];
[self initAudioStreams:&metalSound :@"metalsound" :@"wav"];
[self initAudioStreams:&gotlifeSound :@"gotlife" :@"wav"];
[self initAudioStreams:&missileSound :@"missile" :@"wav"];

// I am blindly using GBMusicTrack for now based on their instructions...
song = [[GBMusicTrack alloc] initWithPath:[[NSBundle mainBundle] pathForResource:@"background" ofType:@"mp3"]];
//[song setRepeat:YES];
//[song play];

[self initBricks]; // fill in and display all the bricks
[self initLives];
[self initExplosion]; // pre-load explosion array
[self initRain]; // pre-load rain array
[self initTimer]; // start game timers and we are on our way!

UIImage *image = [UIImage imageNamed:@"missile.png"];
missileView = [[[UIImageView alloc] initWithImage:image] autorelease];
missileView.hidden=YES; // load the missile image, hide it for now
[self.view addSubview:missileView];

ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);

}

//=============================================================================
// play sounds only if volume is not muted
-(void) playAudio: (SystemSoundID) soundId
{
if (kVolumeIsOn) {AudioServicesPlaySystemSound(soundId);}
}
//=============================================================================
// convenience function. Pass it a reference to the SystemSoundID handle, the file and its type and it
// creates an audio stream ready to play
- (void) initAudioStreams: (SystemSoundID *)soundId :(NSString *)soundFile :(NSString *) type
{
NSString *audioPath=[[NSBundle mainBundle] pathForResource:soundFile ofType:type];
CFURLRef audioURL = (CFURLRef) [NSURL fileURLWithPath:audioPath];
AudioServicesCreateSystemSoundID(audioURL, soundId);
}

//=============================================================================
// pre-load the explosion effect into an array.
- (void) initExplosion
{
explodeImages = [[NSMutableArray alloc] initWithCapacity: 34];
for (int i = 1; i <= 34; i++)
{
NSString *imageName = [NSString stringWithFormat:@"boom%d.png",i];
UIImage *image = [UIImage imageNamed:imageName ];
[explodeImages addObject: image];
}
}

//=============================================================================
// load bricks layout. This array controls what sort of bricks show at each
// cell of the brick matrix

- (void) initBrickLayout
{
NSLog(@"init brick layout");
NSLog(@"Current Game Level is %d",gameLevel);

// You can modify these arrays to respresent the bricks
// as you want for each level. Each block is for a particular level.
// 0 = no brick at this cell
// 1 = normal brick - you hit it, it explodes and you add to your score
// 2 = metal brick - you can't kill it - the ball will hit and bounce off faster
// 3 = super brick - you hit it, it explodes and you will get a new life (if you are not at max)
// 4 = missile brick - you get 5 missiles each time you hit this one

int bricklevel[GAME_LEVELS][BRICKS_ROWS][BRICKS_COLUMNS] = {
{
// level 1
{2,0,0,1,0,0,2},
{0,0,1,1,1,0,0},
{0,1,4,0,4,1,0},
{1,1,3,0,3,1,1},
{1,1,1,0,1,1,1},
{0,1,3,0,3,1,0},
{0,0,4,1,4,0,0},
{2,0,0,1,0,0,2},
},
{
// level 2
{3,2,1,1,1,2,3},
{1,1,1,1,1,1,1},
{2,1,1,1,1,1,2},
{1,1,1,1,1,1,1},
{1,1,2,3,2,1,1},
{4,1,1,2,1,1,4},
{1,2,1,1,1,2,1},
{2,1,1,1,1,1,2},
},
{

// level 3
{0,0,0,3,0,0,0},
{0,0,1,1,1,0,0},
{0,0,1,1,1,0,0},
{0,2,1,1,1,2,0},
{0,1,1,1,1,1,0},
{2,1,1,1,1,1,2},
{1,4,1,1,1,4,1},
{1,1,2,1,2,1,1},
},
{
// level 4
{0,1,0,3,0,1,0},
{2,1,1,1,1,4,2},
{0,1,1,1,1,1,0},
{0,0,1,1,1,0,0},
{0,0,1,1,1,0,0},
{0,1,1,1,1,1,0},
{2,4,1,3,1,1,2},
{1,0,1,0,1,0,1},
},
};

NSLog (@"Copying level %d(-1) to bricks description", gameLevel);
memcpy (brick_description, bricklevel[gameLevel-1], sizeof(brick_description)); // C still rocks. W00t :-)
}

//=============================================================================
// init rain array
- (void) initRain
{
NSLog(@"Inside initRain");
NSMutableArray *array = [[NSMutableArray alloc] init];
self.rainDrops = array;
[array release];

UIImage *rainImage = [UIImage imageNamed:@"raindrop.png"];
UIImageView *rainView;

// randomly place 50 rain drops across the screen
for (int i = 0; i< 50; i++)
{
rainView= [[UIImageView alloc] initWithImage:rainImage];
rainView.alpha=0.3;
rainView.hidden=YES;

int x = arc4random()% (int)self.view.bounds.size.width;
int y = arc4random()% (int)self.view.bounds.size.height;
rainView.center = CGPointMake (x,y);

// the background image is at index 0, make rain at 1,
// so it is behind the bricks
[self.view insertSubview:rainView atIndex:1];
[self.rainDrops addObject: rainView];
[rainView release];
}
}

// we need to hide the rain for layers that don't have rain
- (void) hideRain
{
for (UIImageView *rain in rainDrops)
{
rain.hidden=YES;
}
}

// animate the rain across the screen
-(void)moveRain{

for (UIImageView *rain in rainDrops) {

CGPoint newCenter = rain.center;
newCenter.y = newCenter.y +2;
newCenter.x = newCenter.x +1;
rain.hidden = NO;

if (newCenter.y > self.view.bounds.size.height){
newCenter.y=0;
newCenter.x = arc4random()%(int)self.view.bounds.size.width;
}
if (newCenter.x > self.view.bounds.size.width){
newCenter.x =0;
} else if (newCenter.x < 0) {
newCenter.x = self.view.bounds.size.width;
}

rain.center = newCenter;

}
}

//=============================================================================
// launches a missile
// you would think we can use Core Graphics animate to automatically do this
// but we can't as we also need to detect if it hit a brick with CGRectIntersect
// The automatic animation does not allow for detection if intersects, so we need
// to do it ourselves, or, use auto animation in small steps.

- (void) launchMissile;
{
if (!kMissilesLeft) {NSLog(@"You don't have missiles");return;}
if (kMissileFiring) {return;} // only allow 1 missile at a time
haveMissileView.hidden=NO;
kMissileFiring=1;

missileView.hidden=NO;
[self playAudio:missileSound];
missileView.center=CGPointMake(paddle.center.x, paddle.center.y-10);
kMissilesLeft--;
if (!kMissilesLeft) {haveMissileView.hidden=YES;}

NSLog(@"You have %d missiles left",kMissilesLeft);
}

-(void) missileFinished
{
missileView.hidden=YES;
kMissileFiring=0;
}

//=============================================================================
// load the bricks images and create the bricks matrix. Also create the lives display
- (void) initBricks
{
NSLog(@"Inside initBricks");

brickImages[0]=@"brick2.png"; //normal
brickImages[1]=@"brick2.png";
brickImages[2]=@"steel.png"; // cant destroy
brickImages[3]=@"brick3.png"; // special powers - life
brickImages[4]=@"brick1.png"; // special powers - missiles

[self initBrickLayout];

for (int rows=0; rows self.view.bounds.size.width+80) {newposx=-64;} // wrap it around
cloud.center = CGPointMake(newposx, cloud.center.y);
}
else
{
cloud.hidden=YES;
[self moveRain];
}

if (kMissileFiring)
{

int missileY;
missileY=missileView.center.y-10;
if (missileY<0)
{
[self missileFinished];
}

else
{
missileView.center=CGPointMake(missileView.center.x, missileY);
}

} // kMissileFiring
}

//=============================================================================
// resets game back to normal.
-(void) resetGame
{
NSLog(@"Inside reset Game");
score = 0;
livesleft = LIVES;
kBallSpeedX = 4;
kBallSpeedY = 4;
ballSpeed = CGPointMake(kBallSpeedX,kBallSpeedY);
gameLevel = 1;
labelCurrentLevel.text = [NSString stringWithFormat:@"%d",gameLevel];
labelScore.text = [NSString stringWithFormat:@"%d", score];
kMissileFiring=0;
kMissilesLeft=0;
haveMissileView.hidden=YES;

for (UIImageView *rain in rainDrops) { rain.hidden = YES; }
[backgroundImageView setImage:[UIImage imageNamed: backgroundArray[gameLevel-1]]];

NSLog(@"Calling initBricks with level:%d",gameLevel);
[self remapBricks];
[self initTimer];

kGamePause=1;
ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
NSLog(@"*****GAME RESET***********");

}

//=============================================================================
// 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.

#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=NORMAL_BRICK))
{
if (brick_description[y][x] !=METAL_BRICK) {youWin=0;}

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

// 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))
{
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];

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];

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

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

if (livesleft0) && (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

#pragma mark Winning Logic
/*********** You've won **********************/
if ((kHackHitCount) && (hack_hit_count>=HACK_HITCOUNT))
{
hack_hit_count=0;
youWin=1;
NSLog(@"**Hack hitcount reached");
}
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;
}
}

#pragma mark Paddle hits ball logic

// Now lets check if the paddle hit the ball

if (CGRectIntersectsRect(ball.frame, paddle.frame) && ball.center.y=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];

}

#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);

#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];

}

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;

#pragma mark You lost logic
/*********** You've lost **********************/

if (livesleft == 0)
{
NSLog(@"inside no lives left");
// all lives over, so pop the message out and stop timers
// game is frozen
labelGameOver.text = @"Bad Luck!";
labelGameOver.hidden=NO;
labelPause.hidden=YES;
[gameTimer invalidate]; gameTimer = nil;
[backgroundTimer invalidate]; backgroundTimer = nil;
[harderTimer invalidate]; harderTimer = nil;

[self playAudio:booSound];

} // livesleft

} // if ball.center.y

} // playgame

// called after death rotation animation completes
- (void)lifeDeathAnimeDidStop{
if ((livesleft=0)) lives[livesleft].alpha = 0;

}

//=============================================================================
// 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];

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;
}

}

// 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;
}

// 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;

}

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];
}
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];
}
}

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];

}
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

}

// 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);
}

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

// 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);
}

// This is called when the ball hits a solid brick. It shows an explosion
-(void) explodeBrick: (int) y : (int) x
{

NSLog(@"Explosion!!!!!");

// Position the explosion so it is properly centered on the brick
explodeView.center = CGPointMake(bricks[y][x].center.x, bricks[y][x].center.y);
// bring the explosion view to the front
[self.view bringSubviewToFront:explodeView];

explodeView.animationImages = explodeImages;
explodeView.animationDuration = 1;
explodeView.animationRepeatCount = 1;
[explodeView startAnimating];

}

// resets alphas of images of the game. This is just a convenience function
// that I can call from different places to display or hide the lives left and
// the bricks opacity
- (void) resetAlphasforLives:(int) livesVal forBricks:(int) bricksVal
{
NSLog(@"Inside resetAlphaseforLives");
if (bricksVal != -1)
{
for (int rows=0; rows<BRICKS_ROWS; rows++)
{
for (int columns=0; columns<BRICKS_COLUMNS; columns++)
{

CGRect myFrame = bricks[rows][columns].frame;
//myFrame.size.width=60;
//myFrame.size.height=16;
bricks[rows][columns].frame= myFrame;
bricks[rows][columns].alpha = MIN(bricksVal,brick_description[rows][columns]);

}
}
} //if
if (livesVal != -1)
{
for (int i=0; i<LIVES; i++)
{
lives[i].alpha=livesVal;
lives[i].transform = CGAffineTransformIdentity;
}
} //if
}

// called by App delegate when the app is interrupted (home key pressed..)
- (void) saveState
{
NSLog(@"interrupted");
kGamePause=1;
labelPause.hidden = NO;

}

- (void)viewDidUnload {
}

- (void)dealloc {
[ball release];
[paddle release];
[labelScore release];
[song release];
[explodeImages release];
[rainDrops release];

AudioServicesDisposeSystemSoundID(hitSound);
AudioServicesDisposeSystemSoundID(missSound);
AudioServicesDisposeSystemSoundID(clapSound);
AudioServicesDisposeSystemSoundID(explodeSound);
AudioServicesDisposeSystemSoundID(booSound);
AudioServicesDisposeSystemSoundID(holycowSound);
AudioServicesDisposeSystemSoundID(boingSound);
AudioServicesDisposeSystemSoundID(metalSound);
AudioServicesDisposeSystemSoundID(gotlifeSound);
AudioServicesDisposeSystemSoundID(missileSound);

// anything else that I missed?
[super dealloc];

}

@end
[/objc]

Breaking up the implementation in parts


Declare, property and synthesize dance


[objc]
#import "BricksViewController.h"
#import "GBMusicTrack.h"

@implementation BricksViewController

@synthesize ball;
@synthesize paddle;
@synthesize cloud;
@synthesize labelScore;
@synthesize labelCurrentLevel;
@synthesize labelPause;
@synthesize labelGameOver;
@synthesize explodeView;
@synthesize backgroundImageView;
@synthesize missileView;
@synthesize haveMissileView;
@synthesize hackView;
@synthesize volumeView;
@synthesize infoView;
@synthesize helpView;
@synthesize hitSound;
@synthesize missSound;
@synthesize explodeSound;
@synthesize clapSound;
@synthesize boingSound;
@synthesize booSound;
@synthesize holycowSound;
@synthesize metalSound;
@synthesize gotlifeSound;
@synthesize missileSound;
@synthesize gameTimer;
@synthesize backgroundTimer;
@synthesize harderTimer;
@synthesize rainDrops;
[/objc]

  1. What you basically see here is a whole bunch of @synthesize statements, the goal of which is to automatically have xcode generate the required accessor functions that allow us developers to read and write settings via accessor functions.


Things to do when the game loads


[objc]

//=============================================================================
- (void)viewDidLoad {

NSLog(@"Inside ViewDidLoad");
[super viewDidLoad];

//************************
kHackHitCount=0; // 1=skip levels quickly. 0=normal play
//************************

kBallSpeedX = 4;
kBallSpeedY = 4;
tBallSpeedX = 0;
tBallSpeedY = 0;
kMakeItHarder = 10; // Time in seconds to make things harder in the game
kGamePause = 1;
gameTimer = nil;
backgroundTimer = nil;
harderTimer = nil;
gameLevel =1;
kMissileFiring=0;
kMissilesLeft=0;
kVolumeIsOn=1;
kMakeItHarder = 60; // Time in seconds to make things harder in the game

backgroundArray[0]=@"level1.png";
backgroundArray[1]=@"level2.png";
backgroundArray[2]=@"level3.png";
backgroundArray[3]=@"level4.png";
[backgroundImageView setImage:[UIImage imageNamed: backgroundArray[gameLevel-1]]];

// set up the accelerometer - I want to use it to move my paddle
UIAccelerometer *myAccel = [UIAccelerometer sharedAccelerometer];
myAccel.updateInterval = 1.0f/30.0f;
myAccel.delegate=self;

ballSpeed = CGPointMake(kBallSpeedX,kBallSpeedY);
score = 0;
livesleft = LIVES;

// Initialize all the audio streams that we will be playing

UInt32 sessionCategory = kAudioSessionCategory_AmbientSound;
AudioSessionSetProperty (kAudioSessionProperty_AudioCategory,
sizeof (sessionCategory),
&sessionCategory);

[self initAudioStreams:&hitSound :@"hitme" :@"wav"];
[self initAudioStreams:&booSound :@"boo" :@"wav"];
[self initAudioStreams:&missSound :@"die" :@"wav"];
[self initAudioStreams:&clapSound :@"applause" :@"wav"];
[self initAudioStreams:&explodeSound :@"explode" :@"wav"];
[self initAudioStreams:&boingSound :@"boing" :@"wav"];
[self initAudioStreams:&holycowSound :@"holycow" :@"wav"];
[self initAudioStreams:&metalSound :@"metalsound" :@"wav"];
[self initAudioStreams:&gotlifeSound :@"gotlife" :@"wav"];
[self initAudioStreams:&missileSound :@"missile" :@"wav"];

// I am blindly using GBMusicTrack for now based on their instructions...
song = [[GBMusicTrack alloc] initWithPath:[[NSBundle mainBundle] pathForResource:@"background" ofType:@"mp3"]];
//[song setRepeat:YES];
//[song play];

[self initBricks]; // fill in and display all the bricks
[self initLives];
[self initExplosion]; // pre-load explosion array
[self initRain]; // pre-load rain array
[self initTimer]; // start game timers and we are on our way!

UIImage *image = [UIImage imageNamed:@"missile.png"];
missileView = [[[UIImageView alloc] initWithImage:image] autorelease];
missileView.hidden=YES; // load the missile image, hide it for now
[self.view addSubview:missileView];

ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);

}
[/objc]

  1. Let's discuss what we are doing here - its pretty straightforward - this function is called when the game first loads (only once, remember that).

  2. First off, we set some variables to the right values - most of these were explained earlier in the .h file in part I

  3. Some notes: kBallSpeedX and kBallSpeedY will have integral values that decide the next increment of the ball. later you will see in the game logic that that we change these values + or - to take care of bounces, hitting objects etc. That way after these variables are changed based on what the ball hits, when we add these to the current X and Y position of the ball, the ball will move around the screen

  4. You will also see that I set the timers to nil. I am taking a short-cut here - you will see later that when a game ends or pauses, I reset some timers and start them again. But instead of having to figure out which timers are alive or not, I call the "stop" timer on them anyway and setting them to nil is important here. Objective C guarantees that I can invokes messages on nil without crashing, so I don't need to write extra code later to check their status. Some argue this is bad programming. To me, since I know what I am doing, I think its a great convenience

  5. backgroundArray is initialized with different images. Basically, these images will be set to the background depending on which level you are in later

  6. I then go ahead and set up some standard operations to enable and detect the accelerometer. Basically, I have requested that I be notified once in 30 seconds about accelerometer movements. When it detects accelerometer changes, it will invoke my touchesMoved function discussed later

  7. You will also notice that I initialize a bunch of different sounds and assign them handles. initAudioStreams is a convenience function I created to implement the necessary code to initialize a handle to load a wav file and make it ready to play. Basically I am using AudioSystemSound APIs to play them as they are all small sounds. It is easy to use, but also has disadvantages (like no volume control on a per sound basis etc.). The names have already been explained in the .h file in part 1

  8. Next up, in addition to playing sounds , I also wanted to play background music. I could explore and use the AV foundation myself for this, but I chanced upon Jake peterson's excellent GBMusic track that you can simply point to an mp3 and away it goes. I later commented it because I got bored of continuous music

  9. Needless to say, before you access wav files, png files etc. make sure you copy them to the resources folder of your project otherwise your program won't find them

  10. Next up, I call messages to initialize the bricks display, lives display, initialize the explosion sprites, rain animation and start the timers (I will explain it all later). The timers are critical - thats where the game logic is controlled


Some other helper functions


[objc]

//=============================================================================
// play sounds only if volume is not muted
-(void) playAudio: (SystemSoundID) soundId
{
if (kVolumeIsOn) {AudioServicesPlaySystemSound(soundId);}
}
//=============================================================================
// convenience function. Pass it a reference to the SystemSoundID handle, the file and its type and it
// creates an audio stream ready to play
- (void) initAudioStreams: (SystemSoundID *)soundId :(NSString *)soundFile :(NSString *) type
{
NSString *audioPath=[[NSBundle mainBundle] pathForResource:soundFile ofType:type];
CFURLRef audioURL = (CFURLRef) [NSURL fileURLWithPath:audioPath];
AudioServicesCreateSystemSoundID(audioURL, soundId);
}

//=============================================================================
// pre-load the explosion effect into an array.
- (void) initExplosion
{
explodeImages = [[NSMutableArray alloc] initWithCapacity: 34];
for (int i = 1; i <= 34; i++)
{
NSString *imageName = [NSString stringWithFormat:@"boom%d.png",i];
UIImage *image = [UIImage imageNamed:imageName ];
[explodeImages addObject: image];
}
}

[/objc]

  1. initAudioStreams invokes the steps necessary to associate a handle to wav files. It follows the apple procedures exactly

  2. playAudio simply invokes AudioServicesPlaySystemSound but only if kVolumeIsOn variable is 1. You will see later that you can tap on the volume icon on the game to enable/disable sound. This action will toggle kVolumeIsOn between 0 and 1 and the respective sound will be played or not played. It is worthwhile to note that when sounds are played, they are done asynchronously which is great because you can animate while the sound plays in the background

  3. initExplosion basically creates an array of 34 UIImages. Each UIImage will hold an image of an explosion in progress. Later, when a ball or missile hits a brick, I will use UIKit animation APIs to iterate through each image quickly to make it look like a real explosion


The 2D Arrays that define the brick layout and their powers


[objc]

//=============================================================================
// load bricks layout. This array controls what sort of bricks show at each
// cell of the brick matrix

- (void) initBrickLayout
{
NSLog(@"init brick layout");
NSLog(@"Current Game Level is %d",gameLevel);

// You can modify these arrays to respresent the bricks
// as you want for each level. Each block is for a particular level.
// 0 = no brick at this cell
// 1 = normal brick - you hit it, it explodes and you add to your score
// 2 = metal brick - you can't kill it - the ball will hit and bounce off faster
// 3 = super brick - you hit it, it explodes and you will get a new life (if you are not at max)
// 4 = missile brick - you get 5 missiles each time you hit this one

int bricklevel[GAME_LEVELS][BRICKS_ROWS][BRICKS_COLUMNS] = {
{
// level 1
{2,0,0,1,0,0,2},
{0,0,1,1,1,0,0},
{0,1,4,0,4,1,0},
{1,1,3,0,3,1,1},
{1,1,1,0,1,1,1},
{0,1,3,0,3,1,0},
{0,0,4,1,4,0,0},
{2,0,0,1,0,0,2},
},
{
// level 2
{3,2,1,1,1,2,3},
{1,1,1,1,1,1,1},
{2,1,1,1,1,1,2},
{1,1,1,1,1,1,1},
{1,1,2,3,2,1,1},
{4,1,1,2,1,1,4},
{1,2,1,1,1,2,1},
{2,1,1,1,1,1,2},
},
{

// level 3
{0,0,0,3,0,0,0},
{0,0,1,1,1,0,0},
{0,0,1,1,1,0,0},
{0,2,1,1,1,2,0},
{0,1,1,1,1,1,0},
{2,1,1,1,1,1,2},
{1,4,1,1,1,4,1},
{1,1,2,1,2,1,1},
},
{
// level 4
{0,1,0,3,0,1,0},
{2,1,1,1,1,4,2},
{0,1,1,1,1,1,0},
{0,0,1,1,1,0,0},
{0,0,1,1,1,0,0},
{0,1,1,1,1,1,0},
{2,4,1,3,1,1,2},
{1,0,1,0,1,0,1},
},
};

NSLog (@"Copying level %d(-1) to bricks description", gameLevel);
memcpy (brick_description, bricklevel[gameLevel-1], sizeof(brick_description)); // C still rocks. W00t :-)
}
[/objc]

  1. Right, this is why I am using a 2D array for the bricks. It is just visually easy. Each level has a 2D array (hence brick_level[levels][rows][col]. Each number represents a brick type.

  2. So basically, depending on the value of gameLevel, I copy the right array index into brick_description and then display the bricks based on these numbers

  3. If you want to add levels, just add another matrix here and increase GAME_LEVELS #define in the .h  file


Making it a rainy day


[objc]

//=============================================================================
// init rain array
- (void) initRain
{
NSLog(@"Inside initRain");
NSMutableArray *array = [[NSMutableArray alloc] init];
self.rainDrops = array;
[array release];

UIImage *rainImage = [UIImage imageNamed:@"raindrop.png"];
UIImageView *rainView;

// randomly place 50 rain drops across the screen
for (int i = 0; i< 50; i++)
{
rainView= [[UIImageView alloc] initWithImage:rainImage];
rainView.alpha=0.3;
rainView.hidden=YES;

int x = arc4random()% (int)self.view.bounds.size.width;
int y = arc4random()% (int)self.view.bounds.size.height;
rainView.center = CGPointMake (x,y);

// the background image is at index 0, make rain at 1,
// so it is behind the bricks
[self.view insertSubview:rainView atIndex:1];
[self.rainDrops addObject: rainView];
[rainView release];
}
}

// we need to hide the rain for layers that don't have rain
- (void) hideRain
{
for (UIImageView *rain in rainDrops)
{
rain.hidden=YES;
}
}

// animate the rain across the screen
-(void)moveRain{

for (UIImageView *rain in rainDrops) {

CGPoint newCenter = rain.center;
newCenter.y = newCenter.y +2;
newCenter.x = newCenter.x +1;
rain.hidden = NO;

if (newCenter.y > self.view.bounds.size.height){
newCenter.y=0;
newCenter.x = arc4random()%(int)self.view.bounds.size.width;
}
if (newCenter.x > self.view.bounds.size.width){
newCenter.x =0;
} else if (newCenter.x < 0) {
newCenter.x = self.view.bounds.size.width;
}

rain.center = newCenter;

}
}
[/objc]

  1. These are the 'rain animation' functions. Basically, initRain randomly creates 50 rain drops on the screen. It is set to appear at index 1 which is right above the background image at index 0 so it falls below the bricks

  2. Depending on the level, I either show the rain or hide the rain (rain.hidden  = YES | NO)

  3. moveRain just increments the co-ords of each drop so they animate by


Launching the missile


[objc]
//=============================================================================
// launches a missile
// you would think we can use Core Graphics animate to automatically do this
// but we can't as we also need to detect if it hit a brick with CGRectIntersect
// The automatic animation does not allow for detection if intersects, so we need
// to do it ourselves, or, use auto animation in small steps.

- (void) launchMissile;
{
if (!kMissilesLeft) {NSLog(@"You don't have missiles");return;}
if (kMissileFiring) {return;} // only allow 1 missile at a time
haveMissileView.hidden=NO;
kMissileFiring=1;

missileView.hidden=NO;
[self playAudio:missileSound];
missileView.center=CGPointMake(paddle.center.x, paddle.center.y-10);
kMissilesLeft--;
if (!kMissilesLeft) {haveMissileView.hidden=YES;}

NSLog(@"You have %d missiles left",kMissilesLeft);
}

-(void) missileFinished
{
missileView.hidden=YES;
kMissileFiring=0;
}
[/objc]

  1. I explained this earlier - like everything else that moves in this game, the missile is again just a UIView that moves up the screen when you fire it. You fire it by tapping twice on the paddle. That calls launchMissile. When the missile goes to the top of the screen, or, hits a brick first (you will see this in the collision logic part), its job is done.


Initializing the Bricks  & Lives Display


[objc]
//=============================================================================
// load the bricks images and create the bricks matrix. Also create the lives display
- (void) initBricks
{
NSLog(@"Inside initBricks");

brickImages[0]=@"brick2.png"; //normal
brickImages[1]=@"brick2.png";
brickImages[2]=@"steel.png"; // cant destroy
brickImages[3]=@"brick3.png"; // special powers - life
brickImages[4]=@"brick1.png"; // special powers - missiles

[self initBrickLayout];

[/objc]

We load up the bricks array with the correct images and then call initBrickLayout which copies the correct brick matrix description to the brick_description array.
The logic will be that based on the brick_array matrix description for the level, we will show the right brick at the right place

[objc]

for (int rows=0; rows<BRICKS_ROWS; rows++)
{

for (int columns=0; columns<BRICKS_COLUMNS; columns++)
{
int imageType = brick_description[rows][columns];
UIImage *image = [UIImage imageNamed:brickImages[imageType]];
bricks[rows][columns] = [[[UIImageView alloc] initWithImage:image] autorelease];
CGRect myFrame = bricks[rows][columns].frame;

myFrame.size.width=30;
myFrame.size.height=10;
// position the bricks so there is some space between them
myFrame.origin = CGPointMake(columns*42+5,rows*17+80);
//NSLog(@"Width:%f, height:%f",myFrame.size.width, myFrame.size.height);

bricks[rows][columns].frame= myFrame;
bricks[rows][columns].alpha = MIN(1,brick_description[rows][columns]);
// every brick is a subview - show it

[self.view addSubview:bricks[rows][columns]];
} //cols
} //rows
}

[/objc]

Some laziness on my part. I drew the bricks to be originally larger than what I am displaying. I then found them too easy to kill, so I just changed their size in code (see myFrame.size.width/height lines). Ideally, you should not waste resources.
Anyway, so now, we iterate through the bricks matrix and display them according to the brick type in brick_description. As far as the MIN(1, brick_description....) line goes, all it is doing is saying "display any bricks that don't have a 0 in the description matrix (since 0 means no brick there)". Whether the brick is a normal brick (1) or a super brick (2,3,4), they will all be displayed, so have an alpha of 1 (alpha determines opacity. 0 = transparent. 1 = opaque)

Finally, each brick is actually displayed on the screen as a new view (self.view addSubview)

[objc]

//=============================================================================
// load the bricks images and create the bricks matrix. Also create the lives display
- (void) remapBricks
{
NSLog(@"Inside remapBricks");

[self initBrickLayout];

for (int rows=0; rows<BRICKS_ROWS; rows++)
{

for (int columns=0; columns<BRICKS_COLUMNS; columns++)
{
int imageType = brick_description[rows][columns];
[bricks[rows][columns] setImage:[UIImage imageNamed: brickImages[imageType]]];
bricks[rows][columns].alpha = MIN(1,brick_description[rows][columns]);
} //cols
} //rows
}
[/objc]

I could have probably merged this into initBricks but thought it better to be separate. Basically, initBricks is only called once when the game loads.
After that, the brick layers have already been created, so we don't need to create new UIViews etc. All we need to do to move up and down levels
is simply to copy a new matrix of brick descriptions, change the brick image at the relevant positon to a new brick image and change its alpha based on the new matrix.
So remapBricks is called after that to move up levels, reset to level 1 after death etc.

[objc]
//=============================================================================
// Initializes the lives display on top left
- (void) initLives
{
NSLog(@"Inside initLives");
// load lives array and create the lives display
for (int i=0; i<LIVES;i++)
{
UIImage *image = [UIImage imageNamed:@"lives.png"];
lives[i] = [[[UIImageView alloc] initWithImage:image] autorelease];
CGRect myFrame = lives[i].frame;
// position the lives so there is some place between them
myFrame.origin = CGPointMake(i*40 % 160 ,20+(20* (i/4)));
lives[i].frame= myFrame;
// every life is a subview - show it
[self.view addSubview:lives[i]];
}
}
[/objc]

This function basically displays a small paddle for each life you have remaining at the top left. It breaks up the # of lives in rows of 4 each.

The main game timers


[objc]

//=============================================================================
// Initializes all timers,
// gameTimer: The core timer that controls game logic
// backgroundTimer: animates the background elements
// harderTimer: after X seconds, it makes the game harder
- (void) initTimer
{
NSLog(@"Inside initTimer");
[gameTimer invalidate];
[backgroundTimer invalidate];
[harderTimer invalidate];

gameTimer=[NSTimer scheduledTimerWithTimeInterval:1.0/30.0 target:self selector:@selector(playGame:) userInfo:nil repeats:YES];
backgroundTimer=[NSTimer scheduledTimerWithTimeInterval:1.0/30.0 target:self selector:@selector(adjustBackground:) userInfo:nil repeats:YES];
harderTimer = [NSTimer scheduledTimerWithTimeInterval:kMakeItHarder target:self selector:@selector(makeItHarder:) userInfo:nil repeats:YES];

}
[/objc]

The entire game logic is really happening inside the gameTimer callback function (Which happens to be playGame). Basically, gameTime is called once in 30 seconds. So every
1/30 seconds my program checks where is the ball, where are the missiles, where is the paddle, who hit whom and update scores and status accordingly.

BackgroundTimer takes care of animating some non essential stuff (the cloud moving and the moveRain functions are processed in BackgroundTimer callback. The backgroundTimer callback does however do one important game related function - it moves the missile too. More on that later

When life just got harder


[objc]

//=============================================================================
// This is the callback that is called when the game needs to get harder
- (void) makeItHarder:(NSTimer *)timer
{

if (kGamePause) {return;}

[self playAudio:holycowSound];
NSLog (@"TIME TO MAKE IT HARDER FOR LEVEL:%d", gameLevel);

//NSLog(@"*********************Inside MakeItHarder, bumping up ball speed");

NSLog(@"Original SpeedX:%d, SpeedY:%d",kBallSpeedX,kBallSpeedY);
if (kBallSpeedX<8) {kBallSpeedX++;}
if (kBallSpeedY<8) {kBallSpeedY++;}

ballSpeed.x = (ballSpeed.x <0) ? kBallSpeedX:-kBallSpeedX;
ballSpeed.y = (ballSpeed.y <0) ? kBallSpeedY:-kBallSpeedY;

NSLog(@"Inside MakeItHarder, bumping up ball speed to X:Y %d:%d", kBallSpeedX, kBallSpeedY);
ballSpeed = CGPointMake(kBallSpeedX,kBallSpeedY);

}
[/objc]

There are many ways you can make a level harder. I used to hate those ping pong players who would always play defensive and waste time. Once in a while, I'd try and force them into a smash round. Same here. After this timer ticks off, I increase the ball speed. You can add more features here, like possibly moving the bricks down every few seconds etc (if you do, you will need to add another collision logic later to see if the paddle hit the bricks (which is currently impossible)

Background Animation and Missile Animation


[objc]

//=============================================================================
// This is the callback that backgroundTimer calls at every interval
- (void) adjustBackground: (NSTimer *)timer

{
//if (kGamePause) {return;}

[/objc]

I originally did not allow the background to animate if the game was paused. Then I thought it to be cooler if it still did, so I commented it out

[objc]

if (gameLevel % 2)
{
float newposx;
cloud.hidden=NO;
[self hideRain];

newposx = cloud.center.x+0.3; // move cloud image across the screen smoothly
if (cloud.center.x > self.view.bounds.size.width+80) {newposx=-64;} // wrap it around
cloud.center = CGPointMake(newposx, cloud.center.y);
}
else
{
cloud.hidden=YES;
[self moveRain];
}
[/objc]

So I am basically either moving clouds or showing rain for every alternate level. Short-cuts. Short-cuts.

[objc]
if (kMissileFiring)
{

int missileY;
missileY=missileView.center.y-10;
if (missileY<0)
{
[self missileFinished];
}

else
{
missileView.center=CGPointMake(missileView.center.x, missileY);
}

} // kMissileFiring
}
[/objc]

Remember I said the missile firing is also handled here? Well, right here. If kMissileFiring is on, then we know we need to animate the missile up. All we do here is decrease the Y co-ord of the missile so it keeps moving up. Once it moves up the screen, we hide it (self missileFinished). The missile launch happens elsewhere. This is just moving it, once it is fired.

Now, you may ask why do we need to do this? Can't we just use the CGTransform functions to automatically and cool-ly move it up? Yes you can. But here is the deal. If you do, you will not be able to detect when it hits anything. If you read up on CG Transform functions, you first specify a start, then an end (so you specify missile Y = some value and then again missile Y = 0 one after the other and then invoke start animation. iOS takes care of transitioning from "some value" to "0" but to everyone else outside of the animation module, the position of the missile is already 0 (it has already reached its destination). So net-net you can't use CG Intersect Rect etc. to detect collisions with auto-animation of this type. Boo.

Reset


[objc]

//=============================================================================
// resets game back to normal.
-(void) resetGame
{
NSLog(@"Inside reset Game");
score = 0;
livesleft = LIVES;
kBallSpeedX = 4;
kBallSpeedY = 4;
ballSpeed = CGPointMake(kBallSpeedX,kBallSpeedY);
gameLevel = 1;
labelCurrentLevel.text = [NSString stringWithFormat:@"%d",gameLevel];
labelScore.text = [NSString stringWithFormat:@"%d", score];
kMissileFiring=0;
kMissilesLeft=0;
haveMissileView.hidden=YES;

for (UIImageView *rain in rainDrops) { rain.hidden = YES; }
[backgroundImageView setImage:[UIImage imageNamed: backgroundArray[gameLevel-1]]];

NSLog(@"Calling initBricks with level:%d",gameLevel);
[self remapBricks];
[self initTimer];

kGamePause=1;
ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
NSLog(@"*****GAME RESET***********");

}
[/objc]

Nothing much. Very similar to what I do when the game loads. Reset all the variables etc. So logically, this is called when the game gets over (You die, or, you finish all levels)

Explosion Time!!


[objc]
// This is called when the ball hits a solid brick. It shows an explosion
-(void) explodeBrick: (int) y : (int) x
{

NSLog(@"Explosion!!!!!");

// Position the explosion so it is properly centered on the brick
explodeView.center = CGPointMake(bricks[y][x].center.x, bricks[y][x].center.y);
// bring the explosion view to the front
[self.view bringSubviewToFront:explodeView];

explodeView.animationImages = explodeImages;
explodeView.animationDuration = 1;
explodeView.animationRepeatCount = 1;
[explodeView startAnimating];

}
[/objc]


My favorite function. (Ok, objective C wants me to call it a message, but I prefer function. I grew up on em).
Remember the explosion array we init'd during ViewDidLoad? Time to play it. All we do is get the center X and Y co-ordinate of the brick that was hit and use the UIView animation to animate through all the 34 images in 1 second to give a great animation of an explosion

Goodbye and thanks for the fish


[objc]
- (void)viewDidUnload {
}

- (void)dealloc {
[ball release];
[paddle release];
[labelScore release];
[song release];
[explodeImages release];
[rainDrops release];

AudioServicesDisposeSystemSoundID(hitSound);
AudioServicesDisposeSystemSoundID(missSound);
AudioServicesDisposeSystemSoundID(clapSound);
AudioServicesDisposeSystemSoundID(explodeSound);
AudioServicesDisposeSystemSoundID(booSound);
AudioServicesDisposeSystemSoundID(holycowSound);
AudioServicesDisposeSystemSoundID(boingSound);
AudioServicesDisposeSystemSoundID(metalSound);
AudioServicesDisposeSystemSoundID(gotlifeSound);
AudioServicesDisposeSystemSoundID(missileSound);

// anything else that I missed?
[super dealloc];

}

@end
[/objc]

Finally, I deallocate stuff when its time to say goodbye.

Huh ? Where is the GAME!?!


As it turns out, wordpress barfs with long posts. So the playGame function and the code for detecting touches is in part III.

2 comments: