Swift SpriteKit 接触処理(didBegin) が動作しない

投稿者: Anonymous

didBegin内に1つのNodeに対する接触は問題なく動作するのですが、didBegin内にもう一つ別のNodeの接触処理を書いたとこと、うまく作動しません。
どうかよろしくお願い致します。

func didBegin(_ contact: SKPhysicsContact) {

    var asteroid: SKPhysicsBody
    var target: SKPhysicsBody
    var itemBox: SKPhysicsBody

    if contact.bodyA.categoryBitMask == asteroidCategory {
        asteroid = contact.bodyA
        target = contact.bodyB
    } else {
        asteroid = contact.bodyB
        target = contact.bodyA
    }

    // ** 別の接触処置を記す場合、このコードでbodyAとbodyBを宣言してはダメなのでしょうか。
    if contact.bodyA.categoryBitMask == itemBoxCategory {
        itemBox = contact.bodyA
        target = contact.bodyB
    } else {
        itemBox = contact.bodyB
        target = contact.bodyA
    }

    guard let asteroidNode = asteroid.node as? AsteroidNode else { return }
    guard let tagetNode = target.node else { return }
    guard let itemBoxNode = itemBox.node else { return }

    // ** ここが作動しません。
    if(contact.bodyA.categoryBitMask == itemBoxCategory && contact.bodyB.categoryBitMask == spaceshipCategory)  || (contact.bodyB.categoryBitMask == itemBoxCategory && contact.bodyA.categoryBitMask == spaceshipCategory) {
        itemBoxNode.removeFromParent()
        addChild(item1)
    }

    guard let Bakuhatu = SKEmitterNode(fileNamed: "Bakuhatu") else { return }
    Bakuhatu.position = asteroidNode.position
    addChild(Bakuhatu)

    if target.categoryBitMask == missileCategory  {
        tagetNode.removeFromParent()
        asteroidNode.life -= missile.Pw

        if asteroidNode.life <= 0 {
            asteroidNode.removeFromParent()
        }
    }

    self.run(SKAction.wait(forDuration: 1.0)) {
        Bakuhatu.removeFromParent()
    }

    if target.categoryBitMask == spaceshipCategory {
        asteroidNode.removeFromParent()
        self.gameVC.lifeGauge.setProgress(gameVC.lifeGauge.progress - 0.2, animated: true)
        lifeLabel.text = "Life:(Int(gameVC.lifeGauge.progress * 100))"

        if self.gameVC.lifeGauge.progress <= 0.0 {
            GameOver()
        }
    }

}

※補足
接触に必要な
categoryBitMask
collisionBitMask
contactTestBitMask
は、下記の様に設定しております。

let spaceshipCategory : UInt32 = 0b0001
let missileCategory : UInt32 = 0b0010
let asteroidCategory  : UInt32 = 0b0100
let itemBoxCategory : UInt32 = 0b1000

//spaceship
self.spaceship.physicsBody?.categoryBitMask = spaceshipCategory
self.spaceship.physicsBody?.collisionBitMask = spaceshipCategory | missileCategory | asteroidCategory | itemBoxCategory
self.spaceship.physicsBody?.contactTestBitMask = asteroidCategory

//itemBox
itemBox.physicsBody?.categoryBitMask = itemBoxCategory
itemBox.physicsBody?.collisionBitMask = spaceshipCategory | itemBoxCategory
itemBox.physicsBody?.contactTestBitMask = spaceshipCategory

解決

まず大前提ですが、「spaceshipとitemBoxの接触を検出したい」と言う場合には、コメントに示したように、spaceship側とitemBox側の両方のcontactTestBitMaskに相手側を指定するようにしてください。片側だけだと、うまく検出してくれない場合があるようです。

self.spaceship.physicsBody?.contactTestBitMask = asteroidCategory | itemBoxCategory

さて、その部分の設定は正しくできているとして、あなたの現在のdidBegin(_:)を見てみると、最も問題になるのはこの行だと言えます。

    guard let asteroidNode = asteroid.node as? AsteroidNode else { return }

この行、「asteroid対何か」「何か対asteroid」の接触時の処理を書くためには必須のものですが、それ以外の接触でこの行にたどり着いた場合、変数asteroidには、その名前に反してasteroidを表すSKPhysicsBodyは入っていませんから、as? AsteroidNodeの結果が必ずnilとなり、else節内のreturnで処理を終了してしまいます。


今までは「asteroid対何か」「何か対asteroid」でしかdidBegin(_:)が呼ばれなかったところに「itemBox対spaceship」「spaceship対itemBox」の処理を割り込ませたために、どの部分がどんな場合に実行されるのかと言ったことがごっちゃになってしまっているのが、ある場合には必要な行が別の場合に邪魔をする、と言う原因になっています。

混乱を避けるためには、接触時の処理の種類ごとにメソッドを分けてやると良いでしょう。

//「asteroid対何か」「何か対asteroid」
    func hit(asteroid: SKPhysicsBody, target: SKPhysicsBody) {

        guard let asteroidNode = asteroid.node as? AsteroidNode else { return }
        guard let tagetNode = target.node else { return }

        guard let bakuhatu = SKEmitterNode(fileNamed: "Bakuhatu") else { return }
        bakuhatu.position = asteroidNode.position
        addChild(bakuhatu)

        if target.categoryBitMask == missileCategory  {
            tagetNode.removeFromParent()
            asteroidNode.life -= missile.Pw

            if asteroidNode.life <= 0 {
                asteroidNode.removeFromParent()
            }
        }

        self.run(SKAction.wait(forDuration: 1.0)) {
            bakuhatu.removeFromParent()
        }

        if target.categoryBitMask == spaceshipCategory {
            asteroidNode.removeFromParent()
            self.gameVC.lifeGauge.setProgress(gameVC.lifeGauge.progress - 0.2, animated: true)
            lifeLabel.text = "Life:(Int(gameVC.lifeGauge.progress * 100))"

            if self.gameVC.lifeGauge.progress <= 0.0 {
                GameOver()
            }
        }

    }

    //「itemBox対spaceship」「spaceship対itemBox」
    func hit(itemBox: SKPhysicsBody, target: SKPhysicsBody) {

        guard let itemBoxNode = itemBox.node else { return }

        itemBoxNode.removeFromParent()
        addChild(item1)

        //...
    }

(hit(asteroid:target:)中に書かれた処理の一部は、hit(itemBox:target:)にも必要なのかもしれません。その場合は、//...のところの必要な処理を追加していってください。もちろん両者に共通な処理は、さらに別のメソッドを作って、そこにまとめてしまっても構いません。)


上記のようなメソッドを用意した上で、didBegin(_:)の中では、接触の種類に応じて、上記のメソッドを呼び出してやるだけ、と言った具合にします。

ただし、

    if contact.bodyA.categoryBitMask == asteroidCategory {
        //...
    } else {
        //...
    }

と言った簡易判定は、接触判定の種類が「asteroid対何か」「何か対asteroid」しかなかったからうまくいっていたものです。接触判定の種類が増えた場合、簡易判定を2箇所に別個に書いても正しい判定はできません。

現在は、処理を行いたい接触は「asteroid対何か」「何か対asteroid」「itemBox対spaceship」「spaceship対itemBox」の4通りですから、それらをきちんと判定してやらないといけません。

    func didBegin(_ contact: SKPhysicsContact) {

        if contact.bodyA.categoryBitMask == asteroidCategory {
            //「asteroid対何か」
            hit(asteroid: contact.bodyA, target: contact.bodyB)
        } else if contact.bodyB.categoryBitMask == asteroidCategory {
            //「何か対asteroid」
            hit(asteroid: contact.bodyB, target: contact.bodyA)
        } else if contact.bodyA.categoryBitMask == itemBoxCategory && contact.bodyB.categoryBitMask == spaceshipCategory {
            //「itemBox対spaceship」
            hit(itemBox: contact.bodyA, target: contact.bodyB)
        } else if contact.bodyB.categoryBitMask == itemBoxCategory && contact.bodyA.categoryBitMask == spaceshipCategory {
            //「spaceship対itemBox」
            hit(itemBox: contact.bodyB, target: contact.bodyA)
        }
    }

後は、接触判定の種類がもっと増えても、同じように「メソッドを増やす」「didBegin(_:)中の判定を増やす」で対応できるのはおわかりいただけると思います。

細かいところで修正が必要になるかもしれませんが、まずはお試しください。

回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *