Author Archives: jakeheis

Creating a Flappy Bird Clone

After Flappy Bird was taken down off the App Store, there was an influx of Flappy Bird clones to take its place. I was challenged by my family to see if I myself could make a clone, and was subsequently shocked at how easy it was to make a Flappy Bird look alike – it only took around an hour and very little code! I had a fun time making my version of Flappy Bird, which I call Flappy Bat, and I thought others might have some fun making their own version. In this post, I will go through step-by-step on how to write a Flappy Bird clone, but if you’d rather just see the code, head on over to https://github.com/jakeheis/FlappyBat.

Here’s what the finished product will look like:

Screen Shot 2014-02-13 at 11.00.51 AM

Difficulty

The game is simple enough that no complicated game frameworks need to be used. That means no Cocos2D, no Sprite Kit, no Quartz Core, and, most importantly, no OpenGL.

Some prior Objective-C knowledge is definitely needed, however, in order to follow this tutorial.

Making the game

Start by creating a new “Single View Application”. I called mine “Flappy Bat” with a FB class prefix.

Screen Shot 2014-02-13 at 8.53.42 AM

Go to FBViewController.h and add a few properties:


@interface FBViewController : UIViewController

@property (strong, nonatomic) UIView *bat; // The character that the player controls

@property (strong, nonatomic) UILabel *counterLabel; // The label counting points

@property (strong, nonatomic) NSMutableArray *blocks; The blocks that the player tries to avoid

@property (assign, nonatomic) CGFloat batVelocity; // The vertical velocity of the bat

@property (strong, nonatomic) CADisplayLink *displayLink; // A display link to step movement

@property (strong, nonatomic) NSTimer *blockTimer; // A timer to add new blocks to the screen

@end

The only property here that is perhaps somewhat obscure is the CADisplayLink. The documentation describes it best: “A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.”

Then move over to your FBViewController.m. Start by making it look like this:


@implementation FBViewController

#define FBBirdStartingFrame CGRectMake(100, CGRectGetMidY([[self view] frame])-25, 50, 50)

-(void)viewDidLoad {
    [super viewDidLoad];

    UIView *bird = [[UIView alloc] initWithFrame:FBBirdStartingFrame];
    [bird setBackgroundColor:[UIColor blueColor]];
    [[self view] addSubview:bird];
    [self setBat:bird];

    UILabel *counter = [[UILabel alloc] initWithFrame:CGRectMake(0, 80, CGRectGetWidth([[self view] frame]), 80)];
    [counter setTextAlignment:NSTextAlignmentCenter];
    [counter setFont:[UIFont systemFontOfSize:80.0f]];
    [[self view] addSubview:counter];
    [self setCounterLabel:counter];

    [self setBlocks:[NSMutableArray array]];

    [self startTimers];
}

#define FBPipeDelay 1.8

-(void)startTimers {
    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [self setDisplayLink:link];

    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:FBPipeDelay target:self selector:@selector(addNewBar:) userInfo:nil repeats:YES];
    [self setBlockTimer:timer];
}

-(void)tick:(CADisplayLink *)link {

}

-(void)addNewBar:(NSTimer *)timer {

}

@end

This code isn’t too complicated. In -viewDidLoad:, we create a nice blue box to be the bat (we’ll come back later to make that an actual animating bat), create the counter label, initialize an empty blocks array, and then call -startTimers. -startTimers creates and starts the display link timer, which will call -tick: at the screen refresh rate (usually either 60 or 30 times a second), and the block timer, which will call -addNewBar: every 1.8 seconds. We then provide empty implementations for those two methods.

Also, see those macros, FBBirdStartingFrame and FBPipeDelay? I will be using macros periodically in the code. Don’t be afraid to change these to fit your own liking. You want the game to be harder with the pipes closer together? Decrease FBPipeDelay. Want to make it easier? Bump FBPipeDelay up to 2 seconds or more.

If you run this now, you should get a screen looking like this:

Screen Shot 2014-02-13 at 9.13.54 AM

Now that we’ve done all the setup, we can start implementing the actual game. First we’ll start filling out the tick: method.


#define FBDownardBatAcceleration 15

-(void)tick:(CADisplayLink *)link {
   self.batVelocity += [link duration]*FBDownardBatAcceleration;

   [[self bat] setFrame:CGRectOffset([[self bat] frame], 0, [self batVelocity])];
}

The first line

self.batVelocity += [link duration]*FBDownardBatAcceleration;

requires a little bit of explaining. [link duration] gives the interval between timer ticks (for example, if the screen is currently refreshing at a rate of 60 frames a second, [link duration] will be 1.0/60.0). We’ve defined FBDownardBatAcceleration as 15, which is how many pixels per second we want the bat’s downward velocity to increase per second.  Multiplying the link duration by the acceleration per second gives velocity increase per tick. Right after that line, the line

[[self bat] setFrame:CGRectOffset([[self bat] frame], 0, [self batVelocity])];

moves the bat’s frame down by its current velocity.

Running the program now results in the bat (blue box) falling directly off the screen. Now we’ll add the implementation of -addNewBar:


#define FBHoleHeight 130
#define FBTopAndBottomPadding 50

-(void)addNewBar:(NSTimer *)timer {
   NSInteger possibleHoleLocationRange = CGRectGetHeight([[self view] frame])-FBHoleHeight-2*FBTopAndBottomPadding;
   NSInteger holeTop = arc4random()%possibleHoleLocationRange + FBTopAndBottomPadding;

   UIView *newTopBar = [[UIView alloc] initWithFrame:CGRectMake(CGRectGetWidth([[self view] frame]), 0, 50, holeTop)];
   [newTopBar setBackgroundColor:[UIColor greenColor]];

   CGFloat holeBottom = holeTop+FBHoleHeight;

   UIView *newBottomBar = [[UIView alloc] initWithFrame:CGRectMake(CGRectGetWidth([[self view] frame]), holeBottom, 50, CGRectGetHeight([[self view] frame])-holeBottom)];
   [newBottomBar setBackgroundColor:[UIColor greenColor]];

   [[self view] insertSubview:newTopBar belowSubview:[self counterLabel]];
   [[self view] insertSubview:newBottomBar belowSubview:[self counterLabel]];

   [[self blocks] addObject:newTopBar];
   [[self blocks] addObject:newBottomBar];
}

The first line creates a range for the hole to be in. It takes the height of the view, subtracts some padding for the top and bottom (so that a hole isn’t at the very bottom of the screen or the very top), and subtracts the hole height (so that the bottom of the hole doesn’t extend beyond the bottom padding):

barrange

The next line chooses a place in possibleHoleLocationRange for the top of the hole. The rest of that method isn’t as involved. It creates two green bars (which we will later replace with pipe images) horizontally aligned and with FBHoleHeight spacing between them, with the top bar ending at the randomly chosen holeTop. It then adds them to the view and to the blocks array.

Now at the bottom of the -tick: method:


#define FBDownardBatAcceleration 15
#define FBSidewaysVelocity -120

-(void)tick:(CADisplayLink *)link {

   ...

   UIView *removeBlock = nil;

   for (UIView *block in [self blocks]) {
       [block setFrame:CGRectOffset([block frame], [link duration]*FBSidewaysVelocity, 0)];

       if (CGRectGetMaxX([block frame]) < 0)
           removeBlock = block;

   }

   if (removeBlock) {
       [removeBlock removeFromSuperview];
       [[self blocks] removeObject:removeBlock];
   }
}

Here we iterate through the blocks array and shift each block over FBSidewaysVelocity * [link duration]. If the block passes past the end of the screen, we remove it from the view and from the bars array.

Now if you run the app, the bat should still fall off the screen, but now every 1.8 seconds a green bar should slide in, looking something like this:

Screen Shot 2014-02-13 at 10.01.12 AM

Next add this method:

#define FBTapUpwardBatVelocity -5.5

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   [self setBatVelocity:FBTapUpwardBatVelocity];
}

Here, every time the player taps the screen, we stop the bats downward velocity and give it some upward velocity, resulting in a sort of “jump”. Now the game is actually playable, though the user still can’t lose, so lets fix that next. Make your -tick: method look like:

-(void)tick:(CADisplayLink *)link {
   self.batVelocity += [link duration]*FBDownardBatAcceleration;

   [[self bat] setFrame:CGRectOffset([[self bat] frame], 0, [self batVelocity])];

   UIView *removeBlock = nil;

   for (UIView *block in [self blocks]) {

       [block setFrame:CGRectOffset([block frame], [link duration]*FBSidewaysVelocity, 0)];

      // We added the following 3 lines
       if (CGRectIntersectsRect([[self bat] frame], [block frame])) {
           [self failed];
           break;
       }

       if (CGRectGetMaxX([block frame]) < 0)
           removeBlock = block;    
   }
   if (removeBlock) {       
       [removeBlock removeFromSuperview];       
       [[self blocks] removeObject:removeBlock];    
   }  

   // And also the following 3 lines    
   if (CGRectGetMaxY([[self bat] frame]) >= CGRectGetHeight([[self view] frame])) {
       [self failed];
   }
}

The added code in the loop detects collisions, and, if the bat collides with a block we call a yet to be defined method -failed. The added code at the end of the method checks if the bat has fallen of the bottom of the screen, and, if so, calls that same -failed method. Lets implement that next, along with a -startover method:

-(void)failed {
   [[self blockTimer] invalidate];
   [self setBlockTimer:nil];

   [[self displayLink] invalidate];
   [self setDisplayLink:nil];

   [self performSelector:@selector(startOver) withObject:nil afterDelay:1];
}

-(void)startOver {
   for (UIView *block in [self blocks]) {
       [block removeFromSuperview];
   }

   [[self blocks] removeAllObjects];

   [[self bat] setFrame:FBBirdStartingFrame];

   [self setBatVelocity:0];

   [self startTimers];
}

In -failed, we stop the two timers, then call a -startover method which removes all the blocks from the screen and the block array, resets the position and velocity of the bat, and then starts the timers for a new game. All that’s left to do is add a point counter and the game will be fully functioning!

Adding a point counter involves additions in several places. First add a new method -incrementCount.

-(void)incrementCount {
   NSInteger current = [[[[self counterLabel] attributedText] string] intValue];

   NSDictionary *attributes = @{NSForegroundColorAttributeName: [UIColor whiteColor],
                                NSStrokeColorAttributeName: [UIColor blackColor],
                                NSStrokeWidthAttributeName: @(-2)};

   NSAttributedString *attrib = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@&amp;quot;%i&amp;quot;, current+1] attributes:attributes];

   [[self counterLabel] setAttributedText:attrib];
}

All we’re doing here is grabbing the current count, adding 1, then setting the text of the counter to that new count. We stylize the text a tiny bit with a NSAttributedString, giving it white text and a black outline. Change that loop in the -tick: method to look like this:

for (UIView *block in [self blocks]) {
       [block setFrame:CGRectOffset([block frame], [link duration]*FBSidewaysVelocity, 0)];

       if (CGRectIntersectsRect([[self bat] frame], [block frame])) {
           [self failed];
           break;
       }

     // We added the following 3 lines
       if ([block tag] == 0 &amp;amp;&amp;amp; CGRectGetMinX([block frame]) &amp;lt; CGRectGetMinX([[self bat] frame])) {
           [block setTag:1];
           [self incrementCount];
       }

       if (CGRectGetMaxX([block frame]) &amp;lt; 0)
           removeBlock = block;
}

The if-statement we added checks if the left side of a block had passed the left side of the bat, and also checks if the tag of the block is 0. As you can see, if this if-statement passes, we set the tag of the block to be 1 and then call -incrementCount. By only allowing the if-statement to pass if the tag is 0 and then changing the tag to a value other than 0 once it does pass, we insure the condition of the if statement will only be true once for each block.

If you were to play the app now, you’d see that the counter went up twice for every hole the bat passes. This is because the top and bottom blocks are separate. To fix this, we make a small alteration to -addNewBar:

-(void)addNewBar:(NSTimer *)timer {
   NSInteger possibleHoleLocationRange = CGRectGetHeight([[self view] frame])-FBHoleHeight-2*FBTopAndBottomPadding;
   NSInteger holeTop = arc4random()%possibleHoleLocationRange + FBTopAndBottomPadding;

   UIView *newTopBar = [[UIView alloc] initWithFrame:CGRectMake(CGRectGetWidth([[self view] frame]), 0, 50, holeTop)];
   [newTopBar setBackgroundColor:[UIColor greenColor]];
   [newTopBar setTag:1]; // We added this

   CGFloat holeBottom = holeTop+FBHoleHeight;
   UIView *newBottomBar = [[UIView alloc] initWithFrame:CGRectMake(CGRectGetWidth([[self view] frame]), holeBottom, 50, CGRectGetHeight([[self view] frame])-holeBottom)];
   [newBottomBar setBackgroundColor:[UIColor greenColor]];

   [[self view] insertSubview:newTopBar belowSubview:[self counterLabel]];
   [[self view] insertSubview:newBottomBar belowSubview:[self counterLabel]];

   [[self blocks] addObject:newTopBar];
   [[self blocks] addObject:newBottomBar];

}

The addition will ensure that the if statement in -tick will only pass for the bottom bar, and so the counter will only go up once every cleared hole.

One last addition to the -startOver method to reset the counter every game:

-(void)startOver {
    [[self counterLabel] setAttributedText:nil]; // Added this

    for (UIView *block in [self blocks]) {
        [block removeFromSuperview];
    }
    [[self blocks] removeAllObjects];

    [[self bat] setFrame:FBBirdStartingFrame];

    [self setBatVelocity:0];

    [self startTimers];
}

Run the app, and it should completely work! At this point it should look something like this:

Screen Shot 2014-02-13 at 10.58.56 AM

Final touch ups

Now that the app is fully functioning, its time to add some images to make it look nicer. All the images I use can be found at https://github.com/jakeheis/FlappyBat/tree/master/FlappyBat, but by all means use your own images and put a little spin on the game if you want!

-(void)viewDidLoad {
   [super viewDidLoad];

   UIImageView *background = [[UIImageView alloc] initWithFrame:[[self view] bounds]];
   [background setImage:[UIImage imageNamed:@&amp;quot;large.jpg&amp;quot;]];
   [background setContentMode:UIViewContentModeScaleAspectFill];
   [[self view] addSubview:background];

   UIImageView *bird = [[UIImageView alloc] initWithFrame:FBBirdStartingFrame];
   [bird setContentMode:UIViewContentModeScaleAspectFit];
   [bird setAnimationImages:@[[UIImage imageNamed:@&amp;quot;bat0.png&amp;quot;],
                              [UIImage imageNamed:@&amp;quot;bat1.png&amp;quot;],
                              [UIImage imageNamed:@&amp;quot;bat2.png&amp;quot;],
                              [UIImage imageNamed:@&amp;quot;bat3.png&amp;quot;]]];
   [bird setAnimationDuration:0.7];
   [bird startAnimating];
   [[self view] addSubview:bird];
   [self setBat:bird];

   UILabel *counter = [[UILabel alloc] initWithFrame:CGRectMake(0, 80, CGRectGetWidth([[self view] frame]), 80)];
   [counter setTextAlignment:NSTextAlignmentCenter];
   [counter setFont:[UIFont systemFontOfSize:80.0f]];
   [[self view] addSubview:counter];
   [self setCounterLabel:counter];

   [self setBlocks:[NSMutableArray array]];

   [self startTimers];
}

Here we add a nice background and replace the blue-box-bat with an animating flying bat. We’ll also make the pipes look nicer:

-(void)addNewBar:(NSTimer *)timer {
   NSInteger possibleHoleLocationRange = CGRectGetHeight([[self view] frame])-FBHoleHeight-2*FBTopAndBottomPadding;
   NSInteger holeTop = arc4random()%possibleHoleLocationRange + FBTopAndBottomPadding;

   UIImageView *newTopBar = [[UIImageView alloc] initWithFrame:CGRectMake(CGRectGetWidth([[self view] frame]), 0, 50, holeTop)];
   [newTopBar setImage:[[UIImage imageNamed:@&amp;quot;pipe_upside_down.png&amp;quot;] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 20, 0)]];
   [newTopBar setTag:1];

   CGFloat holeBottom = holeTop+FBHoleHeight;
   UIImageView *newBottomBar = [[UIImageView alloc] initWithFrame:CGRectMake(CGRectGetWidth([[self view] frame]), holeBottom, 50, CGRectGetHeight([[self view] frame])-holeBottom)];
   [newBottomBar setImage:[[UIImage imageNamed:@&amp;quot;pipe.png&amp;quot;] resizableImageWithCapInsets:UIEdgeInsetsMake(20, 0, 0, 0)]];

   [[self view] insertSubview:newTopBar belowSubview:[self counterLabel]];
   [[self view] insertSubview:newBottomBar belowSubview:[self counterLabel]];

   [[self blocks] addObject:newTopBar];
   [[self blocks] addObject:newBottomBar];
}

Now you should have a great looking game:

Screen Shot 2014-02-13 at 11.00.51 AM

Conclusion

You just made an awesome Flappy Bird clone! You can see the full example code at https://github.com/jakeheis/FlappyBat. There’s also a slightly improved version at https://github.com/jakeheis/FlappyBat/tree/improved_game/FlappyBat. A few small changes you’ll notice in the improved version are:

– All the macros used in the game (FBDownardBatAcceleration, FBHoleHeight, etc) were moved to the top of the file for easy modification

– I added a nice little start screen included in the game, but I’ll leave that to you to figure out how to make!

I hope you enjoyed making your own sweet, customized version of Flappy Bird! Obviously it is still missing some features of Flappy Bird like Game Center integration or high scores, but this tutorial hopefully at least helped you build a more bare bones version that works none the less!

Samsung is a genius

Do you remember the public’s reaction when Samsung first released the Galaxy Note back in 2011? In case your forgot, here’s an excerpt from this article http://www.slashgear.com/samsung-galaxy-note-review-04193076/:

The money quote:

“Bigger isn’t necessarily better. The Galaxy Note will automatically be out of contention for many, simply because of its size. For the mainstream, 5.3-inches – even with 1280 x 800 resolution – is simply too much to pocket.” 

Continue reading

Comparing Heroku and Instacart Screw Ups

Relatively recently both Heroku and Instacart made some mistakes that caused large public outcries. Heroku had a problem with some confusion surrounding their routing on their Bamboo stack. Instacart had a problem with their Instacart Express service.

Both companies published posts explaining what happened and apoligizing. Despite their similar nature, Heroku’s post is actually quite different compared to Instacart’s.

Heroku’s response can be seen here:

https://blog.heroku.com/archives/2013/4/3/routing_and_web_performance_on_heroku_a_faq

And Instacart’s here:

https://www.instacart.com/blog/2013/04/02/we-are-sorry.

Numerous articles have been written by/about both companies, but I’m going to be comparing the above two, which to my knowledge are the most recent.

Disclaimer: I was not affected by either of these companies’ mistakes; this post it just comparing Heroku’s response and Instacart’s response

Continue reading