Go into your Game.m file and edit the SPQuad.

Change its:

  • ‘x’ and ‘y’ to ’0′;
  • ‘width’ to ‘self.width’;
  • and ‘height’ to ‘self.height’.

That gives us a nice background BUT it’s bright red, which isn’t very good. We want some nice gradients like we did in UIKit for the menu’s.

Well in sparrow you can actually set a quads color property to different values for each corner, set top left and top right the same but different to bottom right and bottom left and you have a nice gradient.

Here’s what you should have now in your init method

1
 
  2
 
  3
 
  4
 
  5
 
  6
 
  7
 
  8
 
  9
 
  
//************* INITIAL STARTUP **************//
 
  //Background Gradient
 
  SPQuad *backgroundColor = [[SPQuad alloc] initWithWidth:self.width height:self.height];
 
  [backgroundColor setColor:0xcccccc ofVertex:0]; //set the color differently
 
  [backgroundColor setColor:0xcccccc ofVertex:1]; //in each corner to get
 
  [backgroundColor setColor:0x868686 ofVertex:2]; //a nice gradient
 
  [backgroundColor setColor:0x868686 ofVertex:3];
 
  [self addChild:backgroundColor]; //Add to screen
 
  [backgroundColor release]; //Don't forget to release it now

Now something you’ll notice is “I love Programatically created graphics”. There are several reasons for that.

  • It reduces the size of the resulting binary a lot.
  • Changes can be made quickly in code rather then firing up Pixelmator or Photoshop.
  • Scaling, with an image scaling loses quality greatly. But as the image was generated the computer regenerates the image improving the quality.
  • And lastly. I hate (read can’t) make graphics so if I have to that slows things down massively.

For me the last point holds the most sway over which option I choose. This isn’t a problem if you have an artist or are working with a team, but if like me you do pretty much everything yourself then graphics can be a major slow up.

Well we need to be able to talk to the AppDelegate so we can get back to the main menu. There are multiple ways we can do this, and throughout the length of this tutorial we’ll probably use all of them. But for this we’ll make our own GameDelegate protocol.

At the top (just under the commented out copyright stuff) of Game.h add this:

1
 
  2
 
  3
 
  4
 
  
@protocol SPGameDelegate
 
  @required
 
  -(void)removeGameView;
 
  @end

Very simply what this does is say we have a new protocol (protocols are a collection of methods that will appear in the class (if that class confirms to that protocol)). And that any classes which conform to that protocol must have the method ‘removeGameView’ in their implementation.

(For more information about them read the wikipedia page here)

Now make these changes to the Game.h

1
 
  2
 
  3
 
  4
 
  5
 
  
@interface Game : SPStage {
 
  id <SPGameDelegate> delegate;
 
  }
 
   
 
  @property(nonatomic,assign) id <SPGameDelegate> delegate;

The ‘id’ in objc just means it’s location in memory and the name we’ll give this is delegate. But we send about <something>’s that the object should conform to the protocol. So that id can be set to any object at all (anything can become the games delegate), but the instance of that class must conform to the SPGameDelegate protocol.

And the @property line is so we can access the delegate object from other objects like ‘game.delegate’.

Lastly just add ‘@synthesize delegate;‘ under ‘@implementation Game‘ in the Game.m file.



In the AppDelegate we have to make a single change to the game creation method, add.

1
 
  2
 
  
sparrowView.stage = game; //Already there just to show you where to put it.
 
  game.delegate = self;

But that’ll give us a warning when we build because we haven’t made the AppDelegate support the SPDelegate protocol yet.

To do that we need to add <SPGameDelegate> to the @interface declaration in AppDelegate.h so it looks like:

1
 
  2
 
  3
 
  4
 
  5
 
  6
 
  
@interface AppDelegate_iPhone : NSObject <UIApplicationDelegate, SPGameDelegate>{
 
  //..........what's already there
 
  //Add these 2 lines at the bottom
 
  int slideRight;
 
  int slideLeft;
 
  }

Now for the removeGameView method itself.

1
 
  2
 
  3
 
  4
 
  5
 
  6
 
  7
 
  8
 
  9
 
  10
 
  11
 
  12
 
  13
 
  14
 
  15
 
  16
 
  17
 
  18
 
  19
 
  20
 
  21
 
  22
 
  23
 
  24
 
  25
 
  26
 
  27
 
  28
 
  29
 
  30
 
  31
 
  32
 
  33
 
  34
 
  35
 
  36
 
  37
 
  38
 
  39
 
  40
 
  41
 
  42
 
  43
 
  44
 
  45
 
  46
 
  47
 
  48
 
  49
 
  50
 
  51
 
  52
 
  53
 
  54
 
  55
 
  56
 
  57
 
  58
 
  59
 
  60
 
  61
 
  62
 
  63
 
  64
 
  65
 
  66
 
  67
 
  68
 
  69
 
  70
 
  71
 
  72
 
  73
 
  74
 
  75
 
  76
 
  
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 
  //..........what's already there.......
 
  //Just above 'return YES' add this
 
  //We are working out the distance to slide the buttons of screen here to use throughout
 
  slideRight = (playButton.frame.origin.x+window.frame.size.width);
 
  slideLeft = -slideRight;
 
   
 
  	return YES;
 
  }
 
   
 
  -(void)removeGameView{
 
  	[SPAudioEngine stop];
 
  	[sparrowView stop];
 
   
 
  	[UIView beginAnimations:@"Fade Out" context:nil];
 
  	[UIView setAnimationDelegate:self];
 
  	[UIView setAnimationDuration:0.5f];
 
  	sparrowView.alpha = 0;
 
  	[UIView setAnimationDidStopSelector:@selector(removeSparrowPart2)];
 
  	[UIView commitAnimations];
 
  }
 
   
 
  -(void)removeSparrowPart2{
 
  	sparrowView.stage = nil;
 
  	[game release];
 
  	game = nil;
 
  	[sparrowView removeFromSuperview];
 
  	sparrowView.alpha = 1;
 
   
 
  	[self resetGameButtons];
 
  }
 
   
 
  -(void)resetGameButtons{
 
  	//Reset Buttons
 
  	[UIView beginAnimations:@"SlideOut" context:nil];
 
  	[UIView setAnimationDuration:0.2];
 
  	[UIView setAnimationDelay:0.6];
 
  	OpenFeintButton.alpha = 1;
 
  	[UIView commitAnimations];
 
   
 
  	[UIView beginAnimations:@"Slide_Out" context:nil];
 
  	[UIView setAnimationDuration:0.5];
 
  	[UIView setAnimationDelegate:self];
 
  	[UIView setAnimationDidStopSelector:@selector(resetGameButtons2)];
 
  	playButton.frame = CGRectOffset(playButton.frame, slideLeft, 0);
 
  	settingsButton.frame = CGRectOffset(settingsButton.frame, slideRight, 0);
 
  	aboutButton.frame = CGRectOffset(aboutButton.frame, slideLeft, 0);
 
  	creditsButton.frame = CGRectOffset(creditsButton.frame, slideRight, 0);
 
  	//Fade out buttons
 
  	playButton.alpha = 1;
 
  	settingsButton.alpha = 1;
 
  	aboutButton.alpha = 1;
 
  	creditsButton.alpha = 1;
 
  	[UIView commitAnimations];
 
   
 
  	[UIView beginAnimations:@"Fade Out" context:nil];
 
  	[UIView setAnimationDuration:0.25];
 
  	[UIView setAnimationDelay:0];
 
  	titleView.alpha = 1;
 
  	titleViewShadow.alpha = 0.1;
 
  	[UIView commitAnimations];
 
   
 
  	[UIView beginAnimations:@"FadeIn" context:nil];
 
  	[UIView setAnimationDuration:0.3];
 
  	backgroundView.alpha = 1;
 
  	[UIView commitAnimations];
 
  }
 
  -(void)resetGameButtons2{
 
  	[UIView beginAnimations:@"SlideOver" context:nil];
 
  	[UIView setAnimationDuration:0.1];
 
  	playButton.frame = CGRectOffset(playButton.frame, 10, 0);
 
  	settingsButton.frame = CGRectOffset(settingsButton.frame, -10, 0);
 
  	aboutButton.frame = CGRectOffset(aboutButton.frame, 10, 0);
 
  	creditsButton.frame = CGRectOffset(creditsButton.frame, -10, 0);
 
  	[UIView commitAnimations];
 
  }

Most that it just animation code ‘removeGameView’ and ‘removeSparrowPart2′ are the only ones that do anything at all BUT animation.

First it stops the Sparrow audio engine and then the Sparrow View.

After a little animation ‘removeSparrowPart2′ removes the game instance variable, releases it, sets it to nil (safety net incase it didn’t release properly), removes the sparrowView itself from the screen, sets it back to fully visible and then fires off the rest of the animations (which are effectively just the original animations in reverse).








Back to the Game class

Ok now we’ve setup a way out of the game view we need to create the reason users will what to stay in it, the game itself.

NOTE: Although we’ll be covering basic AI, ‘basic’ is the operative word here as it is… well……. a pong game. Although in the next part of the tutorial we will be trying to add a scaling increase in difficulty as the game goes on.




In the Game.h file add these lines to the @interface decleration

1
 
  2
 
  3
 
  4
 
  5
 
  6
 
  7
 
  8
 
  9
 
  10
 
  11
 
  12
 
  13
 
  14
 
  
//Under the id delegate line
 
   
 
  	SHCircle *ball;
 
  	SPImage *paddle1;
 
  	SPImage *paddle2;
 
  	SPRectangle *player1Side;
 
  	SPRectangle *player2Side;
 
  	SPQuad *guideX1;
 
  	SPQuad *guideY1;
 
   
 
  	BOOL gamePaused;
 
   
 
  	float ballXSpeed;
 
  	float ballYSpeed;

These will be available anywhere in our Games implementation (Anywhere in Game.m between “@implementation Game” and “@end”).

Now sparrow has an SPQuad (square) class but not an SPCircle class. But rather then fall back to a graphics (I mentioned earlier I try to not use them) I have a few other options that still let me create a circle from code. I could fall back to using core graphics and SPTexture (+SPImage to display the texture) or, what i actually did, i can browse over the Sparrow Forum looking for a circle class.

More specifically the “List Of User Contributions” thread, which is basically just a list of user created modifications and addons. There is everything there from a Sparrow-Framework specific Chipmunk-Wrapper to classes that handle Parallax backgrounds and thumbstick controllers.

By the way if you even have a problem with your game sparrow’s forum is a great place to go for help as it has a very active community there.


Anyway one of the Add-ons is called SHCircle (perfect for us). It’s basically the same as SPQuad – the set color per corner feature we used for the background. So download the 2 files and drag them into our project in xcode.


In Game.m replace your dealloc method with this.

1
 
  2
 
  3
 
  4
 
  5
 
  6
 
  7
 
  8
 
  9
 
  10
 
  11
 
  
- (void)dealloc {
 
  	NSLog(@"Game Released");
 
  	[ball release];
 
  	[paddle1 release];
 
  	[paddle2 release];
 
  	[guideX1 release];
 
  	[guideY1 release];
 
  	[player1Side release];
 
  	[player2Side release];
 
  	[super dealloc];
 
  }




At the top of the Game.m file first add this (using the #import “Game.h” as a guide where and what to override)

1
 
  2
 
  3
 
  4
 
  5
 
  6
 
  7
 
  8
 
  9
 
  10
 
  11
 
  12
 
  13
 
  14
 
  15
 
  16
 
  
#import "Game.h"
 
   
 
  #define kAISpeed 3.5
 
   
 
  // --- private interface ---------------------------------------------------------------------------
 
  @interface Game ()
 
   
 
  -(void)remove:(SPTouchEvent*)event;
 
  -(void)movementController:(SPEnterFrameEvent*)event;
 
  -(void)collisionController:(SPEnterFrameEvent*)event;
 
  -(void)paddleTouched:(SPTouchEvent*)event;
 
  -(void)pause;
 
  -(void)gameOver;
 
   
 
  @end
 
  // --- class implementation ------------------------------------------------------------------------


Now back into the init method.

I’ll do this bit at a time as the init method is quite big and just about everything is setup from it.

1
 
  2
 
  3
 
  4
 
  5
 
  6
 
  7
 
  8
 
  9
 
  10
 
  11
 
  12
 
  13
 
  14
 
  15
 
  16
 
  17
 
  18
 
  
/** The Paddles Position Guides **/
 
  //Create the here because we what them on the screen below the ball and paddle,
 
  // but their positions are worked out relative to the paddle so we put their
 
  //postitioning code after the paddle is added to the screen
 
   
 
  guideX1 = [[SPQuad alloc] initWithWidth:1 height:55];
 
  [self addChild:guideX1];
 
  guideY1 = [[SPQuad alloc] initWithWidth:320 height:1];
 
  [self addChild:guideY1];
 
  /** The Paddles Position Guides **/
 
   
 
  //The ball Itself
 
  ball = [[SHCircle alloc] initWithWidth:30 height:30];
 
  ball.x = (self.width/2-ball.width/2);
 
  ball.y = (self.height/2-ball.height/2);
 
  ball.color = 0x0000FF;
 
  [self addChild:ball];
 
  //We release it later i didn't forget the release call

Hopefully the code comments will example that enough but we just added 3 things to the stage. 1 was the ball, which is 30px on the iPhone, positioned in the middle of the screen and set to blue.

1
 
  2
 
  3
 
  4
 
  5
 
  6
 
  7
 
  8
 
  9
 
  10
 
  11
 
  12
 
  13
 
  14
 
  15
 
  16
 
  17
 
  18
 
  19
 
  20
 
  21
 
  22
 
  23
 
  24
 
  25
 
  26
 
  27
 
  28
 
  29
 
  30
 
  31
 
  32
 
  
/***** The Paddel Textures *****/
 
  //Load the graphics only once and use it twice.
 
  SPTexture *paddleTexture = [[SPTexture alloc] initWithContentsOfFile:@"scroller.png"];
 
   
 
  //Setup Paddle 1
 
  paddle1 = [[SPImage alloc] initWithTexture:paddleTexture];
 
  paddle1.y = (self.height - paddle1.height - (self.height*0.025));
 
  paddle1.x = (self.width*0.5)-(paddle1.width*0.5);
 
  [self addChild:paddle1];
 
   
 
  /***** The Paddles Position Guides *****/
 
  //Again these need to be added to the screen before the paddle but their positions
 
  guideX1.color = 0x666666;  //are relative to the paddle so we have to split the setup
 
  guideX1.alpha = 0.15;
 
  guideX1.visible = NO;  //Only appear when the user presses on the paddle
 
  guideX1.x = paddle1.x;
 
  guideX1.y = (paddle1.y-guideX1.height+1);
 
  guideX1.width = paddle1.width;
 
   
 
  guideY1.color = 0xFFFF00;
 
  guideY1.alpha = 0.08;
 
  guideY1.visible = NO;
 
  guideY1.y = paddle1.y;
 
  /***** The Paddles Position Guides *****/
 
   
 
  //Setup Paddle 2
 
  paddle2 = [[SPImage alloc] initWithTexture:paddleTexture];
 
  paddle2.y = (self.height*0.025);
 
  paddle2.x = (self.width*0.5)-(paddle2.width*0.5);
 
  [self addChild:paddle2];
 
   
 
  [paddleTexture release]; //Release the paddle texture now we've finnishes with it.

This is quite a big block of code but all we do here is create the 2 paddles, player 1′s and the AI’s.

There are 2 paddles but 1 image file so to save loading it twice we create a texture from that file and then use that to create the images rather then the file path for the image.

After we’ve positioned paddle 1 at the bottom of the screen we know where it is so can position those guides we added to the screen before.

Finally we setup the AI’s paddle and release the texture of the paddle we used earlier.


I did use 1 image however as you will have noticed, the one for the paddles. Download it here.



And finally (for the init method at least).

1
 
  2
 
  3
 
  4
 
  5
 
  6
 
  7
 
  8
 
  9
 
  
//These aren't even display objects they are just give us a box for each half of the screen to check where the ball is.
 
  //Really these isn't actually needed for this (we could use some basic math + the balls y location) but it uses another part of
 
  player2Side = [[SPRectangle alloc] initWithX:0 y:0 width:self.width height:((self.height*0.5)-1)]; //sparrow so is good for
 
  player1Side = [[SPRectangle alloc] initWithX:0 y:((self.height*0.5)+1) width:self.width height:((self.height*0.5)-1)]; //learning.
 
   
 
  //Default Values
 
  ballYSpeed = 4;
 
  ballXSpeed = 4;
 
  gamePaused = NO;

Lastly we are creating 2 rectangles, 1 for each half of the screen, as references for later and setting the default values for things.




Now we have everything setup but if you build and run you’ll see everything on screen but nothing actually happening.


Now for the game we need:

  • Basic Collision Detection;
  • Movement of the ball and paddles;
  • Basic AI to compete against;
  • A game loop to update all these;

For each of these we’ll have a separate function controlling only that. So add 4 more lines to the bottom of your initWith… method.

1
 
  2
 
  3
 
  4
 
  5
 
  
[self addEventListener:@selector(movementController:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME]; //Ball's movement
 
  [self addEventListener:@selector(collisionController:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME]; //Collisions
 
  [self addEventListener:@selector(AIController:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME]; // AI
 
   
 
  [paddle1 addEventListener:@selector(paddleTouched:) atObject:self forType:SP_EVENT_TYPE_TOUCH];

Sparrow lets you specify methods to handle different events. Here we are setting “movementController:”, “collisionController:” and “AIController:” to be called once every frame and “paddleTouched:” to be called when the paddle is well… (you guessed it) touched.

Add these methods to your Game.m file between the init method and the dealloc method.

1
 
  2
 
  3
 
  4
 
  5
 
  6
 
  7
 
  
-(void)movementController:(SPEnterFrameEvent*)event{
 
  	if (!gamePaused) {
 
  		//Move The Ball
 
  		ball.x += ballXSpeed;
 
  		ball.y += ballYSpeed;
 
  	}
 
  }

This is VERY simple. Every frame if the game is running (not paused/stopped) the ball’s position will be increased by ball_Speed. There is slightly better version of this which is “ball.x += ballXSpeed*event.passedTime;”, ball_Speed needs to be 150 rather then 5 though. However with this if OpenFeint finishes starting up when the user is in the game the ball will be shot forward passed the edge of the screen.

1
 
  2
 
  3
 
  4
 
  5
 
  6
 
  7
 
  8
 
  9
 
  10
 
  11
 
  12
 
  13
 
  14
 
  15
 
  16
 
  17
 
  18
 
  19
 
  20
 
  21
 
  22
 
  23
 
  24
 
  25
 
  26
 
  27
 
  28
 
  29
 
  30
 
  31
 
  32
 
  33
 
  34
 
  
-(void)collisionController:(SPEnterFrameEvent*)event{
 
  	if (!gamePaused) {
 
  		//Check the ball's sides
 
  		if ((ball.x+ball.width) >= self.width && ballXSpeed >= 0) {
 
  			ballXSpeed = -ballXSpeed;
 
  		}
 
  		else if (ball.x <= 0 && ballXSpeed < 0){
 
  			ballXSpeed = -ballXSpeed;
 
  		}
 
  		//Check the ball's top & bottom
 
  		if ((ball.y+ball.height) >= self.height && ballYSpeed >= 0) {
 
  			ballYSpeed = -ballYSpeed;
 
  			//[self gameOver];
 
  		}
 
  		else if (ball.y <= 0 && ballYSpeed < 0){
 
  			ballYSpeed = -ballYSpeed;
 
  			//[self gameOver];
 
  		}
 
   
 
  		/*** Check Ball and paddles ***/
 
  		//Check Paddle 1
 
  		if ((ball.y+ball.height) >= paddle1.y && ballYSpeed >= 0) {
 
  			if ((ball.x+ball.width) >= paddle1.x && ball.x <= (paddle1.x+paddle1.width)) {
 
  				ballYSpeed = -ballYSpeed;
 
  			}
 
  		}
 
  		//Check Paddle 2
 
  		if (ball.y <= (paddle2.y+paddle2.height) && ballYSpeed < 0) {
 
  			if ((ball.x+ball.width) >= paddle2.x && ball.x <= (paddle2.x+paddle2.width)) {
 
  				ballYSpeed = -ballYSpeed;
 
  			}
 
  		}
 
  	}
 
  }

This is the most complicated one out of the 3 but is still very simple.

All it does is check if the ball has hit (in order):

  • the right side of the screen;
  • the left side of the screen;
  • the bottom, in which case the computer won;
  • the top, in which case you won;
  • your paddle;
  • or the AI’s paddle.

If it has hit the paddles then its Y speed is reversed (but only if it hasn’t already been, so we avoid it getting stuck) and if it has hit the sides its X speed is reversed.

1
 
  2
 
  3
 
  4
 
  5
 
  6
 
  7
 
  8
 
  9
 
  10
 
  11
 
  12
 
  13
 
  14
 
  15
 
  16
 
  17
 
  18
 
  19
 
  20
 
  
-(void)AIController:(SPEnterFrameEvent*)event{
 
  	if (!gamePaused) {
 
  		if ([ball.bounds intersectsRectangle:player2Side]) {
 
  			if ((ball.x+ball.width)*0.5 > (paddle2.x+paddle2.width)*0.5) {
 
  				paddle2.x += kAISpeed;
 
  			}
 
  			else if ((ball.x+ball.width)*0.5 < (paddle2.x+paddle1.width)*0.5) {
 
  				paddle2.x += -kAISpeed;
 
  			}
 
  		}
 
  		else {
 
  			if ((paddle2.x+(paddle2.width*0.5)) > (self.width*0.5)) {
 
  				paddle2.x += -1.2;
 
  			}
 
  			else if ((paddle2.x+(paddle2.width*0.5)) < (self.width*0.5)){
 
  				paddle2.x += 1.2;
 
  			}
 
  		}
 
  	}
 
  }

This simply checks if the ball is on its side of the screen (using one of the SPRectangles we created earlier) and if it is moves to block it. If it isn’t one its side of the screen it just moves slowly toward the center of the screen. In part 4 we’ll work on improving the AI’s randomness gameplay.

1
 
  2
 
  3
 
  4
 
  5
 
  6
 
  7
 
  8
 
  9
 
  10
 
  11
 
  12
 
  13
 
  14
 
  15
 
  16
 
  17
 
  18
 
  19
 
  
-(void)paddleTouched:(SPTouchEvent*)event{
 
  	SPTouch *touch = [[event touchesWithTarget:self] anyObject];
 
  	if (touch.phase == SPTouchPhaseBegan) {
 
  		guideX1.visible = YES;
 
  		guideY1.visible = YES;
 
  	}
 
  	else if (touch.phase == SPTouchPhaseMoved) {
 
  			SPPoint *currentPos = [touch locationInSpace:self];
 
  			SPPoint *previousPos = [touch previousLocationInSpace:self];
 
  			SPPoint *dist = [currentPos subtractPoint:previousPos];
 
   
 
  			paddle1.x += dist.x;
 
  			guideX1.x = paddle1.x;
 
  	}
 
  	else if (touch.phase == SPTouchPhaseEnded || touch.phase == SPTouchPhaseCancelled) {
 
  		guideX1.visible = NO;
 
  		guideY1.visible = NO;
 
  	}
 
  }

And lastly this just handles the paddle being dragged around the screen. Again sparrow is very easy to use. We simply create an SPTouch from the SPTouchEvent.

If the touch has just started, make the guides visible. If it has moved, move the paddle along by the amount it moved. If the touch has just stopped, make the guides disappear again.



Now if you Build & Go you should have the game running with the ball bouncing off everything it’s meant to.

However obviously we can’t just leave it like this so.

Coming In Part 4

  • Improving the game play and refining it;
  • Improving the AI;
  • Adding scoring;
  • Adding some basic settings;

Hope your enjoying these tutorials so far, if you have any questions please leave them in the comments bellow.