Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


Home > >

29. July 2014

 

Ok I think I ended up with an overly complicated title for this series once again…  This is the next part in the ongoing Swift with SpriteKit tutorial series.  In the previous part we hooked up a very simple physics simulation.  First starting with basic gravity then with a collision off the edge of the screen.  In this part we are going to look at some slightly more complicated collision scenarios.  This tutorial section is going to be a bit more code heavy than before.

 

In this first code example, we are going to have two physics guided balls on the screen.   This example will show how to “do something” when two objects collide.  Let’s hope right in:

 

import SpriteKit

 

class GameScene: SKScene, SKPhysicsContactDelegate {

    override func didMoveToView(view: SKView) {

        

        // Define the bitmasks identifying various physics objects

        let sphereObject : UInt32 = 0x01;

        let worldObject : UInt32 = 0x02;

        

        //Create a ball shape

        var path = CGPathCreateMutable();

        CGPathAddArc(path, nil, 0, 0, 45, 0, M_PI*2, true);

        CGPathCloseSubpath(path);

 

        // Create one ball

        var shapeNode = SKShapeNode();

        shapeNode.path = path;

        shapeNode.lineWidth = 2.0;

        shapeNode.position = CGPoint(x:self.view.frame.width/2,y:self.view.frame.height);

        

        // Set the ball's physical properties

        shapeNode.physicsBody = SKPhysicsBody(circleOfRadius: shapeNode.frame.width/2);

        shapeNode.physicsBody.dynamic = true;

        shapeNode.physicsBody.mass = 5;

        shapeNode.physicsBody.friction = 0.2;

        shapeNode.physicsBody.restitution = 1;

        shapeNode.physicsBody.collisionBitMask = sphereObject | worldObject;

        shapeNode.physicsBody.categoryBitMask = sphereObject;

        shapeNode.physicsBody.contactTestBitMask = sphereObject;

        

        // Now create another ball

        var shapeNode2 = SKShapeNode();

        shapeNode2.path = path;

        shapeNode2.position = CGPoint(x:self.view.frame.width/2,y:self.view.frame.height/2);

        

        shapeNode2.physicsBody = SKPhysicsBody(circleOfRadius: shapeNode.frame.width/2);

        shapeNode2.physicsBody.dynamic = true;

        shapeNode2.physicsBody.mass = 5;

        shapeNode2.physicsBody.friction = 0.2;

        shapeNode2.physicsBody.restitution = 1;

        shapeNode2.physicsBody.collisionBitMask = sphereObject | worldObject;

        shapeNode2.physicsBody.categoryBitMask = sphereObject;

        shapeNode2.physicsBody.contactTestBitMask = sphereObject;

        

        // Now make the edges of the screen a physics object as well

        scene.physicsBody = SKPhysicsBody(edgeLoopFromRect: view.frame);

        scene.physicsBody.contactTestBitMask = worldObject;

        scene.physicsBody.categoryBitMask = worldObject;

        

        // Make gravity "fall" at 1 unit per second along the y-axis

        self.physicsWorld.gravity.dy = -1;

        

        self.addChild(shapeNode);

        self.addChild(shapeNode2);

        

        // We implement SKPhysicsContactDelegate to get called back when a contact occurs

        // Register ourself as the delegate

        self.physicsWorld.contactDelegate = self;

 

    }

    

    // This function is called on contact between physics objects

    func didBeginContact(contact:SKPhysicsContact){

        let node1:SKNode = contact.bodyA.node;

        let node2:SKNode = contact.bodyB.node;

        

        // Node 1 is the object the hit another object

        // Randomly apply a force of 0 - 1000 on both x and y axis

        node1.physicsBody.applyImpulse(CGVector(

            CGFloat(arc4random() % 1000),

            CGFloat(arc4random() % 1000)));

        

        // Node 2 is the other object, the one being hit

        node2.physicsBody.applyImpulse(CGVector(

            CGFloat(arc4random() % 1000),

            CGFloat(arc4random() % 1000)));

    }

    

}

 

And when you run it:

 

Physics3

 

A lot of the code is very similar to our previous example, so I will only focus on what’s new.  The first thing you may notice is our class now implements SKPhysicsContactDelegate.  This provides a function, didBeginContact() that will be called when a contact occurs.  In this example, didBeginContact will only be called when a contact occurs between spheres, and not the edge of the screen.  I will explain why shortly.

 

The only other major change in this code is for each PhysicsObject in the scene, we now define a couple values, collisionBitMask, categoryBitmask and contactTestBitMask.  Earlier in the code you may have noticed:

        let sphereObject : UInt32 = 0x01;

        let worldObject : UInt32 = 0x02;

This is where we define our two bitmasks.  Bitmasking may be a somewhat new concept to you.  Basically its a way of packing multiple values into a single variable.  Lets use a simple 8 byte char as an example.  In memory, in terms of bits, a char is composed of 8 bits that can be either on or off.  Like this for example:

10011001

 

So using a single byte of memory, we are able to store 8 individual on/off values.  1 representing on, while 0 represents off.  Of course in variables we don’t generally deal with values in binary form, but instead we often use hexadecimal.  The value above, 10011001 as binary translates to 153 decimal or 0x99 in hex.  Now lets look at our defined values.  We are essentially saying

0001 is a sphereObject

0010 is a worldObject.

 

Now using bitwise math you can do some neat stuff. For example, using a bitwise AND, you can make an object out of both:

let worldSphere = sphereObject & worldObject; // result 0011

This allows you to pack multiple on/off values into a single variable.  Now a full discussion on bitwise math is way beyond the scope of this tutorial, you can read more about it here.  But the basics above should get you through this code example.

 

Basically in SpriteKit physics you define the “type” of an object using categoryBitmask.  So in this example we set the categoryBitmask of each of our spheres to sphereObject, and the world frame to worldObject.  Next you tell each object what it interacts with, like we did here:

        shapeNode2.physicsBody.collisionBitMask = sphereObject | worldObject;

        shapeNode2.physicsBody.categoryBitMask = sphereObject;

        shapeNode2.physicsBody.contactTestBitMask = sphereObject;

What we are saying here is this node will collide with sphereObjects OR worldObjects, but it is a sphereObject and will only contact with other sphereObjects.

Therefore, our contact delegate will only be called when two spheres contact, while nothing will happen when a sphere contacts the side of the screen.  As you notice from the balls bouncing off the side, the collision still occur.  By default, each collision and contact bit mask is set to 0xFFFFFF, which basically sets all possible bits to on, meaning everything contacts and collides with everything else.

 

The delegate function called each time a collision occurs between sphere objects is pretty straight forward:

    func didBeginContact(contact:SKPhysicsContact){

        let node1:SKNode = contact.bodyA.node;

        let node2:SKNode = contact.bodyB.node;

        

        // Node 1 is the object the hit another object

        // Randomly apply a force of 0 - 1000 on both x and y axis

        node1.physicsBody.applyImpulse(CGVector(

            CGFloat(arc4random() % 1000),

            CGFloat(arc4random() % 1000)));

        

        // Node 2 is the other object, the one being hit

        node2.physicsBody.applyImpulse(CGVector(

            CGFloat(arc4random() % 1000),

            CGFloat(arc4random() % 1000)));

    }

Basically for each node involved in the collision we apply a random impulse ( think push ) in a random direction between 0 and 1000 in both the x and y axis.

Speaking of applying force, let’s take a quick look at another example:

import SpriteKit

 

class GameScene: SKScene, SKPhysicsContactDelegate {

    

    var shapeNode:SKShapeNode;

    

    init(size:CGSize) {

 

        shapeNode = SKShapeNode();

        //Create a ball shape

        var path = CGPathCreateMutable();

        CGPathAddArc(path, nil, 0, 0, 45, 0, M_PI*2, true);

        CGPathCloseSubpath(path);

        

 

        shapeNode.path = path;

        shapeNode.lineWidth = 2.0;

 

        

        // Set the ball's physical properties

        shapeNode.physicsBody = SKPhysicsBody(circleOfRadius: shapeNode.frame.width/2);

        shapeNode.physicsBody.dynamic = true;

        shapeNode.physicsBody.mass = 5;

        shapeNode.physicsBody.friction = 0.2;

        shapeNode.physicsBody.restitution = 1;

        // this time we dont want gravity mucking things up

        shapeNode.physicsBody.affectedByGravity = false;

        

        super.init(size:size);

    }

    

    override func didMoveToView(view: SKView) {

 

        

        // Position the ball top center of the view

        shapeNode.position = CGPoint(x:view.frame.width/2,y:view.frame.height);

 

        // Now make the edges of the screen a physics object as well

        scene.physicsBody = SKPhysicsBody(edgeLoopFromRect: view.frame);

        

        self.addChild(shapeNode);

        

    }

    

    override func keyUp(theEvent: NSEvent!) {

 

        switch theEvent.keyCode{

        

        case126: // up arrow

            shapeNode.physicsBody.applyImpulse(CGVector(0,1000));

        case125: // down arrow

            shapeNode.physicsBody.applyImpulse(CGVector(0,-1000));

        case123: // left arrow

            shapeNode.physicsBody.applyForce(CGVector(-1000,0));

        case124: // right arrow

            shapeNode.physicsBody.applyForce(CGVector(1000,0));

        case49: // spacebar

            shapeNode.physicsBody.velocity = CGVector(0,0);

            shapeNode.position = CGPoint(x: self.view.frame.width/2,y:self.view.frame.height/2);

        default:

            return;

        }

    }

    

}

This sample is pretty straight forward, thus no animated gif. If the user presses up or down, an implies is applied along that direction. If the user presses left or right, a force is applied instead.  While if the user hits the spacebar, the sphere’s velocity is set to nothing and it is manually moved to the centre of the screen. One thing you might notice is implies moves a great deal more than force.  This is because force is expected to be applied per frame.  Think of it like driving a car.

An impulse is like i loaded your car into a gigantic slingshot and propelled you along at a blistering speed.

Force on the other hand is much more akin to you driving the car yourself.  If you take your foot of the gas, you rapidly decelerate.  If on the other hand you keep your foot down ( apply the same amount of force each frame ), you will move along consistently at the same speed.

As you see from the above example, you can also manipulate velocity and position directly.  Generally though this mucks with the physics simulation something severe, so generally isn’t recommended if alternatives exist.

blog comments powered by Disqus

Month List

Popular Comments