我正在尝试检测ARKit SceneKit中两个节点之间的接触

时间:2019-06-11 04:09:06

标签: swift xcode scenekit arkit

我正在创建AR UFO入侵者游戏。我用搅拌器创建了一个UFO,并将.dae文件添加到art.scnassets下的xCode中,并将其转换为.scn。接下来,我使用“触摸开始”功能创建了一个橙色激光节点。我正在尝试检测UFO节点和橙色激光节点何时接触,从而导致得分增加1分。该应用程序的构建和运行是在移动UFO节点并在触摸时也创建橙色激光节点的情况下进行的,但是,它不会检测到接触或碰撞并添加得分。

我尝试了枚举BodyType和struct OptionSet。

import UIKit
import SceneKit
import ARKit
import Foundation

enum BodyType:Int {

    case UFO = 1
    case orangelaser = 2
    case redlaser = 4

}

extension UIColor {
    convenience init(rgb: UInt) {
        self.init(
            red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0,
            green: CGFloat((rgb & 0x00FF00) >> 8) / 255.0,
            blue: CGFloat(rgb & 0x0000FF) / 255.0,
            alpha: CGFloat(1.0)
        )
    }
}


class ViewController: UIViewController, ARSCNViewDelegate, ARSessionDelegate, SCNPhysicsContactDelegate {

// .........

    var gamerunning: Int = 0

    var score: Int = 0
    var lives: Int = 3
    var ammo: Int = 100

    var coinCount = 0
    var displaylivesammocounter = 0

    var rotationtimer: Int = 0
    var UFOxposition: Float = 0
    var UFOyposition: Float = 0
    var UFOzposition: Float = 0

    var UFONode: SCNNode!
    var orangelaserNode: SCNNode!

    var detectedPlanes: [String : SCNNode] = [:]
    var translation = matrix_identity_float4x4
    var gamePos = SCNVector3Make(0.0, 0.0, 0.0)

   // ......

    override func viewDidLoad() {
        super.viewDidLoad()

        // Set the view's delegate
        sceneView.delegate = self



        // Show statistics such as fps and timing information
        sceneView.showsStatistics = true

        // Create a new scene
        let scene = SCNScene(named: "art.scnassets/UFO final with paint.scn")!

        // Set the scene to the view
        sceneView.scene = scene
        sceneView.scene.physicsWorld.contactDelegate = self

        createUFO()

        // .............

        UFONode.isHidden = true

        // .........


    func createUFO() {


        UFONode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
        UFONode.physicsBody?.restitution = 0.0
        UFONode.physicsBody?.friction = 1.0

        // Set the category masks
        UFONode.physicsBody?.categoryBitMask = BodyType.UFO.rawValue

        // Set the contact masks
        UFONode.physicsBody?.contactTestBitMask = BodyType.orangelaser.rawValue

        // Set the collission masks
        UFONode.physicsBody?.collisionBitMask = BodyType.orangelaser.rawValue

        UFONode = sceneView.scene.rootNode.childNode(withName: "UFO", recursively: true)!
        sceneView.scene.rootNode.addChildNode(UFONode)

    }

    // .............

    func startGame() {

        // .................

        gamerunning = 1

        UFONode.isHidden = false

        // ...................

        let UFOrotationTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { timer in

            if (self.rotationtimer >= 1) {
                timer.invalidate()
            }

            self.UFONode.runAction(SCNAction.repeat(SCNAction.rotateBy(x: 0, y: 1, z: 0, duration: 1), count: 1))

            func random(min: CGFloat, max: CGFloat) -> CGFloat {
                assert(min < max)
                return CGFloat(Float(arc4random()) / 0xFFFFFFFF) * (max - min) + min
            }

            self.UFOyposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFOxposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFOzposition = Float(random(
                min: -0.2,
                max: 0.2
           ))

            self.UFONode.runAction(SCNAction.moveBy(x: CGFloat(self.UFOxposition), y: CGFloat(self.UFOyposition), z: CGFloat(self.UFOzposition), duration: 1.0))

        })

    }

    func restartSameGame(){

        // ..............

        gamerunning = 1

        UFONode.isHidden = false

        // ................

        let UFOrotationTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { timer in

            if (self.rotationtimer >= 1) {
                timer.invalidate()
            }

            self.UFONode.runAction(SCNAction.repeat(SCNAction.rotateBy(x: 0, y: 1, z: 0, duration: 1), count: 1))

            func random(min: CGFloat, max: CGFloat) -> CGFloat {
                assert(min < max)
                return CGFloat(Float(arc4random()) / 0xFFFFFFFF) * (max - min) + min
            }

            self.UFOyposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFOxposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFOzposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFONode.runAction(SCNAction.moveBy(x: CGFloat(self.UFOxposition), y: CGFloat(self.UFOyposition), z: CGFloat(self.UFOzposition), duration: 1.0))

        })

    }

    func newGame() {

        // ................

        score = 0
        ammo = 100
        lives = 3

        // ......................

        gamerunning = 1

        UFONode.isHidden = false

        // ........................

        let UFOrotationTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { timer in

            if (self.rotationtimer >= 1) {
                timer.invalidate()
            }

            self.UFONode.runAction(SCNAction.repeat(SCNAction.rotateBy(x: 0, y: 1, z: 0, duration: 1), count: 1))

            func random(min: CGFloat, max: CGFloat) -> CGFloat {
                assert(min < max)
                return CGFloat(Float(arc4random()) / 0xFFFFFFFF) * (max - min) + min
            }

            self.UFOyposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFOxposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFOzposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFONode.runAction(SCNAction.moveBy(x: CGFloat(self.UFOxposition), y: CGFloat(self.UFOyposition), z: CGFloat(self.UFOzposition), duration: 1.0))

        })

    }

    @IBAction func PlayGameButton(_ sender: Any) {

        startGame()

    }

    @IBAction func PlayAgainButton(_ sender: Any) {

            if interstitial.isReady {
                interstitial.present(fromRootViewController: self)
            } else {

                self.rotationtimer = 0

                if (self.ammo > 0 && self.lives > 0)  {
                    self.restartSameGame()
                } else {
                    self.newGame()
                }

        }

    }

    func addScore() {

        score = score + 1

        DisplayScore.text = "\(score)"
        DisplayLives.text = "\(lives)"
        DisplayAmmo.text = "\(ammo)"

        if (lives <= 0) {
            gameOver()
        }

        if (ammo <= 0) {
            gameOver()
        }

    }

    func decreaseLives() {

        if (lives > 0) {
            lives = lives - 1
        }

        DisplayScore.text = "\(score)"
        DisplayLives.text = "\(lives)"
        DisplayAmmo.text = "\(ammo)"

        if (lives <= 0) {
            gameOver()
        }

        if (ammo <= 0) {
            gameOver()
        }

    }

    func decreaseAmmo() {

        if (ammo > 0) {
            ammo = ammo - 1
        }

        DisplayScore.text = "\(score)"
        DisplayLives.text = "\(lives)"
        DisplayAmmo.text = "\(ammo)"

        if (lives <= 0) {
            gameOver()
        }

        if (ammo <= 0) {
            gameOver()
        }

    }

    // .....................

    func gameOver() {

        DisplayScore.text = "\(score)"
        DisplayLives.text = "\(lives)"
        DisplayAmmo.text = "\(ammo)"

        UFONode.isHidden = true
        gamerunning = 0


        MoreLivesAmmoButton.isHidden = false
        EarnRewardsButton.isHidden = false
        PlayAgainButton.isHidden = false

        rotationtimer = 1

        // .....................

        GameOverLabel.isHidden = false



    }

    // .............................

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // Create a session configuration
        let configuration = ARWorldTrackingConfiguration()
        configuration.planeDetection = [.horizontal, .vertical]

        // Run the view's session
        sceneView.session.run(configuration)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        // Pause the view's session
        sceneView.session.pause()
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let frame = sceneView.session.currentFrame else { return } //1
        let camMatrix = SCNMatrix4(frame.camera.transform)
        let direction = SCNVector3Make(-camMatrix.m31 * 5.0, -camMatrix.m32 * 10.0, -camMatrix.m33 * 5.0) //2
        let position = SCNVector3Make(camMatrix.m41, camMatrix.m42, camMatrix.m43) //3
        if ((ammo > 0) && (lives > 0) && (UFONode.isHidden == false) && (gamerunning == 1)) {
            let orangelaser = SCNCapsule(capRadius: 0.25, height: 1.0) //1
            orangelaser.firstMaterial?.diffuse.contents = UIColor(red: 245.0 / 255.0, green: 127.0 / 255.0, blue: 44.0 / 255.0, alpha: 1 )
            orangelaser.firstMaterial?.emission.contents = UIColor(red: 245.0 / 255.0, green: 127.0 / 255.0, blue: 44.0 / 255.0, alpha: 1 ) //2
            orangelaserNode = SCNNode(geometry: orangelaser)
            orangelaserNode.scale = SCNVector3(x: 0.009, y: 0.009, z: 0.009)
            orangelaserNode.position = position //3
            self.orangelaserNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
            orangelaserNode.physicsBody?.mass = 1.5; // 1.5kg
            orangelaserNode.physicsBody?.restitution = 0.25
            orangelaserNode.physicsBody?.friction = 0.75
            orangelaserNode.physicsBody?.categoryBitMask = BodyType.orangelaser.rawValue
            orangelaserNode.physicsBody?.contactTestBitMask = BodyType.UFO.rawValue
            orangelaserNode.physicsBody?.collisionBitMask = BodyType.UFO.rawValue
            sceneView.scene.rootNode.addChildNode(orangelaserNode)
            orangelaserNode.runAction(SCNAction.sequence([SCNAction.wait(duration: 10.0), SCNAction.removeFromParentNode()])) //5
            orangelaserNode.physicsBody?.applyForce(direction, asImpulse: true) //6

            decreaseAmmo()

        }
    }

    func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
        print("contact happened!")
        if (contact.nodeA.physicsBody?.categoryBitMask == BodyType.UFO.rawValue && contact.nodeB.physicsBody?.categoryBitMask == BodyType.orangelaser.rawValue ){

            print("collision UFO and orangelaser")

            addScore()

        } else if (contact.nodeB.physicsBody?.categoryBitMask == BodyType.UFO.rawValue && contact.nodeA.physicsBody?.categoryBitMask == BodyType.orangelaser.rawValue ){

            print("collision orangelaser and UFO")

            addScore()

        }

    }




    // MARK: - ARSCNViewDelegate

/*
    // Override to create and configure nodes for anchors added to the view's session.
    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
        let node = SCNNode()

        return node
    }
*/

    func session(_ session: ARSession, didFailWithError error: Error) {
        // Present an error message to the user

    }

    func sessionWasInterrupted(_ session: ARSession) {
        // Inform the user that the session has been interrupted, for example, by presenting an overlay

    }

    func sessionInterruptionEnded(_ session: ARSession) {
        // Reset tracking and/or remove existing anchors if consistent tracking is required

    }
}

应用程序生成并运行。我没有看到任何错误消息。但是,当橙色激光节点与UFO接触或碰撞时,什么也没发生。

1 个答案:

答案 0 :(得分:0)

要在两个对象之间的ARKit中实现碰撞检测,请确保已遵循每个步骤:

1。。创建一个包含所有主体类型的枚举:

enum BodyType : Int {
    case type1 = 1
    case type2 = 2
} 

2。。将物理物体categoryBitMask应用于所有需要碰撞的节点:

node1.physicsBody = SCNPhysicsBody(type: .static, shape: nil)
node1.physicsBody?.categoryBitMask = BodyType.type1.rawValue

node2.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
node2.physicsBody?.categoryBitMask = BodyType.type2.rawValue

3。。将contactTestBitMask添加到移动对象中以仅在您的节点上过滤碰撞触发器:

node2.physicsBody?.contactTestBitMask = BodyType.type2.rawValue

4。。符合SCNPhysicsContactDelegate并实施physicsWorld(_: didBegin:)

将此添加到viewDidLoad()

self.sceneView.scene.physicsWorld.contactDelegate = self

按照这些步骤,只要您的节点之间发生冲突,就会physicsWorld(_: didBegin:)委托被调用:

extension ViewController: SCNPhysicsContactDelegate {
    func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
        // Collision happened between contact.nodeA and contact.nodeB
    }
}