Build a Better Obstacle
In the SlippyFlippy challenge we have a small obstacle. In Flappy Bird there are two obstacles which the bird has to pass between. Let’s change our current obstacle into a pass-through obstacle like Flappy Bird.
Make a Random Obstacle
Our current obstacle is a bit boring. It always shows up in the same place. Let’s randomize it a bit.
Here is our current method for making a obstacle:
-(void)makeObstacle{ //make the obstacle sprite UIColor *obstacleColor = [UIColor blueColor]; CGSize obstacleSize = CGSizeMake(64, 128); SKSpriteNode *obstacle = [SKSpriteNode spriteNodeWithColor:obstacleColor size:obstacleSize]; obstacle.name = @"obstacle"; //set its position obstacle.position = CGPointMake(CGRectGetMaxX(self.frame)+ 2.0 * obstacle.size.width, CGRectGetMidY(self.frame)); //set the physics for the obstacle obstacle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle.size]; obstacle.physicsBody.affectedByGravity = NO; obstacle.physicsBody.allowsRotation = NO; //move the obstacle SKAction *obstacleAction = [SKAction moveToX:0.0 - 2.0 * obstacle.size.width duration:4]; [obstacle runAction:obstacleAction completion:^{[obstacle removeFromParent];}]; [self addChild:obstacle]; }
Change the position code to the following:
//set its position NSInteger maxNumber = self.frame.size.height; CGFloat y = arc4random_uniform(maxNumber); obstacle.position = CGPointMake(CGRectGetMaxX(self.frame)+ (2.0 * obstacle.size.width), y);
We create a random number between the origin at 0 and the height of the frame. Now the center of the obstacle will randomly show on the screen.
Build and run our code. You will see the obstacle randomly appears.
How to Make a Gap: The Theory
We don’t have two obstacles on top of each other. We have one big obstacle with a gap in it. The obstacle is so big that it will fill any size device. The gap is just always somewhere in a mid range on the screen. We can make the obstacle as one sprite, but with other sprites in it.

Sprites do not have to be single objects. They may have their own hierarchy. In this case we define three sprites inside the bigger obstacle. We don’t need a sprite for the gap. We could use just two obstacle objects. I decided to use one for the gap, for an expansion later. I define the upper and lower obstacles by the anchor point of the parent node, which is 0,0. With center anchor points, we can find the position of the upper and lower obstacle from:

0.5*gap.height + 0.5*obstacle.height
The distributive property makes this a bit simpler:
0.5*(gapHeight+obstacleHeight)
We add that to the origin.y of the obstacle parent to get a position for an upper obstacle. We subtract that from the origin.y of the parent obstacle to get a position for the lower obstacle. We turn off physics for the parent, but turn it on for the upper and lower obstacles to handle collisions. That gives us an obstacle that we can move around with a gap in it.
We make the obstacles as big as our largest possible frame. The upper and lower obstacles are always partially off-screen for a portion, but appears solid no matter what device we are playing on. To be big enough, the upper and lower obstacle sizes are the height of the frame.
Make the Parent Obstacle
Start by making our current obstacle bigger. Make it as big as the frame. Change the code to initialize the obstacle sprite to the following:
//make the obstacle sprite UIColor *obstacleColor = [UIColor colorWithRed:0.0 green:0.0 blue:1.0 alpha:0.5]; //for debugging visulization CGSize obstacleSize; obstacleSize.width = 64; obstacleSize.height = self.frame.size.height; SKSpriteNode *obstacle = [SKSpriteNode spriteNodeWithColor:obstacleColor size:obstacleSize]; obstacle.name = @"obstacle";
We changed our color to a blue with an alpha value. In the real game this is transparent, but for debugging purposes it will be helpful to see it. We broke up obstacle size from a CGSizeMake
function to two setters. For the height, we set
obstacleSize.height = self.frame.size.height;
Build and run.
We have a solid stripe trying to push SlippyFlippy off the screen, then squeezing past him like Jello. That is because we have the physics turned on. Let’s turn it off for the obstacle. Comment out the physics for the main obstacle:
//set the physics for the obstacle //obstacle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle.size]; //obstacle.physicsBody.affectedByGravity = NO; //obstacle.physicsBody.allowsRotation = NO;
Add a gap sprite by adding the following code under the commented out physics:
//make the gap SKSpriteNode *gapNode = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(obstacleSize.width, 64)]; gapNode.position = CGPointMake( 0, 0); [obstacle addChild:gapNode];
This time we do not add the sprite to self
. We add it to obstacle
to build a hierarchy of sprites in obstacle
. Build and run this.
You will notice three things:
- The obstacle still shows bottom and top edges. We need to make it bigger.
- The gap is too small.
- The stuttering motion of the obstacle disappears and it now smoothly crosses the screen.
The third issue has to do with physics and collision detection. We have a physics setup where the penguin won’t fall off the edge of the screen by using an edge loop on self
. The obstacle is set to start off-frame on the right and slide off-frame to the right. With physics on for the obstacle, collision detection makes being off-screen impossible. So the obstacle stutters. We will discuss this much more next time.
Let’s fix the second one. We are using constant value of 64. Set a variable for this value and set it to a large number, for example 150. Call the identifier gapSize
. Add this identifier above the declaration for obstacleSize.
static int gapSize = 150;
Now change the code to use gapSize
:
SKSpriteNode *gapNode = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(obstacleSize.width, gapSize)];
Build and run.
SlippyFlippy has a chance of getting throughout the gap. Let’s make one more change to keep the gap proportional:
CGSize obstacleSize; obstacleSize.width = gapSize / 2.0; obstacleSize.height = self.frame.size.height;
We will always have a 1:2 width to height proportion if we change gapSize
.
Make the Ice blocks
Add the lower and upper obstacle sprites:
CGFloat iceYPosition = 0.5* (gapSize + obstacleSize.height); //make the first obstacle -- the lower obstacle SKSpriteNode *obstacle1 = [SKSpriteNode spriteNodeWithColor:obstacleColor size:obstacleSize]; obstacle1.position = CGPointMake( 0,0 - iceYPosition ); obstacle1.name = @"obstacle1"; [obstacle addChild:obstacle1]; //make the second obstacle SKSpriteNode *obstacle2 = [SKSpriteNode spriteNodeWithColor:obstacleColor size:obstacleSize]; obstacle2.position = CGPointMake(0, iceYPosition); obstacle2.name = @"obstacle2"; [obstacle addChild:obstacle2];
We calculate a position for the two blocks surrounding the gap, which I called iceYposition
. Since the origin on obstacle
is (0,0)
, just add or subtract from zero to get the y position of the sprite. Set the size of the upper and lower sprites to be as big as the frame. With obstacle more than twice the height of the frame, the obstacle sprite will always be bigger than the frame.
Comment out the declaration of obstacle
and change it to a generic node:
//SKSpriteNode *obstacle = [SKSpriteNode spriteNodeWithColor:obstacleColor size:obstacleSize]; SKSpriteNode *obstacle = [SKSpriteNode node];
The obstacle1
and obstacle2
block obstacle from view. Obstacle
‘s function is positioning and containment, not viewing. Obstacle
does not need to be visual. Thus simplify it to a initialization of node.
Change the color of the gap to clear:
SKSpriteNode *gapNode = [SKSpriteNode spriteNodeWithColor:[UIColor clearColor] size:CGSizeMake(obstacleSize.width, gapSize)];
We now have a gap in an obstacle that spans the whole frame. Run and build and we have a nice looking gap.
SlippyFlippy still passes right through it. We need to turn on physics, but not gravity. As we mentioned earlier, that’s a bit of a problem. Collision physics has to be selective about what it does and does not collide with. We want obstacel1
and obstacle2
to collide with the penguin, but not the walls of the frame. The penguin needs to collide with all of them. How to handle collisions will be the topic for the next installment.
Source Code
-(void)makeObstacle{ //make the obstacle sprite UIColor *obstacleColor = [UIColor colorWithRed:0.0 green:0.0 blue:1.0 alpha:0.5]; //for debugging visulization static int gapSize = 150; CGSize obstacleSize; obstacleSize.width = gapSize / 2.0; obstacleSize.height = self.frame.size.height; //SKSpriteNode *obstacle = [SKSpriteNode spriteNodeWithColor:obstacleColor size:obstacleSize]; SKSpriteNode *obstacle = [SKSpriteNode node]; obstacle.name = @"obstacle"; //set its position NSInteger maxNumber = self.frame.size.height; CGFloat y = arc4random_uniform(maxNumber); obstacle.position = CGPointMake(CGRectGetMaxX(self.frame)+ (2.0 * obstacle.size.width), y); //set the physics for the obstacle //obstacle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle.size]; //obstacle.physicsBody.affectedByGravity = NO; //obstacle.physicsBody.allowsRotation = NO; //make the gap SKSpriteNode *gapNode = [SKSpriteNode spriteNodeWithColor:[UIColor clearColor] size:CGSizeMake(obstacleSize.width, gapSize)]; gapNode.position = CGPointMake( 0, 0); [obstacle addChild:gapNode]; // set up the position of the solif parts of the obstacles relative to the root obstacle CGFloat iceYPosition = 0.5* (gapSize + obstacleSize.height); //make the first obstacle -- the lower obstacle SKSpriteNode *obstacle1 = [SKSpriteNode spriteNodeWithColor:obstacleColor size:obstacleSize]; obstacle1.position = CGPointMake( 0,0 - iceYPosition ); obstacle1.name = @"obstacle1"; [obstacle addChild:obstacle1]; //make the second obstacle SKSpriteNode *obstacle2 = [SKSpriteNode spriteNodeWithColor:obstacleColor size:obstacleSize]; obstacle2.position = CGPointMake(0, iceYPosition); obstacle2.name = @"obstacle2"; [obstacle addChild:obstacle2]; //move the obstacle SKAction *obstacleAction = [SKAction moveToX:0.0 - 2.0 * obstacle.size.width duration:4]; [obstacle runAction:obstacleAction completion:^{[obstacle removeFromParent];}]; [self addChild:obstacle]; }
Leave a Reply