0

I have a SpriteKit physics contact firing in didBegin(contact:). I grab the physics body for the instance of the Dot object that I want to move offscreen, but when I try to change its position like so, nothing happens:

First Approach

/* In GameScene.swift */

func didBegin(_ contact: SKPhysicsContact) {
    let dotBody: SKPhysicsBody
    if contact.bodyA.categoryBitMask == 0b1 {
        dotBody = contact.bodyB
    } else {
        dotBody = contact.bodyA
    }

    if let dot = dotBody.node as? Dot {
        dot.position.x = 10000
    }
}

However, if I instead call a method in my Dot class, passing in that body, the position gets set correctly:

Second Approach

/* In GameScene.swift */

func didBegin(_ contact: SKPhysicsContact) {
    let dotBody: SKPhysicsBody
    if contact.bodyA.categoryBitMask == 0b1 {
        dotBody = contact.bodyB
    } else {
        dotBody = contact.bodyA
    }

    if let dot = dotBody.node as? Dot {
        dot.move()
    }        
}

Here is the Dot class:

import SpriteKit
class Dot : SKSpriteNode {
    let dotTex = SKTexture(imageNamed: "dot")
    init() {
        super.init(texture: dotTex, color: .clear, size: dotTex.size())
        self.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
        self.physicsBody?.categoryBitMask = 0b1 << 1
        self.physicsBody?.contactTestBitMask = 0b1
    }

    func move() {
        let reset = SKAction.run {
            self.position.x = 10000
        }
        self.run(reset)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

Can anyone explain why the position change in the second approach works but it does not work in my first approach?

Here's a link to the project on Github.

peacetype
  • 1,928
  • 3
  • 29
  • 49
  • With the second approach, you are changing dot's position via an SKAction. What happens if you try the first approach but with the same SKAction? – Steve Ives May 26 '17 at 11:08
  • With the first approach, how are the bodies being moved when they come into contact? If they are being moved by SKActions, and you then manually set the position of one of the bodies (which you do), this position change may not take effect if the movement action is still running. – Steve Ives May 26 '17 at 11:13
  • 1
    @Steve Ives - All motion is shown in the code there. In the first approach, I'm trying to move the node directly from within my game scene with `dot.position.x = 1000`. I'm not running any SKActions in that approach. The second approach is basically the same as the first, only I'm using an SKAction to run the `dot.position.x = 1000` line and doing it inside a separate Dot class, rather than in the game scene class. – peacetype May 26 '17 at 13:16
  • If that's all the motion, how do the bodies come into contact? – Steve Ives May 26 '17 at 13:17
  • 1
    I have a stationary node whose position I set with `touchesBegan`. So the player touches a dot node on the screen, and their player sprite node then appears directly on top of the dot node. That is the moment the two bodies make contact. – peacetype May 26 '17 at 13:20
  • Is the 'stationary node' the player? Are the physics bodies of the Player node and the Dot set up so that they collide? So the player touches the dot, the player sprite is placed on top of the dot and the dot should then immediately be moved off-screen? What actually happens - does the dot stay on screen in the same placef (or to one side) of the player sprite? – Steve Ives May 26 '17 at 13:25
  • 1
    Yes, everything is wired correctly for collisions. As you say, the dot should be moved off screen, but it stays in place and does not move. However, the second approach works as expected. I suspect it has something to do with the fact that in `didBegin(contact:)`, I'm taking an SKPhysicsBody and casting it as a Dot object, then trying to set its position directly. Since I'm instantiating many dot objects in the scene, I suspect that trying to cast the physics body is not getting me the reference to the dot object i need. But seems like it should work, and the compiler doesn't give any errors. – peacetype May 26 '17 at 13:31
  • Looks to me like you are changing position of your dot somewhere else, you just aren't telling us this – Knight0fDragon May 26 '17 at 13:40
  • If you put the player on top of the dot, and they are set to collide (and I mean collide, not contact), then the physics engine will immediately try to move them so that they are no longer on top of each other. whilst this is happening, you are also setting the dot's position, This could be causing an issue. If you do want the player sprite to be able to be placed on top of the dot, then you need to set up their 'collisionBitMask' so that they don't collide. – Steve Ives May 26 '17 at 14:23
  • @peacetype You're not casting an SKPhysicsBody as a Dot, you cast `contact.body` as `dotBody` and then `dotBody.node` as a Dot. If you option-click on the variable in Xcode, it will tell you what they are. (`dotBody` will be an SKPhysicsBody and `dot` will be a Dot). – Steve Ives May 26 '17 at 14:25
  • 1
    @Steve Ives - I tested again with collisionBitMask set to 0 for both the player and dot nodes, but got the same behavior. You are right about casting... I think the problem must lie there. Probably due to how my dot objects are being instantiated and I'm just not getting the correct reference in my game scene. It's probably hard to analyze without sharing the whole project. I was trying to keep this one succinct, lol – peacetype May 26 '17 at 17:26
  • Are your physics bodies categories correct? If it works with an action , it's weird. Can you put the move action in the 1st approach? – Steve Ives May 26 '17 at 18:02
  • 1
    There's a few scene files so I trimmed down the code and put the project on github: https://github.com/prinomen/dotTest1 – peacetype May 26 '17 at 18:21
  • 1
    @Steve Ives - I tried moving the action to GameScene (as in the first approach) and it did work. I guess that's not too surprising considering that same code worked in the Dot class. Although interesting that it works in GameScene. I'm still scratching my head as to why the line to change position in `didBegin(contact:)` does not work. – peacetype May 26 '17 at 18:25
  • Interesting - ill see if I can recreate the problem. – Steve Ives May 26 '17 at 18:26

2 Answers2

1

EDIT:

I think it happens because u are creating a subclass of SKSpriteNode rather than creating instance of a SKSpriteNode. Unless u are going to specialize it for something else I don't see a reason to do that. Instead I used these

import Foundation
import SpriteKit

class Dot {

let dotSprite: SKSpriteNode

init() {
    dotSprite = SKSpriteNode(imageNamed: "Spaceship")
//        dotSprite.physicsBody = SKPhysicsBody(circleOfRadius: dotSprite.size.width/2)
//        dotSprite.physicsBody?.categoryBitMask = 0b1 << 1
//        dotSprite.physicsBody?.contactTestBitMask = 0b1

}

func move() {
    self.dotSprite.position.x = 0
}

func moveAnim(){
    let reset = SKAction.moveTo(x: 0, duration: 3)
    dotSprite.run(reset)
}

}

And in my SKScene

import SpriteKit
import GameplayKit

class GameScene: SKScene {

let newDot = Dot()


override func didMove(to view: SKView) {

    self.addChild(newDot.dotSprite)
    newDot.dotSprite.setScale(0.5)
    newDot.dotSprite.position = CGPoint(x: frame.midX, y: frame.midY)

}


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    newDot.dotSprite.position.x = 200
    newDot.moveAnim()

}

}

Note that I have disable physics body coz I haven't created another body for it to land on (so it would keep on falling). Also I used the Spaceship as a sprite.

So now u can have any custom methods in the dot class. Also u can manipulate directly the properties of the dot SpriteNode. Also create multiple instances. And now you can change it directly when the contact happens or call a method in the dot class to move. Hope this was helpful. :)

  • OK, I just tried that but still no effect. The sense I am getting from people is that you should be able to move a node from `didBegin(contact:)` by adjusting its position in this way. Still trying to figure out why its not working. – peacetype May 27 '17 at 05:49
  • Thanks! This provides a helpful workaround. You mentioned that subclassing `SKSpriteNode` may be the cause of the problem. Seems like you may be on the right track since `SKSpriteNode` does not have a property called `position`. However, it makes me wonder because `SKSpriteNode` inherits from `SKNode` which does have a `position` property. My understanding is that a subclass should have access to all the methods of its superclass. I'm also not sure how to use this workaround in the `didBegin` method. I tried doing that several ways but only got errors. – peacetype May 29 '17 at 20:31
  • I can't tell exactly what's wrong without looking at what you are doing in the SKScene. I subclassed SKSpriteNode and I can change from touches began ( after i have created an instance of my Dot class). But I wouldn't usually subclass it since I am not providing new properties or methods. But it's upto the person. SKSpriteNode will have position since it is inherited from SKNode. – Chipster Chops May 29 '17 at 21:47
  • Ok let me try to use the contact – Chipster Chops May 29 '17 at 21:48
  • It seems I can't edit properties directly in the didbegin contact. But u can use SKAction. But you can easily create a global Bool so when the condition is met, set it to true. And use it in the update function to do any necessary changes. In ur case. have a reset bool. Set it to false in the did move to view. And when you condition is met set it to true in didbegincontact. And in the update function just put. if reset { your code.. reset = false}. Hope this helps. Let me put it in code format in another answer – Chipster Chops May 29 '17 at 23:19
1
func didBegin(_ contact: SKPhysicsContact) {
  let hitDot = contact.bodyB.node! as! SKSpriteNode
  let hitDot1 = contact.bodyA.node! as! SKSpriteNode
    if hitDot.name? == "dot" || hitDot1.name? == "dot" {
       reset = true
    }        
}

   override func update(_ currentTime: TimeInterval) {
    if reset {
        newDot.position.x = 0
        reset = false
    }
}

There might be small errors in the code coz i just edited here. But hope this gives u the idea. This should work without errors.

  • 1
    I read it happens coz of a bug in spriteKit. Hope this provides the answer to the question. Cheers – Chipster Chops May 30 '17 at 04:01
  • 1
    Awesome! This works. Thanks so much for your help. I think this is a good workaround. It's interesting to know this is a bug in SpriteKit. That's the only explanation that makes sense! I couldn't understand why it wasn't working otherwise. – peacetype May 30 '17 at 13:41
  • 1
    BTW, if you have a link to whatever you read that indicated this behavior is a bug, please let me know. It might help to file a report with Apple if they are not aware of this issue already. – peacetype May 30 '17 at 13:43
  • I read it from this website presented and approved as an answer. When I was skimming thru what might be the cause, but I don't have the specific link now. But I think you can easily do a search and find it. Goodluck with the game. – Chipster Chops May 30 '17 at 15:12
  • 2
    Here you go https://stackoverflow.com/questions/22810237/spritekit-cannot-change-node-position-in-contact-callback – Chipster Chops May 30 '17 at 15:29