SKPhysics with Swift and SpriteKit - ios

I am trying to create a iOS game using Swift Programming Language.
I want to use swipe gestures on the screen to make the character (player) move up, down, left and right
So far so good. But I want some physics. I want the player to collide with the edges of the screen. (and later I want it to collide with other stuff, but I need to know the code first ;)
QUESTION: How do I identify a collision between 2 objects (player and edge of screen)
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var player:SKSpriteNode = SKSpriteNode()
let moveUp = SKAction.moveByX(0, y:2000, duration:10.0) //it is set to 2000 because I want to see if it collides with the edge of the screen
let moveDown = SKAction.moveByX(0, y: -200, duration: 2.0)
// Define the bitmasks identifying various physics objects
let worldCategory:UInt32 = 0x1 << 0
let playerCategory:UInt32 = 0x1 << 1
override func didMoveToView(view: SKView) {
}
override init(size:CGSize) {
super.init(size: size)
self.backgroundColor = SKColor.whiteColor()
player = SKSpriteNode(imageNamed: "Kundvagn1")
player.position = CGPointMake(self.frame.size.width/2, player.size.height/2 + 20)
self.addChild(player)
player.runAction(moveUp)
self.physicsWorld.gravity = CGVectorMake(0, 0)
self.physicsWorld.contactDelegate = self
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.size)
player.physicsBody?.dynamic = true //I dont think the question marks should be there, but Xcode doesn´t accept the code if there is no question marks there
player.physicsBody?.categoryBitMask = playerCategory
player.physicsBody?.contactTestBitMask = worldCategory
player.physicsBody?.collisionBitMask = 0
player.physicsBody?.usesPreciseCollisionDetection = true
}
func didBeginContact(contact: SKPhysicsContact!) {
println("Collision")
}

Assuming that you want the left and right edges to block, simply add two SKShapeNode rectangular objects to your scene, set their heights to the screen height, and place them just beyond the left and right edges of the screen. Set up their physics bodies and you should be good to go.
If you want all the edges of the screen to be boundaries, you need to setup a line-based physics body for your scene, as detailed here: Add boundaries to an SKScene

I added this in case you need a code example to jonogilmour's instructions . Turn on the ShowsPhysics = true in your viewController to see physics. Both sides should be blue. If you want a contact notification of course you would add to this code with your bit mask stuff. Hope this helps!
var leftEdge = SKNode()
leftEdge.physicsBody = SKPhysicsBody(edgeFromPoint: CGPointZero, toPoint: CGPointMake(0.0, self.size.height))
leftEdge.position = CGPointZero;
self.addChild(leftEdge)
var rightEdge = SKNode()
rightEdge.physicsBody = SKPhysicsBody(edgeFromPoint: CGPointZero, toPoint: CGPointMake(0.0, self.size.height))
rightEdge.position = CGPointMake(self.size.width, 0.0);
self.addChild(rightEdge)

Related

Swift + Physics: Wrong angle calculation on collision

I have a simple SpriteKit App with walls and a ball. Both setup with a SKPhysicsBody.
When I apply a force in one direction, I expect the ball to reflect at the wall with the same angle when it collide but in opposite direction.
But sometimes I see the angle is weird. I played a lot with all the physicsBody properties, but was not able to fix it. Sometimes the first reflections look good, but then the third or sixth and sometimes the first reflection is at wrong angle.
I read from different posts, people are kinda self-calculating the "correct direction". But I can't imagine SpriteKits physic engine is not capable to do so.
Check my attached image to understand what I mean. Can anybody help on this? I don't want to start playing around with Box2d for Swift, since this looks like it will be too hard for me to integrate into Swift.
This is the physicsWorld init on my GameScene.swift file where all my elements are added to:
self.physicsWorld.gravity = CGVectorMake(0, 0)
Adding all my code would be way too much, so I add the important pieces. Hope its enough for analyze. All elements like the ball, walls are SKSpriteNode's
This is the physicsBody code for the ball:
self.physicsBody = SKPhysicsBody(circleOfRadius: Constants.Config.playersize+self.node.lineWidth)
self.physicsBody?.restitution = 1
self.physicsBody?.friction = 0
self.physicsBody?.linearDamping = 1
self.physicsBody?.allowsRotation = false
self.physicsBody?.categoryBitMask = Constants.Config.PhysicsCategory.Player
self.physicsBody?.collisionBitMask = Constants.Config.PhysicsCategory.Wall | Constants.Config.PhysicsCategory.Enemy
self.physicsBody?.contactTestBitMask = Constants.Config.PhysicsCategory.Wall | Constants.Config.PhysicsCategory.Enemy
This is the physicsBody for the walls:
el = SKSpriteNode(color: UIColor.blueColor(), size: CGSize(width: Constants.Config.wallsize, height: Constants.Config.wallsize))
el.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: Constants.Config.wallsize, height: Constants.Config.wallsize))
el.physicsBody?.dynamic = false
el.physicsBody?.categoryBitMask = Constants.Config.PhysicsCategory.Wall
el.physicsBody?.collisionBitMask = Constants.Config.PhysicsCategory.Player
el.physicsBody?.contactTestBitMask = Constants.Config.PhysicsCategory.Player
At the end I just call the applyImpulse function on the balls physicsBody to make it moving in the physics simulation.
Also check my attached second image/gif. It shows the edge collision problem with a simple skphysics app without any special parameterization. just a rectangle with a ball in it and one vector applied as impulse.
Heres my full non-working code using the solution from appzYourLife.
class GameScene: SKScene {
private var ball:SKShapeNode!
override func didMoveToView(view: SKView) {
let screen = UIScreen.mainScreen().bounds
let p = UIBezierPath()
p.moveToPoint(CGPointMake(0,0))
p.addLineToPoint(CGPointMake(screen.width,0))
p.addLineToPoint(CGPointMake(screen.width, screen.height))
p.addLineToPoint(CGPointMake(0,screen.height))
p.closePath()
let shape = SKShapeNode(path: p.CGPath)
shape.physicsBody = SKPhysicsBody(edgeLoopFromPath: p.CGPath)
shape.physicsBody?.affectedByGravity = false
shape.physicsBody?.dynamic = false
shape.strokeColor = UIColor.blackColor()
self.addChild(shape)
ball = SKShapeNode(circleOfRadius: 17)
ball.name = "player"
ball.position = CGPoint(x: 20, y: 20)
ball.fillColor = UIColor.yellowColor()
ball.physicsBody = SKPhysicsBody(circleOfRadius: 17)
ball.physicsBody?.angularDamping = 0
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.restitution = 1
ball.physicsBody?.friction = 0
ball.physicsBody?.allowsRotation = false
self.addChild(ball)
self.ball.physicsBody?.applyImpulse(CGVector(dx: 2.4, dy: 9.7))
}
I just realized the mass and density of my ball sprite is nil. Why that? Even setting it keeps in nil.
Since i cannot imagine this is some unknown bug in SpriteKits physics engine,
i very hope someone can help me here as this is a full stopper for me.
It is a (known) bug in SpriteKits physic engine. But sometimes just playing with the values will make this bug disappear - for example, try changing the ball's speed (even by small value) every time it's hitting the wall.
Another solutions are listed here:
SpriteKit physics in Swift - Ball slides against wall instead of reflecting
(The same problem as yours, already from 2014, still not fixed..)
SpriteKit is working fine
I created a simple project and it is working fine.
The problem
As #Sulthan already suggested in a comment, I think the problem in your code is this line
el.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: Constants.Config.wallsize, height: Constants.Config.wallsize))
Here instead of creating the physical border for your scene, you are creating a rectangular physics body which overlap the physics body of the Ball producing unrealistic behaviours.
To create the physics border of my scene I simply used this
physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
Full code
By the way, this is the full code for my project
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let ball = Ball()
ball.position = CGPoint(x:frame.midX, y:frame.midY)
addChild(ball)
physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
ball.physicsBody!.applyImpulse(CGVector(dx:50, dy:70))
physicsWorld.gravity = CGVector(dx:0, dy:0)
}
}
class Ball: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "ball")
super.init(texture: texture, color: .clearColor(), size: texture.size())
let physicsBody = SKPhysicsBody(circleOfRadius: texture.size().width/2)
physicsBody.restitution = 1
physicsBody.friction = 0
physicsBody.linearDamping = 0
physicsBody.allowsRotation = false
self.physicsBody = physicsBody
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I also used scene.scaleMode = .ResizeFill inside GameViewController.swift.

SKPhysicsBodies not colliding after addition of SKPhysicsJointLimit

I currently have two SKSpriteNodes that I have added SKPhysicsBodies to. When they have no SKJoint attached, they collide as expected. As soon as I add the SKPhysicsJoint, they just pass right through each other. Any joint I add functions properly, but the SKPhysicsJointLimit only limits the extent to which the nodes can travel apart from each other, not how close they can get. How can I fix this?
Here is code I am using for the joint:
let joint = SKPhysicsJointLimit.joint(withBodyA: object1.physicsBody!, bodyB: object2.physicsBody!, anchorA: CGPoint(x: object1.position.x + iconController.position.x, y: object1.position.y + iconController.position.y), anchorB: CGPoint(x: object2.position.x + iconController.position.x, y: object2.position.y + iconController.position.y))
joint.maxLength = screen.height * 0.4
physicsWorld.add(joint)
PhysicsBody of both nodes:
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width / 2)
self.physicsBody?.allowsRotation = false
self.physicsBody?.friction = 0
self.physicsBody?.mass = 0.1
I have tested it with different values for the above modifications of the SKPhysicsBody and it performs the same.
An SKPhysicsJoint object connects two physics bodies so that they are simulated together by the physics world.
You can use also SKPhysicJointPin:
A pin joint allows the two bodies to independently rotate around the
anchor point as if pinned together.
If your objects work well before the SKPhysicsJoint addition with the physic engine so they fired the didBeginContact as you wish and as you have setted, I think your problem is simply a wrong anchor. Try to add:
let skView = self.view as! SKView
skView.showsPhysics = true
to your scene initialization code: you will see an outline of the physic bodies and maybe you'll see the issue immediatly.
To help you I'll try to make an example of elements configured to collide each other:
enum CollisionTypes: UInt32 {
case Boundaries = 1
case Element = 2
}
class GameScene: SKScene,SKPhysicsContactDelegate {
private var elements = [SKNode]()
override func didMoveToView(view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsWorld.contactDelegate = self
let boundariesFrame = CGRectMake(20, 20, 200, 400)
let boundaries = SKShapeNode.init(rect: boundariesFrame)
boundaries.position = CGPointMake(350,150)
let boundariesBody = SKPhysicsBody.init(edgeLoopFromRect: boundariesFrame)
boundariesBody.dynamic = false
boundariesBody.categoryBitMask = CollisionTypes.Boundaries.rawValue
boundariesBody.contactTestBitMask = CollisionTypes.Element.rawValue
boundaries.physicsBody = boundariesBody
addChild(boundaries)
for index in 0..<5 {
let element = SKShapeNode(circleOfRadius: 10)
let body = SKPhysicsBody(circleOfRadius: 10)
body.linearDamping = 0
// body.mass = 0
body.dynamic = true
body.categoryBitMask = CollisionTypes.Element.rawValue
body.contactTestBitMask = CollisionTypes.Boundaries.rawValue | CollisionTypes.Element.rawValue
body.collisionBitMask = CollisionTypes.Boundaries.rawValue | CollisionTypes.Element.rawValue
element.physicsBody = body
element.position = CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index))
elements.append(element)
addChild(element)
}
}
}
Hope it can help you to find your issue.

Setting up a Ground node using SpriteKit?

I've been designing a game where I want the game to stop as soon as the ball makes contact with the ground. My function below is intended to set up the ground.
class GameScene: SKScene, SKPhysicsContactDelegate {
var ground = SKNode()
let groundCategory: UInt32 = 0x1 << 0
let ballCategory: UInt32 = 0x1 << 1
//Generic Anchor coordinate points
let anchorX: CGFloat = 0.5
let anchorY: CGFloat = 0.5
/*Sets the background and ball to be in the correct dimensions*/
override init(size: CGSize) {
super.init(size: size)
//Create the Phyiscs of the game
setUpPhysics()
setUpGround()
setUpBall()
}
func setUpPhysics() -> Void {
self.physicsWorld.gravity = CGVectorMake( 0.0, -5.0 )
self.physicsWorld.contactDelegate = self
}
func setUpGround() -> Void {
self.ground.position = CGPointMake(0, self.frame.size.height)
self.ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(self.frame.size.width, self.frame.size.height)) //Experiment with this
self.ground.physicsBody?.dynamic = false
self.ground.physicsBody?.categoryBitMask = groundCategory //Assigns the bit mask category for ground
self.ball.physicsBody?.contactTestBitMask = ballCategory //Assigns the contacts that we care about for the ground
/*Added in*/
self.ground.physicsBody?.dynamic = true
self.ground.physicsBody?.affectedByGravity = false
self.ground.physicsBody?.allowsRotation = false
/**/
self.addChild(self.ground)
}
func setUpBall() -> Void {
ball.anchorPoint = CGPointMake(anchorX, anchorY)
self.ball.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2)
self.ball.name = "ball"
self.ball.userInteractionEnabled = false
ball.physicsBody?.usesPreciseCollisionDetection = true
self.ball.physicsBody?.categoryBitMask = ballCategory //Assigns the bit mask category for ball
self.ball.physicsBody?.collisionBitMask = wallCategory | ceilingCategory //Assigns the collisions that the ball can have
self.ball.physicsBody?.contactTestBitMask = groundCategory //Assigns the contacts that we care about for the ball
addChild(self.ball) //Add ball to the display list
}
The problem I am noticing is that when I run the iOS Simulator, the Ground node is not being detected. What am I doing wrong in this case?
Is your ground node not showing up at all? I believe your ground should be an SKSpriteNode. Add that SKSpriteNode as a child of the SKNode object you created.
The SKNode is an empty node and the SKSpriteNode has an actual visual representation. And instead of doing it in a separate function, it would be much simpler if you did it in the DidMoveToView itself.
var objects = SKNode()
addChild(objects)
let ground = SKNode()
ground.position = .......
let colorsprite1 = SKSpriteNode(imageNamed: "yourimageoftheground")
ground.addChild(colorsprite1)
ground.physicsBody?.dynamic = false
Yes you were right to set ground's physics definition as not dynamic. I don't know why people are telling you otherwise. You don't want it to move upon contact with your ball thingies or whatever it is you have moving around in your scene.
Add all other physics definitions you need. Consider using a ContactTestBitMask as you want SpriteKit to let you know when a collision with the ground occurs, instead of SpriteKit handling it by itself. Details about that mask is easily available in StackOverFlow with examples.
objects.addChild(ground)
Hope this helps
Your ballCategory should be assigned to self.ground.contactBitMask not the ball.
You need to set the dynamic property to true in order for contact detection to work.
At the same time, you can set the following properties.
ground.affectedByGravity = false
ground.allowsRotation = false

Centering the camera on the character

I am a beginner and I have started my first project. It should be a little game where you can run to the left and to the right. But I don't know how to set the camera settings.
I'd be very thankful if you could help me, tell me how I can improve my code and what is wrong.
Here is my Code:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var ground:SKSpriteNode = SKSpriteNode()
var hero: MPCharacter!
var tree:MPTree!
override func didMoveToView(view: SKView) {
backgroundColor = UIColor(red: 155.0/255.0, green: 201.0/255.0, blue: 244.0/255.0, alpha: 1.0)
self.anchorPoint = CGPointMake(0.5, 0.5)
// --> hero <--
hero = MPCharacter()
hero.position = CGPointMake(frame.size.width/2, view.frame.size.height/2)
hero.zPosition = 100
hero.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(hero.size.width, hero.size.height))
hero.physicsBody!.dynamic = true
addChild(hero)
// --> ground <--
ground = SKSpriteNode(imageNamed: "ground")
ground.size = CGSizeMake(frame.size.width, frame.size.height/3)
ground.position = CGPointMake(frame.size.width/2, frame.size.height/2 - ground.size.height)
ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(frame.size.width, ground.size.height - 25))
ground.physicsBody!.dynamic = false
addChild(ground)
// --> Tree <--
tree = MPTree()
tree.position = CGPointMake(frame.size.width/2, ground.size.height + tree.size.height/3 - 10)
tree.zPosition = 10
tree.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(tree.size.width, tree.size.height - 10))
tree.physicsBody!.dynamic = false
addChild(tree)
// --> Physics <--
self.physicsWorld.gravity = CGVectorMake(0, -5.0)
self.physicsWorld.contactDelegate = self
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch:AnyObject in touches {
let location = touch.locationInNode(self)
if location.x < CGRectGetMidX(self.frame) {
hero.physicsBody!.velocity = CGVectorMake(0, 0)
hero.physicsBody!.applyImpulse(CGVectorMake(-5, 10))
}
else {
hero.physicsBody!.velocity = CGVectorMake(0, 0)
hero.physicsBody!.applyImpulse(CGVectorMake(5, 10))
}
}
}
}
Im not quite sure what you are trying to achieve. One way to make your character 'run' to the left (or right) is by moving a background (and other game objects) to the left when the character is running right (and vice versa). This way your character will always be in the center of the screen, because he is not really moving, you create an 'illusion' that he is moving in your gameworld. Giving your character a run animation will make it look even better...
There is a tutorial on the website of Ray Wenderlich which also covers 'parallax scrolling':
http://www.raywenderlich.com/49625/sprite-kit-tutorial-space-shooter
Hope this helps..
If you're trying to center the camera on the player, Apple Docs provide you a nice example for how to do so over here (I would add code, but the best way to learn is to get your hands dirty). You might have to translate the methods to swift, but it is the general implementation for what you wish to do. The problem with the answer above is that if you would move the background, you would have to calculate the forces and the impulses directly and apply the inverse vectors to the background. This might require more processing than with simply moving the camera. Hope this helps!!

Locking scene to a sprite node swift xcode

Basically, I want to move the character (player) and have the “camera” or scene follow them in the world. The problem is Apple/xcode does not have a built in camera node and if I try to move the scene, I get an error saying something to the effect of “SKScene cannot be moved.” I don’t want to just move the background around and lock the player in place because I want to be able to apply forces to the player node. I have gone to Apple’s website to the Advanced Scene Processing page and followed the example but it doesn’t work. I just need a nudge in the right direction. Thanks!
Code:
(I have deleted pieces of code that are irrelevant)
class PlayScene: SKScene, SKPhysicsContactDelegate {
let world = SKNode()
let player = SKShapeNode(circleOfRadius: 25)
var cannonRotation = 0.0
var fired = false
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.addChild(world)
//Adds the background to the world
//Adds the elements to the scene
player.fillColor = UIColor.whiteColor()
player.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMinY(self.frame)+65+25)
player.zPosition = 2
player.physicsBody = SKPhysicsBody(circleOfRadius: 25)
player.physicsBody?.dynamic = true
player.physicsBody?.restitution = 0.9
player.physicsBody?.categoryBitMask = BodyType.player.rawValue
//Adds the physics properties to GameScene
let physicsGround = SKPhysicsBody(edgeFromPoint: CGPoint(x: CGRectGetMinX(self.frame), y: CGRectGetMinY(self.frame)+65), toPoint: CGPoint(x: CGRectGetMaxX(self.frame), y: CGRectGetMinY(self.frame)+65))
self.physicsBody = physicsGround
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
powerLevel()
totalPower = powerReached * playCannon.powerMultiplier
self.addChild(player)
player.physicsBody?.applyImpulse(CGVector(dx: CGFloat(Float(totalPower)*cosf(Float(cannonRotation))), dy: CGFloat(Float(totalPower)*sinf(Float(cannonRotation)))))
fired = true
}
Override didsimulatePhysics, And in that function call this
self.centerOnNode(player);
What that does, It calles a function we will later create and it sendes a parameter of an SKNode and in your case it is the player node. Next create a method called centerOnNede and add the following parameters
func centerOnNode(node: SKNode) {
}
In the centerOnNode function add this following line of code.
var cameraPosition: CGPoint = node.scene?.convertPoint(node.position, fromNode: node.parent!)
cameraPosition.x = 0
let posX = node.parent?.position.x
let posY = node.parent?.position.y
node.parent?.position = CGPointMake(posX! - cameraPosition.x, posY! - cameraPosition.y)
That will center the camera on to the node and update it's position. Code converted from here. How to make camera follow SKNode in Sprite Kit?
There is a detailed example of how to do this in the Apple Adventure reference game. It has both Swift and Objective-C examples.
In summary what they recommend is that you
Put all nodes on a background node (SKNode) container.
implement didSimulatePhysics and move the world in the opposite direction of the player. That will keep the player in the middle of the screen. Special logic will be needed if you want the camera to bump into the edge of your world.
A camera node would be very helpful, but has not been implemented into SpriteKit.
As of iOS 9 there is now an SKCameraNode that does all the heavy lifting for you.
Simply add these three lines of code in your didMoveToView function:
let cameraNode = SKCameraNode()
self.addChild(cameraNode)
self.camera = cameraNode
Then in your didFinishUpdate function just set the cameraNode's position to whatever SKSpriteNode you want it to follow.

Resources