ARKitでGeometoryの配置と重力

ARKitとSceneKitを使ってBoxの落下を試してみました。

以前にもやった覚えが合ったんですが、完全に忘れていて思い出すのに時間がかかりました…

Boxの配置

let box = SCNBox(width: 0.2, height: 0.2, length: 0.2,chamferRadius: 0)
let material = SCNMaterial()
material.diffuse.contents = UIColor.purple

let boxNode = SCNNode(geometry: box)
boxNode.geometry?.materials = [material]
boxNode.position = SCNVector3(0, 0, -0.5)

sceneView.scene.rootNode.addChildNode(boxNode)

重力の追加

重力に関してはSpriteKitのようにフィールド全体に対して何か処理をするというのは不要です。

SCNNodeに対してPhysicsBodyを追加するだけで重力が発生しオブジェクトが落下します。

boxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: box, options: nil))

定期的にboxを作成し落下させる

class ViewController: UIViewController, ARSCNViewDelegate {

    @IBOutlet var sceneView: ARSCNView! {
        didSet {
            sceneView.delegate = self
            sceneView.showsStatistics = true
            let scene = SCNScene()
            sceneView.scene = scene
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(createBox), userInfo: nil, repeats: true)
    }
    
    @objc private func createBox(){
        let box = SCNBox(width: 0.2, height: 0.2, length: 0.2,chamferRadius: 0)
        let material = SCNMaterial()
        let systemColor:[UIColor] = [.black, .darkGray, .lightGray, .white, .gray,
                                     .red, .green, .blue, .cyan, .yellow,
                                     .magenta, .orange, .purple, .brown, .clear]
        material.diffuse.contents = systemColor.randomElement()

        let boxNode = SCNNode(geometry: box)
        
        boxNode.geometry?.materials = [material]
        boxNode.position = SCNVector3(0, 0, -0.5)
        boxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: box, options: nil))
        
        sceneView.scene.rootNode.addChildNode(boxNode)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        let configuration = ARWorldTrackingConfiguration()
        sceneView.session.run(configuration)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        sceneView.session.pause()
    }
}

当たり判定をつける

障害物を置いて当たり判定を検証してみました

let obstacle = SCNBox(width: 3, height: 0.1, length: 3,chamferRadius: 0)
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "brick")

let obstacleNode = SCNNode(geometry: obstacle)
obstacleNode.geometry?.materials = [material]
obstacleNode.position = SCNVector3(0, -3.0, -0.5)
obstacleNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: obstacle, options: nil))

sceneView.scene.rootNode.addChildNode(obstacleNode)

boxの生成とほとんど同じですが、違うのはSCNPhysicsBodyのtypeをstaticに設定していることです。

staticにしていると衝突した際に移動しないので初期位置にずっと配置されます。typeをdynamicにしている場合重力やboxとの衝突が起こった際に落下していってしまいます。