5
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SceneKitAdvent Calendar 2018

Day 18

SceneKitのSCNPhysicsContactDelegateで衝突判定を行う

Last updated at Posted at 2018-12-18

大好評の「ペチャバト」にも用いられているSceneKitの物理判定API、Physicsについてを書きたいと思います。

GIFイメージ-96F071D4106D-1.gif

SCNPhysicsContactDelegate

よく使われると思うのが、SCNPhysicsContactDelegateで、当たり判定が発生したときに呼ばれるメソッドが以下のように定義されています。

  • func physicsWorld(SCNPhysicsWorld, didBegin: SCNPhysicsContact)
  • func physicsWorld(SCNPhysicsWorld, didUpdate: SCNPhysicsContact)
  • func physicsWorld(SCNPhysicsWorld, didEnd: SCNPhysicsContact)

didBegin, didUpdate, didEndで呼ばれる3種類のメソッドがあり、それぞれ呼ばれるタイミングが異なります。今回は物体同士の衝突が終わったら呼ばれる、didBeginを例に、実際に2つの物体がぶつかったときのハンドリング方法を簡単に紹介します。

まずはDelegateをセット

まずは、 sceneView.scene.physicsWorldのcontactDelegateにViewControllerをセット

override func viewDidLoad() {
    super.viewDidLoad()

    sceneView.scene.physicsWorld.contactDelegate = self
}

extension ViewController: SCNPhysicsContactDelegate {
    func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {

    }
}

当たったら呼ばれるメソッドを定義

そして、SCNPhysicsContactDelegateのphysicsWorld(didBeginを定義していきます。

引数にはSCNPhysicsWorldとSCNPhysicsContactがあって、前者は環境の情報、後者は当たり判定の情報です。

func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
    let nodeA = contact.nodeA
    let nodeB = contact.nodeB
}

以下のようなケースでは、contact.nodeAかcontact.nodeBに、青いボールか赤いマトのNodeがそれぞれセットされています。

GIFイメージ-96F071D4106D-1.gif

当たり判定を行う

例えば、以下のように的のNodeとボールのNodeが定義されているとしましょう。

let matoNode = SCNNode()
matoNode.name = "mato"

let ballNode = SCNNode()
ballNode.name = "ball"

その場合SCNPhysicsContactDelegateのメソッドにおいて、以下のように当たったかどうか判定します。(もっとスマートな書き方あるかもしれませんが)

func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
    let nodeA = contact.nodeA
    let nodeB = contact.nodeB
    
    if (nodeA.name == "mato" && nodeB.name == "ball") || (nodeB.name == "mato" && nodeA.name == "ball") {
        // 当たった!!
    }
}

当たったときに、触覚フィードバックをしたり、相手に当たり情報を送信したりして、シューティングバトルを成立させることができます。

当たり判定を行うためのNodeの設定

実は、ただ単にNodeを設定しただけでは、当たり判定は発火しません。先にあげたマトのNodeとボールのNodeをそれぞれ具体的に設定してみましょう。

マトのNode

SCNPhysicsShapeを設定する必要があります。geometryを引数にわたします。

let cylinder = SCNCylinder(radius: 0.1, height: 0.05)
let matoNode = SCNNode(geometry: box)
node.name = "mato"
node.position = SCNVector3Make(0, 0, -1.5)

// add PhysicsShape
let shape = SCNPhysicsShape(geometry: cylinder, options: nil)
node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape)
node.physicsBody?.isAffectedByGravity = false

ボールのNode

当てる側のSCNPhysicsBodyにはcontactTestBitMaskを設定するのを忘れずに!
こちらデフォルトは0ですが、そのままだと当たり判定が発火しません。

let ball = SCNSphere(radius: 0.1)
ball.firstMaterial?.diffuse.contents = UIColor.blue

let ballNode = SCNNode(geometry: ball)
ballNode.name = "ball"

// add PhysicsShape
let shape = SCNPhysicsShape(geometry: ball, options: nil)
ballNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape)
ballNode.physicsBody?.contactTestBitMask = 1
ballNode.physicsBody?.isAffectedByGravity = false

以上!

まとめ

  • SceneKitのSCNPhysicsContactDelegateのメソッドには3種類ある
  • SCNPhysicsContactからnodeAとnodeBがあるので、その名前からどのNodeとどのNodeが衝突したか判定できる
  • SCNPhysicsShapeを設定するのを忘れずに
  • 当てる側にはContactTestBitMaskも忘れずに

サンプルコード

https://github.com/kboy-silvergym/ARKit-Emperor のPhysicsにあります!

参考になる記事

5
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?