The SlippyFlippy Challenge: Make a Better Obstacle in Sprite Kit

icon2_120Build 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.

Screenshot 2014-04-30 12.39.32

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.

Screenshot 2014-04-29 05.40.48
The obstacle sprite node tree

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:

Screenshot 2014-04-29 05.41.01
Sprite positioning
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.

Screenshot 2014-04-30 12.41.59

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.

Screenshot 2014-04-30 12.50.03

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.

Screenshot 2014-04-30 12.53.14

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.

Screenshot 2014-04-30 12.58.27

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

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s