Make App Pie

Training for Developers and Artists

The Slippy Flippy Dare: Making a Basic Obstacle

icon2_120To make a game fun there must be a challenge. One of those challenges is some obstacle in your path or headed towards you.  In both Flappy Bird and SlippyFlippyPenguin that  obstacle is a large rectangle with a gap in it. Your job is to get through the gap. If you do, you  score a  point.

In this post, we’ll make a basic obstacle. In the next part of this series, we’ll turn that obstacle in to the pipes of Flappy Bird or the ice caves of SlippyFlippyPenguin

Make a Simple Obstacle

To start making  an obstacle, we will start with a new method,

-(void)makeObstacle{
     UIColor *obstacleColor = [UIColor blueColor];
     CGSize obstacleSize = CGSizeMake(64, 128);
     SKSpriteNode *obstacle = [SKSpriteNode spriteNodeWithColor:obstacleColor size:obstacleSize];
     obstacle.name = @"obstacle";
     [self addChild:obstacle];
}

Next we need to call this method. To start, place the method  towards the end of initWithSize:, just under the [self addChild:penguin] line in the code from the last installment.

[self addChild:penguin];
//make and add an obstacle to the scene
[self makeObstacle];

we now have a small rectangle in the bottom left corner.

iOS Simulator Screen shot Apr 3, 2014, 8.44.49 AM

By default our position is (0,0).  We are seeing a quarter of the rectangle, since our anchor point is the center of the sprite by default.   We need to move the obstacle to the right edge, and center it.

I realized in the last installment, I used but neglected to mention a set of very useful functions that we will use extensively. Repeatedly, I will mention the problem of screen fragmentation.  My iPhone4 has a different size than my iPhone 5. The phones have a different screen size than an iPad.  It becomes very difficult to position anything to an absolute point. Instead we work from a relative value. To help, we have the following functions, which you can read about in Apple’s CGGeometry Reference Guide

Getting Min, Mid, and Max Values
CGRectGetMinX
CGRectGetMinY
CGRectGetMidX
CGRectGetMidY
CGRectGetMaxX
CGRectGetMaxY
Getting Height and Width
CGRectGetHeight
CGRectGetWidth

We can use these functions with the frame of our scene and identify points relative to the top, bottom, left and right side of the visible scene.

To move our obstacle to the center of the scene we would add to our makeObstacle: method

obstacle.position = CGPointMake(CGRectGetMidX(self.frame ),CGRectGetMidY(self.frame));

Run and build. We have an obstacle in the center, obscuring the penguin.  Change the position property to CGRectMaxX(self.frame) instead:

obstacle.position = CGPointMake(<strong>CGRectGetMaxX(self.frame)</strong>, CGRectGetMidY(self.frame));

This creates a blue rectangle that is sitting halfway off the scene on the right edge of the scene. For illustration and debugging purposes, we will keep the obstacle visible for the moment.

iOS Simulator Screen shot Apr 3, 2014, 9.22.50 AM

Move an Obstacle

Next we need to move the obstacle.  Add the SKAction lines in bold below:

-(void)makeObstacle{
     UIColor *obstacleColor = [UIColor blueColor];
     CGSize obstacleSize = CGSizeMake(64, 128);
     SKSpriteNode *obstacle = [SKSpriteNode spriteNodeWithColor:obstacleColor size:obstacleSize];
     obstacle.position = CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMidY(self.frame));
     obstacle.name = @"obstacle";
     <strong>SKAction *obstacleAction = [SKAction moveToX:0.0 duration:4];</strong>
     <strong>[obstacle runAction:obstacleAction];</strong>
     [self addChild:obstacle];
}

Build and run. The obstacle moves from one edge to the other in four seconds, passing over the penguin in the process.

It’s important for memory and performance reasons to get rid of an obstacle after we finish using it.  Change this line

[obstacle runAction:obstacleAction];

to this:

[obstacle runAction:obstacleAction <strong>completion:^{[obstacle removeFromParent];}</strong>];

After we run the action, we remove the obstacle from the node tree.   Build and run.

Set Game Play for the Obstacle

Change the update: method to match this

-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
     SKNode *penguin =[self childNodeWithName:@"penguin"];
     CGPoint position = penguin.position;
     if (++position.y <= CGRectGetMidY(self.frame)){
          [[self childNodeWithName:@"penguin"] setPosition:CGPointMake(++position.x, ++position.y) ];
     }
<strong>
//Code to make an obstacle appear if we do not have one.
     if (![self childNodeWithName:@"obstacle"] ){
          [self makeObstacle];
     }</strong>

}

Build and run. Now our obstacle crosses the screen, disappears, and re-appears at the beginning again.  Note the display on the bottom telling us we only have two nodes.  We used the childNodeWithName: method every frame to find if there were any obstacles in the node tree. If there isn’t, we can make an obstacle. The obstacle will eventually destroy itself and then the next frame will make a new obstacle.

However we don’t want the obstacle to start before the game does. We start the game with a touch, and the penguin becomes subject to gravity as we see here:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
 /* Called when a touch begins */

     if ([touches count]>0) {
          SKNode *penguin =[self childNodeWithName:@"penguin"];
          if (penguin.physicsBody.affectedByGravity){ // game play on
               [penguin.physicsBody applyImpulse:CGVectorMake(0, 0)];
               [penguin.physicsBody applyImpulse:CGVectorMake(0, 7)];
          } else{ //game play off -- begin game
               penguin.physicsBody.affectedByGravity = YES;
               penguin.physicsBody.density = 0.1;
          }
     }
}

We can  make a flag to say we are playing the game or not playing the game. However the penguin’s isAffectedByGravity  property is the same value as a playing/not playing BOOL value, so I just used that.

Change the if statement in update: to read

if (![self childNodeWithName:@"obstacle"] && penguin.physicsBody.affectedByGravity ){
[self makeObstacle];
}

Remove the [self makeObstacle] from initWithsize: Now build and run. When we tap the screen, the obstacle begins its  movement pattern.

Make the Obstacle Slide In and Out

We don’t want to see the obstacle appear or disappear.  Instead, we want the obstacle to slide onto the screen. We are now sure our animation works right. We can change its initial and ending position to be greater  and less than the frame’s MaxX and MinX. It will exist, but not be visible when it appears and disappears.  First in makeObstacle: change

obstacle.position = CGPointMake(CGRectGetMaxX(self.frame)<strong>+ 2.0* obstacle.size.width</strong>, CGRectGetMidY(self.frame));

We start two times the width of the obstacle off the screen. Run and build.  Now the obstacle slides into the scene.

Now change our SKAction the same way, so it move to a point two times the width of the obstacle before disappearing.

SKAction *obstacleAction = [SKAction moveToX:0.0 <strong>- 2.0 * obstacle.size.width</strong> duration:4];

Build and run. Our obstacle is now sliding in and out of the scene.

Add Physics

This looks good but the obstacle needs to collide with the penguin. Right now they pass through each other.  We need to active physics on the obstacle.

obstacle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle.size];
obstacle.physicsBody.affectedByGravity = NO;
obstacle.physicsBody.allowsRotation = NO;

We made a physicsBody like the penguin’s, then changed a few flags. Gravity should not affect the obstacle. We also turn off rotation since we want to keep the obstacle vertical. Some collisions will send it spinning, a lot like Slippy now will as you play with him.

Build and Run. You can now bounce the penguin all over the screen, and against the obstacle. You can also get the penguin crushed, which I made my game over state.

iOS Simulator Screen shot Apr 3, 2014, 9.25.06 AM

allowsRotation is the first major difference between SlippyFlippyPenguin and Flappy Bird. When I had allowsRotation off by mistake and watched Slippy spinning out, I saw my penguin as a different game than Flappy Bird. Both games however score by getting through a gap in the obstacle, and our next installment I will change our current obstacle and make that gap.

</pre>
<h1>Completed code</h1>
<pre>
//
//  SFMyScene.m
//  slippyflippyPenguinDemo
//
//  Created by Steven Lipton on 3/20/14.
//  Copyright (c) 2014 Steven Lipton. All rights reserved.
//

#import "SFMyScene.h"

@implementation SFMyScene{

}

-(void)makeObstacle{
    UIColor *obstacleColor = [UIColor blueColor];
    CGSize obstacleSize = CGSizeMake(64, 128);
    SKSpriteNode *obstacle = [SKSpriteNode spriteNodeWithColor:obstacleColor size:obstacleSize];

    obstacle.position = CGPointMake(CGRectGetMaxX(self.frame)+ 2.0 * obstacle.size.width, CGRectGetMidY(self.frame));
    obstacle.name = @"obstacle";

    obstacle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle.size];
    obstacle.physicsBody.affectedByGravity = NO;
    obstacle.physicsBody.allowsRotation = NO;

    SKAction *obstacleAction = [SKAction moveToX:0.0 - 2.0 * obstacle.size.width duration:4];
    [obstacle runAction:obstacleAction completion:^{[obstacle removeFromParent];}];
    [self addChild:obstacle];
}

-(id)initWithSize:(CGSize)size {
    if (self = [super initWithSize:size]) {
        /* Setup your scene here */

        self.backgroundColor = [SKColor colorWithRed:0.85 green:0.85 blue:1.0 alpha:1.0];
        //set up world physics
        self.physicsWorld.gravity = CGVectorMake(0.0, -3.5); //set to -5 to -9
        //a physics body to prevent the penguin from going off the screen.
        self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];

        /* Make a penguin */

        //Initialize a sprite with a texture from a file.
        SKSpriteNode *penguin = [SKSpriteNode spriteNodeWithImageNamed:@"gray penguin"];
        penguin.position = CGPointMake(0, 0);
        penguin.name = @"penguin";
        penguin.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:penguin.frame.size];
        penguin.physicsBody.affectedByGravity = NO;
        //set mass
        penguin.physicsBody.density = 0.1;

        SKAction *movePenguin = [SKAction moveTo:CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)) duration:2.0];
        [penguin runAction:movePenguin];

        [self addChild:penguin];
        //make and add an obstacle to the scene
       //[self makeObstacle];
    }
    return self;
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    /* Called when a touch begins */

    if ([touches count]>0) {
        SKNode *penguin =[self childNodeWithName:@"penguin"];
        if (penguin.physicsBody.affectedByGravity){ // game play on
            [penguin.physicsBody applyImpulse:CGVectorMake(0, 0)];
            [penguin.physicsBody applyImpulse:CGVectorMake(0, 7)];
        } else{ //game play off -- begin game
            penguin.physicsBody.affectedByGravity = YES;
            penguin.physicsBody.density = 0.1;
        }
    }
}

-(void)update:(CFTimeInterval)currentTime {
    /* Called before each frame is rendered */
    SKNode *penguin =[self childNodeWithName:@"penguin"];
    CGPoint position = penguin.position;
    if (++position.y <= CGRectGetMidY(self.frame)){
        [[self childNodeWithName:@"penguin"] setPosition:CGPointMake(++position.x, ++position.y) ];
    }
    if (![self childNodeWithName:@"obstacle"] && penguin.physicsBody.affectedByGravity ){
        [self makeObstacle];
    }

}

@end

One response to “The Slippy Flippy Dare: Making a Basic Obstacle”

  1. […] to. I can find the node by its name in any code I write using the childNodeWithName: method. See Making a Basic Obstacle for some […]

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 )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: