To 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.
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.
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.
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
Leave a Reply