SpriteKit & Swift: Creating nodes via didBeginContact messes up the positioning

萝らか妹 提交于 2020-01-03 17:10:30

问题


I don't know if this is a weird bug in Xcode or there's something about the SpriteKit's coordinate system I don't understand.

The premise is that the position of a node is always relative to its parent. However, whenever I call a block that creates and positions a node with a physics body from "didBeginContact" of SKPhysicsContactDelegate, the node is always positioned relative to the scene (instead of its parent). Note that calling this same block works as intended when triggered anywhere but "didBeginContact". Another thing is that, if I remove the physics body of the said node, the block will now work as intended even when called from "didBeginContact".

I've been stuck with this issue for two days and it would be too draggy to give other details about my actual code. So I've made a very simple project demonstrating this anomaly. Just create a new project in Xcode 6 with Spritekit Template, and replace GameViewController.swift and GameSwift.swift with the codes posted below. Just run in iPad Air and everything else should be self explanatory.

Final Notes:

  1. As you press the first button and makes contact to the second button, look at the lower left corner of the screen. You will see that the boxes are being "wrongly" positioned there.
  2. Try to remove the physics body of the box in "AddBox". It will now work as intended.
  3. Please let me know if you think this is a bug or there's something in the coordinates system or physics world that I don't understand

GameViewController.swift:

import SpriteKit

class GameViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let scene = GameScene()
        scene.size = view.frame.size
        let skView = self.view as SKView
        scene.scaleMode = .AspectFill
        skView.presentScene(scene)
    }
}

GameScene.swift:

import SpriteKit

let kButtonSize = CGSize(width: 500, height: 100)
let kContainerSize = CGSize(width: 500, height: 300)
let kBoxSize = CGSize(width: 25, height: 25)

class GameScene: SKScene, SKPhysicsContactDelegate {

    override func didMoveToView(view: SKView) {
        physicsWorld.contactDelegate = self
        addFirstButton()
        addSecondButton()
        addContainer()
    }

    func addFirstButton() {
        let button = SKSpriteNode(color: SKColor.blueColor(), size: kButtonSize)
        let label = SKLabelNode(text: "Call 'addBox' from didBeginContact")
        button.name = "firstButton"
        label.name = "firstLabel"
        button.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame))
        button.physicsBody = SKPhysicsBody(rectangleOfSize: button.size)
        button.physicsBody.allowsRotation = false
        button.physicsBody.affectedByGravity = false
        button.physicsBody.categoryBitMask = 0x1
        button.physicsBody.contactTestBitMask = 0x1
        button.addChild(label)
        addChild(button)
    }

    func addSecondButton() {
        let button = SKSpriteNode(color: SKColor.blueColor(), size: kButtonSize)
        let label = SKLabelNode(text: "Call 'addBox' from touchesBegan")
        button.name = "secondButton"
        label.name = "secondLabel"
        button.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame)-200)
        button.physicsBody = SKPhysicsBody(rectangleOfSize: button.size)
        button.physicsBody.dynamic = false
        button.physicsBody.categoryBitMask = 0x1
        button.physicsBody.contactTestBitMask = 0x1
        button.addChild(label)
        addChild(button)
    }

    func addContainer() {
        let container = SKSpriteNode(color: SKColor.greenColor(), size:kContainerSize)
        let label = SKLabelNode(text: "Created node should fall here")
        label.fontColor = SKColor.blackColor()
        container.name = "container"
        container.physicsBody = SKPhysicsBody(edgeLoopFromRect: container.frame)
        container.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame)+300)
        container.addChild(label)
        addChild(container)
    }

    func addBox() {
        let container = childNodeWithName("container")
        let box = SKSpriteNode(color: SKColor.blueColor(), size: kBoxSize)
        box.physicsBody = SKPhysicsBody(rectangleOfSize: box.size)
        box.position = CGPointMake(0, 100)
        container.addChild(box)
    }

    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        let touch = touches.anyObject() as UITouch
        let point = touch.locationInNode(self)
        let node = nodeAtPoint(point)
        if node.name == nil {return}

        switch node.name! {
        case "firstButton", "firstLabel":
            let button = childNodeWithName("firstButton") as SKSpriteNode
            button.physicsBody.applyImpulse(CGVectorMake(0, -500))
        case "secondButton", "secondLabel":
            addBox()
        default:
            break
        }
    }

    func didBeginContact(contact: SKPhysicsContact!) {
        addBox()
    }
}

回答1:


I have submitted a ticket regardomg this issue with Apple Bug Report. I hope this helps anyone who is encountering the same issue. Here is their response:

Apple Developer Relations24-Sep-2014 04:40 AM

Engineering has determined that this is an issue for you to resolve based on the following:

You can't modify a tree which it is being simulated, and this is clearly stated in Programming Guide.




回答2:


It seems like you can't create physics bodies in the contact delegate (this is similar to other engines, eg. AndEngine). Instead of creting new body straight into the delegate, mark some bool flag there and do your creation in the didSimulatePhysics or didFinishUpdate - works for me.



来源:https://stackoverflow.com/questions/25935571/spritekit-swift-creating-nodes-via-didbegincontact-messes-up-the-positioning

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!