The Slippy Flippy Dare: How to Use Basic Physics in Sprite Kit

In the last installment of this series I showed two kinds of animation: SKAction and using the update: method from frame animation. The third major type of animation is using the physics engine.

A physics engine takes the mathematical laws of physics and adds them to the scene. It removes the developer from the math involved. It also adds collision detection, and the physics of collisions. In short, your sprite can work in the scene the same way real objects work in the real word.

To start let’s make sure everyone is in the same place.  If you have not done so, open a Sprite kit template in Xcode. If you have followed along, open your code.

Modify your code  so it should look like this:

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

self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];

/* 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";
SKAction *movePenguin = [SKAction moveTo:CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)) duration:2.0];
[penguin runAction:movePenguin];

[self addChild:penguin];

}
return self;
}

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

for (UITouch *touch in touches) {
/* commented out code */

}
}

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

}

gray penguinIf you are starting with new code, download the penguin graphic to your computer, name it gray penguin.png and drag it into the file list in Xcode. This includes cleaning out most of the original template code. There will be a warning in the touchesBegan: method. We will come back to that very shortly.

Physics turns on and off for individual sprites. Add the following code just below the penguin.name assignment:

penguin.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:penguin.frame.size];
penguin.physicsBody.affectedByGravity = NO;

Line 1 turns on the physics engine. All sprites have a property which holds a SKPhysicsBody object. We initialize that object with a class method bodyWithRectangleOfSize: and give the size of the penguin.

By default, gravity is on. To start we turn off gravity, so the penguin’s SKAction can execute in peace. Build and run the code and the penguin should move to the middle of the screen. Change the affectedByGravity to YES and run again. You see Slippy fighting gravity to get to the middle and then falling off the screen. Set affectedByGravity back to NO.
in touchesBegan:, change the for loop with the code:

for (UITouch *touch in touches) {
        
        [[[self childNodeWithName:@"penguin";] physicsBody] setAffectedByGravity:YES];
    }

Build and run. The penguin moves to the center. When the player touches the screen anywhere, we will turn the gravity on. Slippy then falls off the screen. However, we really don’t want Slippy falling off and out of sight. To solve this,  give the visible scene a physics body. In initWithSize: add the following under the self.background assignment:

//a physics body to prevent the penguin from going off the screen.
        self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];

Here we used a different type of physics body, an edge loop. This builds a collision body that is only a line instead of a solid body, so the penguin is now trapped inside the frame. Edge loops, unlike bodies, are not affected by gravity by default. Try building and running with this change. Slippy now falls to the bottom of the screen, and bounces into the left corner.

The default gravity is earth normal. Add this code just above the comment for the code you just entered.

self.physicsWorld.gravity = CGVectorMake(0.0, -9.8); //-9.8 Earth normal gravity

On the scene we set up a physicsWorld which gives some environmental properties to physics. If you run this, you will see no change. The -9.8 is earth normal gravity, a value known in physics as g.  Over at The Physics Classroom,  they discuss the value of g.  Included there is a table for g for different planets. If Slippy was on Mars, g becomes 3.75.  We set gravity to pull down by putting a negative sign in front of it. Change the -9.8 or earth normal to -3.75. Now Slippy falls as if he is on Mars. In the real game, the penguin is on a tilting ice sheet and we are looking at the sheet from above. Gravity is much less than 9.8, and I ended up deciding on -7.0 for my gravity. Note if I put 7.0 for gravity, slippy would fall up. In the real game, I multiply the gravity by -1 to change the sign periodically, flipping gravity for a bit more of a challenge.

We don’t want Slippy to fall so we need some way of making him move up. What we need to do is give our penguin a push. The  applyImpulse: method does this by pushing the penguin with a specified force in the x and y directions. Change the code in touchesBegan: to the following:

-(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){
            //[penguin.physicsBody applyImpulse:CGVectorMake(0, 0)];
            [penguin.physicsBody applyImpulse:CGVectorMake(0, 7)];
        } else{
            penguin.physicsBody.affectedByGravity = YES;
        }
    }
}

Figuring the position of our touch on the screen is necessary for many games , and the template has code to handle that case . For our game touching anywhere is good. We only need to know if there are any touches. We changed from the fast enumenration for loop of the template whihc list touches to an if...else statement that looks for any touch.

To reference the penguin easier, we created a SKNode and assigned a pointer to the object. We then check if gravity is on. If it is, we are in game mode. If not, we are in game over/start mode. In game mode we use a method applyImpulse and applied an impulse of 7 in the upward direction. Run this and you find no real change. Change 7 to 30 and see what happens. If you tap very quickly you can move the penguin up a little bit.

We are trying to play volleyball with a dumbell.  Slippy is too heavy or too dense. We need to change his mass or density. I’ll change his density to 0.1. In touchesBegan: change the else block to:

} else{
            penguin.physicsBody.affectedByGravity = YES;
            penguin.physicsBody.density = 0.1;
        }

Now run and tap the screen. Pretty much this is  Flappy Bird’s animation behavior. One more line makes the graphics just a bit better. Remove the comment from the second applyImpulse. This stops motion before applying then new impulse.

That is some the basics of physics engines.  We will explore more of physics engines and collisions, but first we need an obstacle to crash into. That will be our next installment.

One thought on “The Slippy Flippy Dare: How to Use Basic Physics in Sprite Kit”

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