didBeginContact not being invoked - ios

I have 2 SKSpriteNodes whose contact needs to be detected. I've tried various methods and looked up a lot of stuff but can't seem to get an answer. Below is my code. controlCircle is a class level variable because it needs to be used in other methods. The objects are generated fine.
class GameScene: SKScene, SKPhysicsContactDelegate {
var controlCircle = SKSpriteNode()
var mainCategory : UInt32 = 1 << 0
var dropCategory: UInt32 = 1 << 1
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = CGVectorMake(0,0)
//CREATING FIRST OBJECT AND ADDING PHYSICS BODY
var mainRadius = 20.0;
var controlCircle = SKSpriteNode(color: UIColor.clearColor(), size:CGSizeMake(mainRadius * 2, mainRadius * 2))
var circleBody = SKPhysicsBody(circleOfRadius: mainRadius)
circleBody.dynamic = false
circleBody.usesPreciseCollisionDetection = true
controlCircle.physicsBody = circleBody
var bodyPath = CGPathCreateWithEllipseInRect(CGRectMake((controlCircle.size.width/2), controlCircle.size.height/2, controlCircle.size.width, controlCircle.size.width),
nil)
var circleShape = SKShapeNode()
circleShape.fillColor = UIColor.brownColor()
circleShape.lineWidth = 0
circleShape.path = bodyPath
controlCircle.addChild(circleShape)
controlCircle.position = CGPointMake(self.frame.width/2, self.frame.height/2)
self.addChild(controlCircle)
controlCircle.physicsBody.categoryBitMask = mainCategory
controlCircle.physicsBody.contactTestBitMask = dropCategory
//CREATING SECOND OBJECT AND ADDING PHYSICS BODY
var radius = 10.0;
var drop = SKSpriteNode(color: UIColor.clearColor(), size:CGSizeMake(radius * 2, radius * 2))
var dropBody = SKPhysicsBody(circleOfRadius: radius)
dropBody.dynamic = false
dropBody.usesPreciseCollisionDetection = true
drop.physicsBody = dropBody
var dropPath = CGPathCreateWithEllipseInRect(CGRectMake((drop.size.width/2), drop.size.height/2, drop.size.width, drop.size.width),
nil)
var dropShape = SKShapeNode()
dropShape.fillColor = UIColor.blackColor()
dropShape.lineWidth = 0
drop.name = "dropMask"
dropShape.path = dropPath
drop.addChild(dropShape)
drop.position = CGPointMake(CGFloat(xValue), self.frame.height-5)
self.addChild(drop)
drop.physicsBody.categoryBitMask = dropCategory
drop.physicsBody.contactTestBitMask = mainCategory
}
func didBeginContact(contact: SKPhysicsContact) -> Void{
NSLog("Hello")
}
}

Good news everyone,
SKPhysicsBody documentation:
The dynamic property controls whether a volume-based body is affected by gravity, friction, collisions with other objects, and forces or impulses you directly apply to the object.
So here is an idea to accomplish stationary objects when not dragging, yet receive collision events. On touchesBegan on a SKSpriteNode, set its physicsBody.dynamic = true, on touchUp physicsBody.dynamic = false
Or, you could just remove gravity from your system and have all nodes with physicsBody.dynamic = true

Related

Restitution not working on ANYTHING in SpriteKit

I have tried creating bitmask categories, setting all restitution values to 1 and everything else under the sun including running the application an a device and it still is not working at all. After setting an SKSpriteNode with retitution 1 to hit another skspritenode also with restitution one at dx: 100, it still doesn't bounce! Here is my code for GameScene.swift:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
let Ball = SKSpriteNode()
Ball.self.childNode(withName: "Ball")
let playerPaddle = SKSpriteNode()
playerPaddle.self.childNode(withName: "playerPaddle")
let computerPaddle = SKSpriteNode()
computerPaddle.self.childNode(withName: "computerPaddle")
let ballCategory: UInt32 = 0x1 << 1
let paddleCategory: UInt32 = 0x1 << 2
let borderCategory: UInt32 = 0x1 << 3
Ball.physicsBody?.categoryBitMask = ballCategory
playerPaddle.physicsBody?.categoryBitMask = paddleCategory
computerPaddle.physicsBody?.categoryBitMask = paddleCategory
self.physicsBody?.categoryBitMask = borderCategory
let playerScore = SKLabelNode()
playerScore.fontName = "Pong Score"
playerScore.text = "0"
playerScore.fontSize = 100
playerScore.color = SKColor.white
playerScore.position = CGPoint(x: -200, y: 220)
playerScore.zPosition = 3
addChild(playerScore)
let comScore = SKLabelNode()
comScore.fontName = "Pong Score"
comScore.text = "0"
comScore.fontSize = 100
comScore.color = SKColor.white
comScore.position = CGPoint(x: 200, y: 220)
comScore.zPosition = 3
addChild(comScore)
var comScoreInt: Int = Int(comScore.text!)!
comScoreInt += 1
comScore.text = String(comScoreInt)
var playerScoreInt: Int = Int(playerScore.text!)!
playerScoreInt += 1
playerScore.text = String(playerScoreInt)
let bodyBorder = SKPhysicsBody(edgeLoopFrom: self.frame)
bodyBorder.friction = 0
bodyBorder.restitution = 1
self.physicsBody = bodyBorder
}
}
PS most all of the physics is in GameScene.sks not GameScene.swift, however this is where the bitmasks are.
EDIT:
Ball Config;
Position x:0, y:0, z:0,
Rotation: 0,
Size: w:25, h:25,
Anchor Point: x:0.5, y:0.5,
Color: white,
Blend Factor: 0,
Blend Mode: alpha,
Alpha: 1,
Ik Constraints: min:0, max:360,
Scale: x:1, y:1,
PHYSICS
Body Type: bounding rect,
only dynamic checked,
Friction: 0,
Restitution: 1,
Linuar Damp:0,
Angular damp: 0,
Category, Collision, and Field mask: 4294967295,
Contact Mask: 0
Inital Velocity: dx:100 dy:0
Paddles are the same physics wise
PLEASE HELP!
Insead of applying an Inital Velocity in GameScene.sks I instead applied a force in GameScene.swift like so: Ball.physicsBody?.applyImpulse(CGVector(dx:50, dy:0)) restitution began working

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.

Collisions in SpriteKit

I have an object kept between two bars. I have collisions set up but I'm not getting the print message "We have Contact" when making contact with either the top or bottom bar and I'm not sure why? I think it might have something to do with the boundary physicsBody. The image below is to give an example of the bars, where the object is kept between them.
var gameOver = false
override func didMoveToView(view: SKView) {
scene?.scaleMode = SKSceneScaleMode.AspectFill
self.physicsWorld.gravity = CGVectorMake(0.0, 2.0)
physicsWorld.contactDelegate = self
object = SKSpriteNode( imageNamed: "object")
object.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
object.physicsBody = SKPhysicsBody(circleOfRadius: object.size.width / 0.32)
object.physicsBody?.dynamic = true
object.physicsBody?.allowsRotation = true
self.addChild(object)
object.zPosition = 2
struct ColliderType {
static let object: UInt32 = 0x1 << 0
static let boundary: UInt32 = 0x1 << 1
}
let boundary = SKPhysicsBody(edgeLoopFromRect: CGRect(x: 253, y: 138, width:515, height:354))
self.physicsBody = boundary
self.physicsBody?.friction = 0
let push = CGVectorMake(10, 10)
object.physicsBody?.applyImpulse(push)
object.physicsBody?.categoryBitMask = ColliderType.object
object.physicsBody?.contactTestBitMask = ColliderType.boundary
object.physicsBody?.collisionBitMask = ColliderType.boundary
boundary.contactTestBitMask = ColliderType.object
boundary.categoryBitMask = ColliderType.boundary
boundary.collisionBitMask = ColliderType.object
func didBeginContact(contact:SKPhysicsContact) {
print("We have Contact")
gameOver = true
self.speed = 0
}
Try browsing the web and stackoverflow before asking a question because there is a lot of tutorials on the web and even stack overflow about this. This question might even be closed because collision detection is one of the basics in SpriteKit and has been asked a lot about on SO.
Anyway there is some errors/omissions in your code, so lets have a look.
1) You need to set the physics world contact delegate in your scenes "didMoveToView" method, so that it is actually listening for contacts. This is the number 1 thing people, including me, forget and than wonder for hours why nothing is working.
physicsWorld.contactDelegate = self
2) You are also not giving your sprites a physicsBody
object.physicsBody = SKPhysicsBody(...)
You have to always give them a physics body first and than set properties such as contactTestBitMasks etc.
3) Try using ? instead of ! when dealing with optionals to avoid nil crashes. Try to always do this even though you know that it is not nil. So say
...physicsBody?.contactTestBitMask...
instead of
...physicsBody!.contactTestBitMask...
You are sometimes doing it, and sometimes not.
In your current code you could get a crash because object has no physicsBody but you are force unwrapping it, essentially telling it that it does have a physics body, which is dangerous.
For example that line in your code should not work
object.physicsBody?.applyImpulse(push)
as there is no physics body on the object, but because it is not force unwrapping (?) nothing happens. Try changing that line using a ! and you should get a crash.
So always use ? when dealing with optionals and when the compiler allows you too.
Also that line should be after you have set up the object sprite not before.
4) Try to write your physics categories differently, your way will get confusing very quick when adding more categories because you cannot just increment the last number. So in your example the next collider type will have to be 4, 8, 16 etc.
Try this instead where you only need to increment the last number by 1.
struct ColliderType {
static let object: UInt32 = 0x1 << 0
static let boundary: UInt32 = 0x1 << 1
....
}
Than use it like so
...contactTestBitMask = ColliderType.object
...
You whole code should look like this
struct ColliderType {
static let object: UInt32 = 0x1 << 0
static let boundary: UInt32 = 0x1 << 1
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var gameOver = false
var object: SKSpriteNode!
override func didMoveToView(view: SKView) {
scene?.scaleMode = SKSceneScaleMode.AspectFill
self.physicsWorld.gravity = CGVectorMake(0.0, 2.0)
physicsWorld.contactDelegate = self
object = SKSpriteNode( imageNamed: "Spaceship")
object.zPosition = 2
object.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
object.physicsBody = SKPhysicsBody(circleOfRadius: object.size.width / 0.32)
object.physicsBody?.dynamic = true
object.physicsBody?.allowsRotation = true
object.physicsBody?.categoryBitMask = ColliderType.object
object.physicsBody?.contactTestBitMask = ColliderType.boundary
object.physicsBody?.collisionBitMask = ColliderType.boundary
self.addChild(object)
let boundary = SKPhysicsBody(edgeLoopFromRect: CGRect(x: 253, y: 138, width:515, height:354))
self.physicsBody = boundary
self.physicsBody?.friction = 0
boundary.contactTestBitMask = ColliderType.object
boundary.categoryBitMask = ColliderType.boundary
boundary.collisionBitMask = ColliderType.object
let push = CGVectorMake(10, 10)
object.physicsBody?.applyImpulse(push)
}
func didBeginContact(contact:SKPhysicsContact) {
print("We have Contact")
gameOver = true
self.speed = 0
}
}
Hope this helps
You must be sure that the two collision shapes are in the same collision space, if they are not in the same space, then collision detection algorithm will be never invoked. About the comment of Seed12, the two objects do not need to be a rigid body (physicsBody) to be checked for the presence of collisions between them.

How do I avoid hard coding values?

In my application, I have items randomly popping up from the bottom of the screen. The way I set my code forced me to use hard coded values for x-position from where the items are popping up. Instead of those x-positions being hard coded, I want to change them to for instance GameScene.size.width / 4, so that the application's game play stays the same for all devices. Here is how I set up my code:
class Items{
var node = SKNode()
var item1 = SKSpriteNode(imageNamed: "CY")
var item2 = SKSpriteNode(imageNamed: "SY")
var item3 = SKSpriteNode(imageNamed: "PY")
var velocity = CGPoint.zero
var positionOffset = CGFloat(0)
var minVelocity = CGFloat(200)
init(pOffset: CGFloat) {
positionOffset = pOffset
node.zPosition = 1
node.addChild(item1)
node.addChild(item2)
node.addChild(item3)
node.hidden = true
}
....
class GameWorld{
var size = CGSize()
var node = SKNode()
var can1 = Items(pOffset: 208) //this is what is deciding the position
var can2 = Items(pOffset: 620)
init() {
node.addChild(can1.node)
node.addChild(can2.node)
}
....
The offset line in GameWorld class is what is deciding the position. What can I change in order to be able to say "GameScene.size.width / 4" for can 1 and similarly the same for can2 and not have an issue? I did try different ways to get it to work, but nothing seems to go my way. When I'm able to say GameScene.size.width / 4, for some reason, the app launches, but none of the gameplay loads up. Any help will be greatly appreciated.
Here is my GameScene Class:
class GameScene: SKScene {
... // added sprites but erased the code for now for space purposes
var touchLocation = CGPoint(x: 0, y: 0)
var nrTouches = 0
var rightTap: Bool = false
var leftTap: Bool = false
var delta: NSTimeInterval = 1/60
static var world = GameWorld()
override init(size: CGSize) {
super.init(size: size)
GameScene.world.size = size
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func didMoveToView(view: SKView) {
backgroundColor = UIColor.whiteColor()
let pos1 = CGPoint(x: size.width / 1.333, y: size.height / 1.1)
addChild(GameScene.world.node)
delta = NSTimeInterval(view.frameInterval) / 60
sprite1.position = pos1
sprite2.position = pos1
sprite3.position = pos1
sprite1.zPosition = 2
sprite2.zPosition = 2
sprite3.zPosition = 2
sprite2.hidden = true
sprite3.hidden = true
addChild(sprite1)
addChild(sprite2)
addChild(sprite3)
}
.....
Edit: added GameScene Class
For this scenario, you cannot declare:
var can1 = Items(pOffset: 208)
var can2 = Items(pOffset: 620)
The way you are doing it now. This is because at compile time, the game has no idea what the screen size is. Instead do not initialize the variable, just declare it like this:
var can1 : Items!
var can2 : Items!
Then this next part is tricky, if you are doing auto layouts, you need to make sure that the view is already created and sized before you present your scene. This does't happen till somewhere around viewDidAppear in the view controller
In your scenes didMoveToView, initialize your variables:
override func didMoveToView(view : SKView)
{
... //super and other code
can1 = Items(pOffset: view.frame.size.width/4)
can2 = Items(pOffset: view.frame.size.width/2)
}

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

Resources