Note: Source code (PhysicsField Example) available on Github
iOS8 introduces a set of new features for SpriteKit, and one of them is Physics field (SKFieldNode). They simulate physical forces and as a result, automatically affect all the physics bodies living in the same tree, very useful for games or any other kind of experiments.
Multiple kinds of fields are introduced, from vortex, magnetic, spring, etc. A SKFieldNode is invisible, but still needs to be in the same node tree (display list) to affect physics bodies.
The API is very simple, in the code below, I have the field follow the touch position to simulate a force field:
// Think as below as your Main class, basically the Stage // Note: The code below is for iOS, you can run it with the iOS simulator // this imports higher level APIs like Starling import SpriteKit // canvas size for the positioning let canvasWidth: UInt32 = 800 let canvasHeight: UInt32 = 800 // From the docs: // When a physics body is inside the region of a SKFieldNode object, that field node’s categoryBitMask property is // compared to this physics body’s fieldBitMask property by performing a logical AND operation. // If the result is a non-zero value, then the field node’s effect is applied to the physics body. let fieldMask : UInt32 = 0b1 let categoryMask: UInt32 = 0b1 // our main logic inside this class // we subclass the SKScene class by using the :TheType syntax below class GameScene: SKScene { // our field node member let fieldNode: SKFieldNode // the NSCoder abstract class declares the interface used by concrete subclasses (thanks 3r1d!) // see: http://stackoverflow.com/users/2664437/3r1d init(coder decoder: NSCoder!){ // we create a magnetic field fieldNode = SKFieldNode.magneticField() // we define its body fieldNode.physicsBody = SKPhysicsBody(circleOfRadius: 80) // we add it to the display list (tree) fieldNode.categoryBitMask = categoryMask // strength of the field fieldNode.strength = 2.8 // we initialize the superclass super.init(coder: decoder) } // this gets triggered automatically when presented to the view, put initialization logic here override func didMoveToView(view: SKView) { // we set the background color to black, self is the equivalent of this in Flash self.scene.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1) // we live in a world with gravity self.physicsWorld.gravity = CGVectorMake(0, -1) // we put contraints on the top, left, right, bottom so that our balls can bounce off them let physicsBody = SKPhysicsBody (edgeLoopFromRect: self.frame) // we set the body defining the physics to our scene self.physicsBody = physicsBody // we add it to the display list self.addChild(fieldNode) // let's create 300 bouncing cubes for i in 1..300 { // SkShapeNode is a primitive for drawing like with the AS3 Drawing API // it has built in support for primitives like a circle, or a rectangle, here we pass a rectangle let shape = SKShapeNode(rect: CGRectMake(-10, -10, 20, 20)) // we set the color and line style shape.strokeColor = UIColor(red: 255, green: 0, blue: 0, alpha: 1) // we set the stroke width shape.lineWidth = 4 // we set initial random positions shape.position = CGPoint (x: CGFloat(arc4random()%(canvasWidth)), y: CGFloat(arc4random()%(canvasHeight))) // we add each circle to the display list self.addChild(shape) // we define the physics body shape.physicsBody = SKPhysicsBody(circleOfRadius: shape.frame.size.width/2) // from the docs: /*The force generated by this field is directed on line that is determined by calculating the cross-product between direction fo the the physics body’s velocity property and a line traced between the field node and the physics body. The force has a magnitude proportional to the field’s strength property and the physics body’s charge and velocity properties.*/ // we define a mass for the gravity shape.physicsBody.mass = 0.9 // the charge and field strength are two properties fun tweaking shape.physicsBody.charge = 0.6 // we set the field mask shape.physicsBody.fieldBitMask = fieldMask // this will allow the balls to rotate when bouncing off each other shape.physicsBody.allowsRotation = true } } // we capture the touch move events by overriding touchesMoved method override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) { // we grab the UITouch object in the current scene (self) coordinate let touch = event.allTouches().anyObject().locationInNode(self) // we apply the position of the touch to the physics field node self.fieldNode.position = touch } // magic of the physics engine, we don't have to do anything here override func update(currentTime: CFTimeInterval) { } }
Here is what this produces: