Spritekit nodes are not colliding even though contact mask are set up - swift

In the code below I have a couple nodes but right now I am just trying to detect collisions between the player and the enemy bullet( I refer to it as "BBullet") but under the function that detects collisions, nothing pops up.
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var player = SKSpriteNode()
var firebutton = SKShapeNode()
var bullet = SKSpriteNode()
enum CategoryMask : UInt32 {
case player = 1
case GBullet = 2
case BBullet = 3
case enemyships = 4
}
override func didMove(to view: SKView) {
player = SKSpriteNode(color: .red, size: CGSize(width: 150, height: 150))
player.physicsBody?.affectedByGravity = false
player.position = CGPoint(x: 100, y: 375)
player.physicsBody?.isDynamic = true
player.physicsBody?.restitution = 1
player.physicsBody?.categoryBitMask = CategoryMask.player.rawValue
player.physicsBody?.collisionBitMask = CategoryMask.BBullet.rawValue
player.physicsBody?.contactTestBitMask = CategoryMask.BBullet.rawValue
player.name = "Player"
addChild(player)
firebutton = SKShapeNode(circleOfRadius: 75)
firebutton.position = CGPoint(x: 1000, y: 150)
firebutton.alpha = 0.7
firebutton.physicsBody?.isDynamic = false
firebutton.physicsBody?.affectedByGravity = false
firebutton.physicsBody?.categoryBitMask = 0
firebutton.physicsBody?.collisionBitMask = 100
firebutton.physicsBody?.contactTestBitMask = 100
firebutton.physicsBody?.contactTestBitMask = 0
firebutton.physicsBody?.collisionBitMask = 0
addChild(firebutton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
spawnenemybullets()
let position = touch.location(in: self)
if firebutton.contains(position){
bullet = SKSpriteNode(color: .orange, size: CGSize(width: 20, height: 10))
bullet.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 20, height: 10))
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.isDynamic = true
bullet.physicsBody?.allowsRotation = false
bullet.position.x = player.position.x + 90
bullet.position.y = player.position.y
bullet.physicsBody?.mass = 0.01
bullet.physicsBody?.categoryBitMask = CategoryMask.GBullet.rawValue
bullet.physicsBody?.collisionBitMask = CategoryMask.enemyships.rawValue
bullet.physicsBody?.contactTestBitMask = CategoryMask.enemyships.rawValue
bullet.name = "FBullet"
addChild(bullet)
bullet.physicsBody?.applyForce(CGVector(dx: 400, dy: 0))
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let position = touch.location(in: self)
if player.contains(position) {
if position.y > 85 && position.y < 665 && position.x > 85 && position.x < 640{
player.position = position
}
}
}
}
func spawnenemybullets () {
bullet = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 10))
bullet.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 20, height: 10))
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.isDynamic = true
bullet.physicsBody?.allowsRotation = false
bullet.position.x = 1300
bullet.position.y = 200
bullet.physicsBody?.mass = 0.01
bullet.physicsBody?.categoryBitMask = CategoryMask.BBullet.rawValue
bullet.physicsBody?.collisionBitMask = CategoryMask.player.rawValue
bullet.physicsBody?.contactTestBitMask = CategoryMask.player.rawValue
bullet.name = "EnemyBullet"
addChild(bullet)
bullet.physicsBody?.applyForce(CGVector(dx: -400, dy: 0))
}
func didBegin(_ contact: SKPhysicsContact) {
print("Contact Happened")
}
}
The two Sprites both have their contact masks set up but for some reason they are not colliding. When I test the project on my phone, they just go right through each other. How do I get them to contact each other?

You haven't created a physicsBody for player (or fireButton).
You also need to make yourself the physics contact delegate:
class GameScene: SKScene, SKPhysicsContactDelegate {
physicsWorld.contactDelegate = self
Edit: my step-by-step guide for collisions and contacts:
https://stackoverflow.com/a/51041474/1430420
A guide to collision and contactTest bit masks:
https://stackoverflow.com/a/40596890/1430420
Manipulating bit masks to turn individual collisions and contacts off and on.
https://stackoverflow.com/a/46495864/1430420

Related

Child nodes inside SKNode disappears with fast shaking device with CMMotionManager applied to move balls

I've create a SKNode with physicsBody edgeFromLoop circular inside which i've added 8 small circular nodes. and 8 circular nodes are moving with CMMotionManager inside Parent circular node. with fast motion shake some balls disappears from screen. SKScene Class given below
Balls only disappears when anyone shake mobile hard randomly.
There are 8 balls initially but after some hard shake reduced.
class GameScene: SKScene {
let motionManager = CMMotionManager()
var Circle = SKShapeNode(circleOfRadius: 108)
var sound = SKAction.playSoundFileNamed(getRandomSound(), waitForCompletion: false)
var collisionBitmasks: [UInt32] = [UInt32]()
override func didMove(to view: SKView) {
Circle.fillTexture = SKTexture.init(image: UIImage.init(named: "img_Ball") ?? UIImage())
Circle.position = CGPoint.init(x: 0, y: 0)
Circle.name = "defaultCircle"
Circle.lineWidth = 0.0
Circle.fillColor = SKColor.lightGray.withAlphaComponent(0.8)
Circle.physicsBody = SKPhysicsBody.init(edgeLoopFrom: UIBezierPath.init(ovalIn: CGRect.init(x: Circle.frame.minX + 12,
y: Circle.frame.minY + 12,
width: Circle.frame.width - 24,
height: Circle.frame.height - 24)).cgPath)
Circle.physicsBody?.isDynamic = true
Circle.physicsBody?.affectedByGravity = false
Circle.physicsBody?.allowsRotation = false
addChild(Circle)
self.physicsWorld.contactDelegate = self
startAcceleroMeter()
for index in 1...8 {
addBalls(index)
}
}
func stop() {
motionManager.stopAccelerometerUpdates()
Circle.children.forEach { (ball) in
ball.physicsBody?.isDynamic = false
ball.physicsBody?.affectedByGravity = false
}
}
func startAcceleroMeter() {
Circle.children.forEach { (ball) in
ball.physicsBody?.isDynamic = true
ball.physicsBody?.affectedByGravity = true
}
motionManager.startAccelerometerUpdates()
motionManager.accelerometerUpdateInterval = 0.1
motionManager.startAccelerometerUpdates(to: .main) { (motionData, error) in
let gravity = CGVector.init(dx: (motionData?.acceleration.x ?? 0.0) * 20, dy: (motionData?.acceleration.y ?? 0.0) * 20)
print(gravity)
self.physicsWorld.gravity = gravity
}
}
func stopPlaying() -> [String] {
var dictionary = [String]()
for (_, ball) in (Circle.children).enumerated() {
dictionary.append("\(ball.position.x), \(ball.position.y), 0.0")
}
return dictionary
}
func addBalls(_ ballNo: Int) {
let ball = SKShapeNode.init(circleOfRadius: 12)
ball.name = "ball\(ballNo)"
ball.fillTexture = SKTexture.init(linearGradientWithAngle: CGFloat.pi, colors: [BallColors(rawValue: ballNo)?.toUIColor(false) ?? UIColor(), BallColors(rawValue: ballNo)?.toUIColor(true) ?? UIColor()], locations: [0, 1], size: ball.frame.size)
ball.fillColor = UIColor.white
ball.physicsBody = SKPhysicsBody.init(circleOfRadius: 12)
ball.physicsBody?.isDynamic = true
ball.physicsBody?.affectedByGravity = true
ball.physicsBody?.allowsRotation = true
ball.physicsBody?.friction = 0.2
ball.physicsBody?.restitution = 0.9
ball.physicsBody?.linearDamping = 0.1
ball.physicsBody?.angularDamping = 0.1
ball.physicsBody?.mass = 0.349065870046616
ball.physicsBody?.usesPreciseCollisionDetection = true
ball.position = CGPoint(x: Circle.frame.midX - CGFloat(ballNo), y: Circle.frame.midY - CGFloat(ballNo))
ball.physicsBody?.fieldBitMask = 1
ball.physicsBody?.categoryBitMask = UInt32.init(ballNo + 100)
let collisionBitMask = UInt32.init(ballNo + 20) | UInt32.init(ballNo + 100)
collisionBitmasks.append(collisionBitMask)
ball.physicsBody?.collisionBitMask = collisionBitMask //To be different for each
ball.physicsBody?.contactTestBitMask = UInt32.init(ballNo + 20)
Circle.addChild(ball)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
if collisionBitmasks.contains(contact.bodyA.collisionBitMask) && collisionBitmasks.contains(contact.bodyB.collisionBitMask) {
run(sound)
}
}
}
You need a hidden magic here. Each small ball adds a constraint that can prevent any accident from high speeds:
ball.constraints = [SKConstraint.distance(SKRange(upperLimit: 108 - 12), to: Circle)]

sprite kit collision not working

This is a game I have been working on. In summary there is a moving block named enemy and I want it to collide with an invisible static block called invisibleGround2. I have it printing hit when they supposedly collide but they are not colliding. Ive read every swift collision documentation by apply and others out there and I dont know whats wrong. Any help would be much appreciated!
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var orb = SKSpriteNode(imageNamed: "orb")
var scrollingG:scrollingGround?
var invisibleGround = SKSpriteNode(imageNamed: "invisible")
var invisibleGround2 = SKSpriteNode(imageNamed: "invisible")
let enemies = [SKSpriteNode(imageNamed: "blueE.png"), SKSpriteNode(imageNamed: "redE.png")]
let enemyCategory : UInt32 = 1
let jumperCategory : UInt32 = 1
let rotateDuration = 2
let enemyMoveSpeed = 5
let groundScrollingSpeed = 5
func createOrb(){
let orbConst = frame.size.width/2
let xConstraint = SKConstraint.positionX(SKRange(constantValue: orbConst))
orb.position = CGPoint(x: frame.size.width/2, y: 480)
orb.physicsBody = SKPhysicsBody(texture: orb.texture!, size: orb.texture!.size())
orb.constraints = [xConstraint]
self.addChild(orb)
}
func createScrollingGround () {
scrollingG = scrollingGround.scrollingNodeWithImage(imageName: "ground", containerWidth: self.size.width)
scrollingG?.scrollingSpeed = CGFloat(groundScrollingSpeed)
scrollingG?.anchorPoint = .zero
self.addChild(scrollingG!)
}
func createGround(){
invisibleGround2.size.width = 1
invisibleGround2.size.height = 1
invisibleGround2.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:1, height: 100))
invisibleGround2.physicsBody?.isDynamic = false
invisibleGround2.position = CGPoint(x: 530, y: 191)
invisibleGround2.physicsBody?.categoryBitMask = jumperCategory
invisibleGround2.physicsBody?.collisionBitMask = enemyCategory
invisibleGround2.physicsBody?.contactTestBitMask = enemyCategory
invisibleGround2.name = "jumper"
invisibleGround.position = CGPoint(x: 0, y: 190)
invisibleGround.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: self.size.width * 3, height: 10))
invisibleGround.physicsBody?.isDynamic = false
self.addChild(invisibleGround2)
self.addChild(invisibleGround)
}
func getRandomEnemy(fromArray array:[SKSpriteNode])->SKSpriteNode{
return array[Int(arc4random_uniform(UInt32(array.count)))]
}
func spawnEnemy() {
if let enemy = getRandomEnemy(fromArray: enemies).copy() as? SKSpriteNode {
enemy.position = CGPoint(x: frame.size.width + frame.size.width/3, y: 440)
enemy.physicsBody = SKPhysicsBody(texture: enemy.texture!, size: enemy.texture!.size())
if enemy.size.width < 95 {
enemy.physicsBody?.categoryBitMask = enemyCategory
enemy.physicsBody?.collisionBitMask = jumperCategory
enemy.physicsBody?.contactTestBitMask = jumperCategory
}
enemy.name = "enemy"
self.addChild(enemy)
let moveLeft = SKAction.moveBy(x: -1500, y: 0, duration: TimeInterval(enemyMoveSpeed))
enemy.run(moveLeft)
}
}
func addEnemies () {
self.run(SKAction.repeatForever(SKAction.sequence([SKAction.run {
self.spawnEnemy()
}, SKAction.wait(forDuration: 4)])))
}
func jump() {
orb.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 335))
}
func rotate() {
let rotate = SKAction.rotate(byAngle: CGFloat(M_PI * -2.55), duration: TimeInterval(rotateDuration))
let repeatAction = SKAction.repeatForever(rotate)
orb.run(repeatAction)
}
override func didMove(to view: SKView) {
createScrollingGround()
createOrb()
createGround()
rotate()
addEnemies()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
jump()
}
override func update(_ currentTime: TimeInterval) {
if self.scrollingG != nil {
scrollingG?.update(currentTime: currentTime)
func didBegin(_ contact: SKPhysicsContact) {
let bodyA = contact.bodyA.categoryBitMask
let bodyB = contact.bodyB.categoryBitMask
if bodyA == jumperCategory && bodyB == enemyCategory {
print("hit")
} else if bodyA == enemyCategory && bodyB == jumperCategory {
print("hit 2")
}
}
}
}
}
Not an swer, but some things to check:
Have you set the scene’s physicsworld delegate property set to
self?
Move didBegin outside of update
Is the 'enemy' sprite's width < 95?
Add a print("Contact detected") as the first line of your relocated didBegin so you at least know that some contact has been detected.
See my answer here https://stackoverflow.com/a/43605825/1430420 for a simple SK collision demo which might help - I think it needs updates to Swift 4 which I'll try and do.

Swift Sprite Kit Physics Body: Ball slows down after applying an impulse

I am quite new to Swift and the Sprite Kit!
I tried to build a Pong game. Everything works well but the ball on which I am applying an impulse slows down really quickly!
I already turned off gravity, friction and damping and set the restitution of the ball and the scene to 1.
Here is my Code:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
    
let player = SKShapeNode(rectOf: CGSize(width: 200, height: 30))
let enemy = SKShapeNode(rectOf: CGSize(width: 200, height: 30))
let ball = SKShapeNode(circleOfRadius: 10)
override func didMove(to view: SKView) {
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.restitution = 1
border.friction = 0
border.angularDamping = 0
border.linearDamping = 0
self.physicsBody = border
scene?.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
player.position = CGPoint(x:  0, y: -(scene?.size.height)! * 0.35)
player.fillColor = UIColor.white
player.strokeColor = UIColor.clear
self.addChild(player)
player.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 200, height: 30))
player.physicsBody?.friction = 0
player.physicsBody?.isDynamic = false
player.physicsBody?.angularDamping = 0
player.physicsBody?.linearDamping = 0
player.physicsBody?.restitution = 1
player.physicsBody?.affectedByGravity = false
enemy.position = CGPoint(x: 0, y: (scene?.size.height)! * 0.35)
enemy.fillColor = UIColor.white
enemy.strokeColor = UIColor.clear
self.addChild(enemy)
enemy.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 200, height: 30))
enemy.physicsBody?.friction = 0
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.linearDamping = 0
enemy.physicsBody?.angularDamping = 0
enemy.physicsBody?.isDynamic = false
ball.position = CGPoint(x: 0, y: 0)
ball.fillColor = UIColor.white
ball.strokeColor = UIColor.clear
ball.physicsBody?.angularDamping = 0
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.friction = 0
ball.physicsBody?.restitution = 1
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.affectedByGravity = true
ball.physicsBody = SKPhysicsBody(circleOfRadius: 10)
ball.physicsBody?.isDynamic = true
self.addChild(ball)
ball.physicsBody?.applyImpulse(CGVector(dx: -30, dy: -30))
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let pos = touch.location(in: self)
player.position.x = pos.x
}
}
override func update(_ currentTime: TimeInterval) {
if ball.position.y >  0{
enemy.position.x = ball.position.x
}
}
}
Thank you already!
Be sure to always create your physics body before you change its properties
ball.physicsBody?.angularDamping = 0
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.friction = 0
ball.physicsBody?.restitution = 1
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.affectedByGravity = true
ball.physicsBody = SKPhysicsBody(circleOfRadius: 10) <----Needs to be first
ball.physicsBody?.isDynamic = true
self.addChild(ball)
ball.physicsBody?.applyImpulse(CGVector(dx: -30, dy: -30))

How to force on touch.location to work only in special place on screen?

that is my first question here so - Hello Word.
I have a small problem with my code. I want to force on this program to make balls appear only if you will touch over and equal y: 650 point and boxes to appear only if you will touch under y: 650 point. x position makes here no role.
That is my code:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
let objects = nodes(at: location)
if objects.contains(editLabel) {
editingMode = !editingMode
} else {
if editingMode {
let size = CGSize(width: GKRandomDistribution(lowestValue: 16, highestValue: 128).nextInt(), height: 16)
let box = SKSpriteNode(color: RandomColor(), size: size)
box.zRotation = RandomCGFloat(min: 0, max: 3)
box.position = location
box.physicsBody = SKPhysicsBody(rectangleOf: box.size)
box.physicsBody!.isDynamic = false
addChild(box)
} else {
let ball = SKSpriteNode(imageNamed: "ballRed")
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width / 2.0)
ball.physicsBody!.contactTestBitMask = ball.physicsBody!.collisionBitMask
ball.physicsBody!.restitution = 0.4
ball.position = location
ball.name = "ball"
addChild(ball)
}
}
}
}
Looking forward for help :)
For me this worked:
ball.position = location
if (location.y < 500) {
ball.position.y = 500
}

SKPhysics Contact not functioning properly

I am trying to use the SKPhysicsContactDelegate function in SpriteKit and it will not seem to work. I want one sprite to perform an action when it hits the other. I have set up breakpoints at the didBeginContact function and for some reason my application never calls this function. All help appreciated. Code posted below.
struct PhysicsCatagory {
static let Enemy :UInt32 = 0x1 << 0
static let Slider :UInt32 = 0x1 << 1
static let Circle :UInt32 = 0x1 << 2
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var EnemyTimer = NSTimer()
var Circle = SKSpriteNode()
var Slider = SKSpriteNode()
var FastButton = SKNode()
var Title = SKSpriteNode()
var Text = SKSpriteNode()
var Path = UIBezierPath()
var gameStarted = Bool()
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
self.backgroundColor = UIColor.whiteColor()
Circle = SKSpriteNode(imageNamed:"blueCircle")
Circle.size = CGSize(width: 140, height: 140)
Circle.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
Circle.zPosition = 1.0
self.addChild(Circle)
Slider = SKSpriteNode(imageNamed: "blocker1")
Slider.size = CGSize(width: 15, height: 50)
Slider.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2 + 80)
addChild(Slider)
Slider.zPosition = 1.0
moveClockWise()
}
func didBeginContact(contact: SKPhysicsContact) {
if contact.bodyA.node != nil && contact.bodyB.node != nil{
let firstBody = contact.bodyA.node as! SKSpriteNode
let secondBody = contact.bodyB.node as! SKSpriteNode
if ((firstBody.name == "Enemy") && (secondBody.name == "Slider")){
collisionBall(firstBody, Slider: secondBody)
}
else if ((firstBody.name == "Slider") && (secondBody.name == "Enemy")) {
collisionBall(secondBody, Slider: firstBody)
}
}
}
func collisionBall(Enemy : SKSpriteNode, Slider : SKSpriteNode){
Enemy.physicsBody?.dynamic = true
Enemy.physicsBody?.affectedByGravity = true
Enemy.physicsBody?.mass = 4.0
Slider.physicsBody?.mass = 4.0
Enemy.removeAllActions()
Enemy.physicsBody?.contactTestBitMask = 0
Enemy.physicsBody?.collisionBitMask = 0
Enemy.name = nil
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
//Slider.hidden = false
FastButton.hidden = false
Title.hidden = true
Text.hidden = true
EnemyTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(GameScene.Enemies), userInfo: nil, repeats: true)
//Physics
Slider.physicsBody?.categoryBitMask = PhysicsCatagory.Slider
Slider.physicsBody?.collisionBitMask = PhysicsCatagory.Enemy
Slider.physicsBody?.contactTestBitMask = PhysicsCatagory.Enemy
Slider.name = "Slider"
Slider.physicsBody?.dynamic = true
Slider.physicsBody?.affectedByGravity = true
if gameStarted == false{
gameStarted = true
}
else if gameStarted == true{
}
}
func moveClockWise(){
let dx = Slider.position.x / 2
let dy = Slider.position.y / 2
let rad = atan2(dy, dx)
let Path = UIBezierPath(arcCenter: CGPoint(x: self.frame.width / 2, y: self.frame.height / 2), radius: 90, startAngle: rad, endAngle: rad + CGFloat(M_PI * 4), clockwise: true)
let follow = SKAction.followPath(Path.CGPath, asOffset: false, orientToPath: true, speed: 150)
//let rotate = SKAction.rotateByAngle(75, duration: 100)
Slider.runAction(SKAction.repeatActionForever(follow).reversedAction())
//Slider.runAction(SKAction.repeatActionForever(rotate).reversedAction())
}
func Enemies(){
let Enemy = SKSpriteNode(imageNamed: "darkRedDot")
Enemy.size = CGSize(width: 20, height: 20)
//Physics
Enemy.physicsBody = SKPhysicsBody(circleOfRadius: Enemy.size.width / 2)
Enemy.physicsBody?.categoryBitMask = PhysicsCatagory.Enemy
Enemy.physicsBody?.contactTestBitMask = PhysicsCatagory.Slider //| PhysicsCatagory.Circle
Enemy.physicsBody?.collisionBitMask = PhysicsCatagory.Slider //| PhysicsCatagory.Circle
Enemy.physicsBody?.dynamic = true
Enemy.physicsBody?.affectedByGravity = false
Enemy.name = "Enemy"
The contact is not beginning because your slider has no physics body, making it impossible to recognize contact. Unless, it's not in this code, your slider has no physics body, but your enemy does. Try declaring Slider.phisicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "blocker1"), size: Slider.size)
Also, you should read about standard naming conventions in Swift. It is recommended that your variables always start with lowercase letters (eg. enemyTimer, circle, slider, fastButton, etc...).