57916


【追記編集】  
理解が進まないので自分の例を交えたいと思います。  
現在、私が取り組んでいる２Dゲームのプロジェクトにおけるクラスやスプライトの存在などは以下のようになっています。

  * GameScene: SKScene
  * Player: SKSpriteNode
  * Bullet: SKShapeNode（Player.swiftファイル内）
  * Enemy: SKSpriteNode

* * *

## 実現したい流れ

流れを簡単に説明しますと、

  1. GameSceneが呼び出される
  2. player(Playerクラス）とenemy(Enemyクラス）の生成
  3. スペースキーを押すとplayerからbullet(Bulletクラス）が放出（※bulletは連打などにより複数生成される）
  4. enemyに当たったらgetDamage(Enemyクラス関数）により敵のライフ減少

僭越ながら、以下にファイルの中身を最大限削減した状態で載せさせていただきました。

## 質問の補足

ファイルの内容を見ていただければわかると思うのですが、contactTestBitMaskを各ファイルごとに１箇所ずつ設定（？）しておいてます。  
どのように書けば呼び出されるのかなぁと色々なタイミング方法で試してはいますが、一度も呼び出されたことがありません。また色々なサイトを参考にしましたが、クラスをまたいだ当たり判定や、複数のオブジェクトに対してのものはなにぶん見つからず、自力で試してはみましたがそれらしい情報を得ることは叶いませんでした。

* * *

**GameScene.swift**

[code]

        import SpriteKit
        import GameplayKit
    
        class GameScene: SKScene {
    
    
    
       let player = Player(def_pos: CGPoint(x: 0.0, y: 0.0))
    　　　　　// 省略
    
            override func didMove(to view: SKView) {
    
                player.position = CGPoint(x: self.view!.bounds.maxX/2, y: (self.view!.bounds.maxY)/2)
                player.setScene(scene: self)
                self.addChild(player)
    
                let enemy = Enemy(def_pos: CGPoint(x: 300, y: 300))
                enemy.setScene(scene: self)
                self.addChild(enemy)
    
                player.physicsBody?.contactTestBitMask = enemy.physicsBody!.categoryBitMask
            }
    
            override func keyDown(with event: NSEvent) {
    
                switch event.keyCode {
                // 省略
                case 49:
                    player.shoot()
                default:
                    break
                }
            }
    
            override func keyUp(with event: NSEvent) {
    　　　　　　　// 省略
            }
    
            override func update(_ currentTime: TimeInterval) {
                // Called before each frame is rendered
                // 省略
                self.player.update()
            }
        }
    
        extension GameScene: SKPhysicsContactDelegate {
            func didBegin(_ contact: SKPhysicsContact) {
                print("------------衝突しました------------")
            }
        }
    
[/code]

**Player.swift**

[code]

    import Foundation
    import SpriteKit
    
    class Player: SKSpriteNode, SKPhysicsContactDelegate {
    
        var gameScene: SKScene!
        func setScene(scene: SKScene) {
            self.gameScene = scene
            print(scene)
        }
    
    
        init(def_pos: CGPoint) {
            // 省略
    
            super.init(texture: textures[2], color: NSColor.clear, size: CGSize(width: 40, height: 40))
            self.position = def_pos
    　　　　　// 省略
    
    
            self.physicsBody = SKPhysicsBody(circleOfRadius: 10)
            self.physicsBody?.affectedByGravity = false
            self.physicsBody?.allowsRotation = false
            self.physicsBody?.isDynamic = false
            self.physicsBody?.categoryBitMask = 0b1000
            self.physicsBody?.collisionBitMask = 0b1000
            self.physicsBody?.contactTestBitMask = 0b0000
    
        }
    
        // 省略
    
        func shoot() {
            let bullet = SKShapeNode(circleOfRadius: 3)
            bullet.fillColor = NSColor.yellow
            bullet.position = self.position
            bullet.physicsBody = SKPhysicsBody(circleOfRadius: 3)
            bullet.physicsBody?.affectedByGravity = false
            bullet.physicsBody?.categoryBitMask = 0b0100
            bullet.physicsBody?.collisionBitMask = 0b0100
            bullet.physicsBody?.velocity = CGVector(dx: 0, dy: 300)
            self.gameScene.addChild(bullet)
        }
    
    　　 // 省略
    
    
        func update() {
        }
    
        func didBegin(_ contact: SKPhysicsContact) {
            print("collision occured by player")
            getDamaged()
        }
        required init?(coder aDecoder: NSCoder) {
            fatalError()
        }
    
    }
    
    
[/code]

**Enemy.swift**

[code]

    import Foundation
    import SpriteKit
    
    
    class Enemy: SKSpriteNode, SKPhysicsContactDelegate {
    
        var gameScene: SKScene!
        func setScene(scene: SKScene) {
            self.gameScene = scene
            print(scene)
        }
    
        var health: Int!
    
        init(def_pos: CGPoint) {
    
            // 省略
            super.init(texture: textures.first, color: NSColor.clear, size: CGSize(width: 200, height: 200))
            self.position = def_pos
            self.health = 100
    
            self.physicsBody = SKPhysicsBody(circleOfRadius: 80)
            self.physicsBody?.affectedByGravity = false
            self.physicsBody?.allowsRotation = false
            self.physicsBody?.isDynamic = false
            self.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
            self.physicsBody?.categoryBitMask = 0b0001
            self.physicsBody?.collisionBitMask = 0b0001
            self.physicsBody?.contactTestBitMask = 0b0100
    
            // 省略
    
        }
    
        func getDamaged() {
    　　　　　// ダメージを受けたときの処理
            self.health -= 10
        }
    
        func didBegin(_ contact: SKPhysicsContact) {
            print("collision occured by enemy")
            getDamaged()
        }
    　　 // 省略
    
        required init?(coder aDecoder: NSCoder) {
            fatalError()
        }
    }
    
    
[/code]

それぞれのサブクラスはphysicsBodyをもち、スプライトひとつひとつはcategoryBitMaskとcollisionBitMaskを持っています。  
しかしながら、どこにcontactTestBitMaskを書けばいいのかがわかりません。  
いわゆる物理エンジンによる、自然な動き（衝突した後の動作など）は全く問題なく機能しますが、GameScene（最初に呼び出されるシーン）において定義した

[code]

    extension GameScene: SKPhysicsContact Delegate {
        func didBegin:(_ contact: SKPhysicsContact) {
            print("衝突！")
    
    }
    
[/code]

といったものを動作させたいときに色々と試しましたがいずれも表示されませんでした。

とすることで全ての敵に対し衝突判定を出せるかと思いましたが、意図したように動きませんでした。  
それに、私が試みようとしていることって、ゲーム作りにおいてはかなり基本的というか、誰もがやるようなことだと思うので、SpriteKitの方で何かしらメソッドがある気がしてなりません。  
もし、ご助力いただけるのであれば些細なことでも感謝いたします。回答よろしくお願いします。


CC BY-SA 4.0
2019-08-16T13:50:12.253
57409
2019-09-07T13:06:39.557
2019-09-07T12:28:45.913
16877
16877
question
swift spritekit
SpriteKitのcontactTestBitMaskを用いた衝突判定を２つのサブクラス（SKSpriteNode）間で行いたい


# ###(1) 一番肝心なところ。

あなたのコードには、`physicsWorld.contactDelegate`を設定している部分がありません。

接触判定のメソッドが呼ばれるのは、実行時に上記のプロパティに設定された1つのインスタンスだけです。`SKPhysicsContactDelegate`を実装したクラスの`didBegin(_:)`メソッドが必ず呼ばれるわけではありません。

特定の`SKSpriteNode`に全然関係ない接触判定の通知をしても仕方ないので、普通は`SKSpriteNode`に`SKPhysicsContactDelegate`を実装させるようなことはしません。接触判定に関する処理は全部`GameScene`中で行う、と思っていてください。

## ###(2) 接触判定には関係ないが致命的なところ

`SKSpriteNode`の中に`GameScene`を(強参照で)保持するようなプロパティを作ってはいけません。両者の間で循環参照が発生しますので、深刻なメモリリークが発生します。

# ###(3) `contactTestBitMask`には接触判定したい相手の`categoryBitMask`を設定する

いつどのファイルでなんてのはあまり重要ではありません。 **接触判定したい状況になるまでに済ませておく** だけで構いません。

なお、設定内容については、[こちらの別質問に対する回答](https://ja.stackoverflow.com/a/57879/13972)を再度熟読してみてください。

ちゃんとした設定を行うには「どのカテゴリーのノードとどのカテゴリーのノードに対する接触判定を行うか」をきちんと決めておかないといけませんが、以下のコードでは、

[enemy] <-> [bullet]

間の接触判定だけを行うものとしています。

* * *

上記のような事柄を踏まえてあなたのコードを書き直すと、こんな感じになります。

### GameScene.swift

[code]

    import SpriteKit
    import GameplayKit
    
    //各カテゴリに対するbit maskを定数宣言しておく
    let playerBit: UInt32 = 1<<3
    let bulletBit: UInt32 = 1<<2
    let enemyBit : UInt32 = 1<<0
    
    class GameScene: SKScene {
        
        let player = Player(def_pos: CGPoint(x: 0.0, y: 0.0))
        
        //...
        
        override func didMove(to view: SKView) {
            physicsWorld.contactDelegate = self //###(1)
            
            player.position = CGPoint(x: self.view!.bounds.maxX/2, y: (self.view!.bounds.maxY)/2)
            //player.setScene(scene: self) ###(2)
            self.addChild(player)
            
            let enemy = Enemy(def_pos: CGPoint(x: 300, y: 300))
            //enemy.setScene(scene: self) ###(2)
            self.addChild(enemy)
            
            //player.physicsBody?.contactTestBitMask = enemy.physicsBody!.categoryBitMask ###(3)
        }
        
        override func keyDown(with event: NSEvent) {
            switch event.keyCode {
            // 省略
            case 49:
                player.shoot(in: self) //###(2)
            default:
                break
            }
        }
    }
    
    extension GameScene: SKPhysicsContactDelegate {
        func didBegin(_ contact: SKPhysicsContact) {
            print("------------衝突しました------------")
            let node1: SKNode
            let node2: SKNode
            if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
                (node1, node2) = (contact.bodyA.node!, contact.bodyB.node!)
            } else {
                (node1, node2) = (contact.bodyB.node!, contact.bodyA.node!)
            }
            if node1.physicsBody?.categoryBitMask == enemyBit {
                let enemy = node1 as! Enemy
                if node2.physicsBody?.categoryBitMask == bulletBit {
                    enemy.getDamaged()
                }
                //...
            }
            //...
        }
    }
    
[/code]

### Player.swift

[code]

    import Foundation
    import SpriteKit
    
    class Player: SKSpriteNode/*, SKPhysicsContactDelegate ###(1) */ {
        
        //###(2)
    //    var gameScene: SKScene!
    //    func setScene(scene: SKScene) {
    //        self.gameScene = scene
    //        print(scene)
    //    }
        
        init(def_pos: CGPoint) {
            //...
            super.init(texture: textures[2], color: NSColor.clear, size: CGSize(width: 40, height: 40))
            self.position = def_pos
            //...
            
            self.physicsBody = SKPhysicsBody(circleOfRadius: 10)
            self.physicsBody?.affectedByGravity = false
            self.physicsBody?.allowsRotation = false
            self.physicsBody?.isDynamic = false
            self.physicsBody?.categoryBitMask = playerBit
            self.physicsBody?.collisionBitMask = 0
            self.physicsBody?.contactTestBitMask = 0 //###(3)
        }
        
        //...
        
        func shoot(in scene: SKScene) { //###(2)
            let bullet = SKShapeNode(circleOfRadius: 3)
            bullet.fillColor = NSColor.yellow
            bullet.position = self.position
            bullet.physicsBody = SKPhysicsBody(circleOfRadius: 3)
            bullet.physicsBody?.affectedByGravity = false
            bullet.physicsBody?.categoryBitMask = bulletBit
            bullet.physicsBody?.collisionBitMask = 0
            bullet.physicsBody?.contactTestBitMask = enemyBit //###(3)
            bullet.physicsBody?.velocity = CGVector(dx: 0, dy: 300)
            //###(2)
            //self.gameScene.addChild(bullet)
            scene.addChild(bullet)
        }
        
        //...
        
        //### (1)
    //    func didBegin(_ contact: SKPhysicsContact) {
    //        print("collision occured by player")
    //        getDamaged()
    //    }
        
        //...
    }
    
[/code]

### Enemy.swift

[code]

    import Foundation
    import SpriteKit
    
    class Enemy: SKSpriteNode/*, SKPhysicsContactDelegate ###(1) */ {
    
        //###(2) SKNode 
    //    var gameScene: SKScene!
    //    func setScene(scene: SKScene) {
    //        self.gameScene = scene
    //        print(scene)
    //    }
        
        var health: Int!
        
        init(def_pos: CGPoint) {
            //...
            super.init(texture: textures.first, color: NSColor.clear, size: CGSize(width: 200, height: 200))
            self.position = def_pos
            self.health = 100
            
            self.physicsBody = SKPhysicsBody(circleOfRadius: 80)
            self.physicsBody?.affectedByGravity = false
            self.physicsBody?.allowsRotation = false
            self.physicsBody?.isDynamic = false
            self.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
            self.physicsBody?.categoryBitMask = enemyBit
            self.physicsBody?.collisionBitMask = 0
            self.physicsBody?.contactTestBitMask = bulletBit
            //...
        }
        
        func getDamaged() {
            // ダメージを受けたときの処理
            self.health -= 10
        }
    
        //###(1)
    //    func didBegin(_ contact: SKPhysicsContact) {
    //        print("collision occured by enemy")
    //        getDamaged()
    //    }
        
        //...
    }
    
[/code]

削除すべきところをわかりやすくするために、コメントアウトするだけにしてあります。実際にはさくっと削除してください。

また、現在の`collisionBitMask`の設定には全く意味がないので仮に`0`にしてあります。衝突処理が(「接触判定」ではなく、跳ね返りなどの物理処理)がうまく行われない場合には、この値を調整してください。

* * *

元コードに省略されている部分があったり、こちらが何か見逃している可能性もあったりで、すぐにご期待の動作をするようになるかはなんとも言えませんが、お試しの上、お気付きの点があれば、コメント等でお知らせください。


CC BY-SA 4.0
2019-09-07T07:48:51.540
57916
2019-09-07T13:06:39.557
2020-06-17T08:14:45.997
-1
13972
57409
answer